From 76dd816e709ef230231173b43076c4a423577a4a Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Thu, 20 Oct 2011 12:47:27 +0200 Subject: [PATCH 01/44] Added getParents() -> returns an arrayList containing the sample's parent(s) if available --- .../broadinstitute/sting/gatk/samples/Sample.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java b/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java index b39fdd79d..a14d999ea 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java +++ b/public/java/src/org/broadinstitute/sting/gatk/samples/Sample.java @@ -3,6 +3,7 @@ package org.broadinstitute.sting.gatk.samples; import org.broadinstitute.sting.utils.exceptions.UserException; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -110,6 +111,17 @@ public class Sample implements Comparable { // implements java.io.Serial return infoDB.getSample(paternalID); } + public ArrayList getParents(){ + ArrayList parents = new ArrayList(2); + Sample parent = getMother(); + if(parent != null) + parents.add(parent); + parent = getFather(); + if(parent != null) + parents.add(parent); + return parents; + } + /** * Get gender of the sample * @return property of key "gender" - must be of type Gender From ef6a6fdfe499809fc3fd6282a460c2fd542963c5 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Thu, 20 Oct 2011 12:49:18 +0200 Subject: [PATCH 02/44] Added getAsMap -> returns the likelihoods as an EnumMap with Genotypes as keys and likelihoods as values. --- .../utils/variantcontext/GenotypeLikelihoods.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java index dba16cf86..292bc17cd 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java @@ -25,8 +25,12 @@ package org.broadinstitute.sting.utils.variantcontext; import org.broad.tribble.TribbleException; +import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; +import java.util.EnumMap; +import java.util.Map; + public class GenotypeLikelihoods { public static final boolean CAP_PLS = false; public static final int PL_CAP = 255; @@ -94,6 +98,16 @@ public class GenotypeLikelihoods { return likelihoodsAsString_PLs; } + public EnumMap getAsMap(boolean normalizeFromLog10){ + //Make sure that the log10likelihoods are set + double[] likelihoods = normalizeFromLog10 ? MathUtils.normalizeFromLog10(getAsVector()) : getAsVector(); + EnumMap likelihoodsMap = new EnumMap(Genotype.Type.class); + likelihoodsMap.put(Genotype.Type.HOM_REF,likelihoods[Genotype.Type.HOM_REF.ordinal()-1]); + likelihoodsMap.put(Genotype.Type.HET,likelihoods[Genotype.Type.HET.ordinal()-1]); + likelihoodsMap.put(Genotype.Type.HOM_VAR, likelihoods[Genotype.Type.HOM_VAR.ordinal() - 1]); + return likelihoodsMap; + } + private final static double[] parsePLsIntoLikelihoods(String likelihoodsAsString_PLs) { if ( !likelihoodsAsString_PLs.equals(VCFConstants.MISSING_VALUE_v4) ) { String[] strings = likelihoodsAsString_PLs.split(","); From 1c61a573296f6df9a3b2af20255c0deff1ece565 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Thu, 20 Oct 2011 13:06:44 +0200 Subject: [PATCH 03/44] Original rewrite of PhaseByTransmission: - Adapted to get the trio information from the SampleDB (i.e. from Pedigree file (ped)) => Multiple trios can be passed as argument - Mendelian violations and trio phasing possibilities are pre-calculated and stored in Maps. => Runtime is ~3x faster - Genotype combinations possible only given two MVs are now given a squared MV prior (e.g. 0/0+0/0=>1/1 is given 10^-16 prior if the MV prior is 10^-8) - Corrected bug: In case the best genotype combination is Het/Het/Het, the genotypes are now set appropriately (before original genotypes were left even if they weren't Het/Het/Het) - Basic reporting added: -- mvf argument let the user specify a file to report remaining MVs -- When the walker ends, some basic stats about the genotype reconfiguration and phasing are output Known problems: - GQ is not recalculated even if the genotype changes Possible improvements: - Phase partially typed trios - Use standard Allele/Genotype Classes for the storage of the pre-calculated phase --- .../walkers/phasing/PhaseByTransmission.java | 781 +++++++++++++----- 1 file changed, 569 insertions(+), 212 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index 3eedc2a28..12b541526 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -7,18 +7,18 @@ import org.broadinstitute.sting.gatk.arguments.StandardVariantContextInputArgume import org.broadinstitute.sting.gatk.contexts.AlignmentContext; import org.broadinstitute.sting.gatk.contexts.ReferenceContext; import org.broadinstitute.sting.gatk.refdata.RefMetaDataTracker; +import org.broadinstitute.sting.gatk.samples.Sample; import org.broadinstitute.sting.gatk.walkers.RodWalker; import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.SampleUtils; import org.broadinstitute.sting.utils.codecs.vcf.*; -import org.broadinstitute.sting.utils.text.XReadLines; +import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.variantcontext.Allele; import org.broadinstitute.sting.utils.variantcontext.Genotype; import org.broadinstitute.sting.utils.variantcontext.VariantContext; import org.broadinstitute.sting.utils.variantcontext.VariantContextUtils; -import java.io.File; -import java.io.FileNotFoundException; +import java.io.PrintStream; import java.util.*; /** @@ -32,13 +32,13 @@ import java.util.*; * the genotypes exhibit a Mendelian violation. This walker assumes there are only three samples in the VCF file to * begin. */ -public class PhaseByTransmission extends RodWalker { +public class PhaseByTransmission extends RodWalker, HashMap> { @ArgumentCollection protected StandardVariantContextInputArgumentCollection variantCollection = new StandardVariantContextInputArgumentCollection(); - @Argument(shortName="f", fullName="familySpec", required=true, doc="Patterns for the family structure (usage: mom+dad=child). Specify several trios by supplying this argument many times and/or a file containing many patterns.") - public ArrayList familySpecs = null; + @Argument(shortName = "mvf",required = false,fullName = "MendelianViolationsFile", doc="File to output the mendelian violation details.") + private PrintStream mvFile = null; @Output protected VCFWriter vcfWriter = null; @@ -48,239 +48,523 @@ public class PhaseByTransmission extends RodWalker { private final Double MENDELIAN_VIOLATION_PRIOR = 1e-8; - private class Trio { - private String mother; - private String father; - private String child; + private ArrayList trios = new ArrayList(); - public Trio(String mother, String father, String child) { - this.mother = mother; - this.father = father; - this.child = child; - } + //Matrix of priors for all genotype combinations + private EnumMap>> mvCountMatrix; - public Trio(String familySpec) { - String[] pieces = familySpec.split("[\\+\\=]"); + //Matrix of allele transmission + private EnumMap>> transmissionMatrix; - this.mother = pieces[0]; - this.father = pieces[1]; - this.child = pieces[2]; - } + //Metrics counters hashkeys + private final Byte NUM_TRIO_GENOTYPES_CALLED = 0; + private final Byte NUM_TRIO_GENOTYPES_NOCALL = 1; + private final Byte NUM_TRIO_GENOTYPES_PHASED = 2; + private final Byte NUM_HET = 3; + private final Byte NUM_HET_HET_HET = 4; + private final Byte NUM_VIOLATIONS = 5; - public String getMother() { return mother; } - public String getFather() { return father; } - public String getChild() { return child; } + private enum AlleleType { + NO_CALL, + REF, + VAR, + UNPHASED_REF, + UNPHASED_VAR } - private ArrayList trios = new ArrayList(); + //Stores a trio-genotype + private class TrioPhase { - public ArrayList getFamilySpecsFromCommandLineInput(ArrayList familySpecs) { - if (familySpecs != null) { - // Let's first go through the list and see if we were given any files. We'll add every entry in the file to our - // spec list set, and treat the entries as if they had been specified on the command line. - ArrayList specs = new ArrayList(); - for (String familySpec : familySpecs) { - File specFile = new File(familySpec); + private ArrayList trioAlleles = new ArrayList(6); - try { - XReadLines reader = new XReadLines(specFile); + private ArrayList getAlleles(Genotype.Type genotype){ + ArrayList alleles = new ArrayList(2); + if(genotype == Genotype.Type.HOM_REF){ + alleles.add(AlleleType.REF); + alleles.add(AlleleType.REF); + } + else if(genotype == Genotype.Type.HET){ + alleles.add(AlleleType.REF); + alleles.add(AlleleType.VAR); + } + else if(genotype == Genotype.Type.HOM_VAR){ + alleles.add(AlleleType.VAR); + alleles.add(AlleleType.VAR); + } + else{ + alleles.add(AlleleType.NO_CALL); + alleles.add(AlleleType.NO_CALL); + } + return alleles; + } - List lines = reader.readLines(); - for (String line : lines) { - specs.add(new Trio(line)); - } - } catch (FileNotFoundException e) { - specs.add(new Trio(familySpec)); // not a file, so must be a family spec - } - } + private ArrayList phaseSingleIndividualAlleles(Genotype.Type genotype){ + if(genotype == Genotype.Type.HET){ + ArrayList phasedAlleles = new ArrayList(2); + phasedAlleles.add(AlleleType.UNPHASED_REF); + phasedAlleles.add(AlleleType.UNPHASED_VAR); + return phasedAlleles; + } + else + return getAlleles(genotype); + } - return specs; + private ArrayList phaseMonoParentFamilyAlleles(Genotype.Type parent, Genotype.Type child){ + ArrayList phasedAlleles = new ArrayList(4); + //Special case for Het/Het as it is ambiguous + if(parent == Genotype.Type.HET && child == Genotype.Type.HET){ + phasedAlleles.add(AlleleType.UNPHASED_REF); + phasedAlleles.add(AlleleType.UNPHASED_VAR); + phasedAlleles.add(AlleleType.UNPHASED_REF); + phasedAlleles.add(AlleleType.UNPHASED_VAR); } - return new ArrayList(); + ArrayList parentAlleles = getAlleles(parent); + ArrayList childAlleles = getAlleles(child); + + int childTransmittedAlleleIndex = childAlleles.indexOf(parentAlleles.get(0)); + if(childTransmittedAlleleIndex > -1){ + phasedAlleles.add(parentAlleles.get(0)); + phasedAlleles.add(parentAlleles.get(1)); + phasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); + phasedAlleles.add(childAlleles.get(0)); + } + else if((childTransmittedAlleleIndex = childAlleles.indexOf(parentAlleles.get(1))) > -1){ + phasedAlleles.add(parentAlleles.get(1)); + phasedAlleles.add(parentAlleles.get(0)); + phasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); + phasedAlleles.add(childAlleles.get(0)); + } + else{ + parentAlleles.addAll(childAlleles); + for(AlleleType allele : parentAlleles){ + if(allele == AlleleType.REF){ + phasedAlleles.add(AlleleType.UNPHASED_REF); + } + else if(allele == AlleleType.VAR){ + phasedAlleles.add(AlleleType.UNPHASED_VAR); + } + else{ + phasedAlleles.add(AlleleType.NO_CALL); + } + } + } + + return phasedAlleles; + } + + private ArrayList phaseFamilyAlleles(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ + ArrayList phasedAlleles = new ArrayList(6); + + Set> possiblePhasedChildGenotypes = new HashSet>(); + ArrayList motherAlleles = getAlleles(mother); + ArrayList fatherAlleles = getAlleles(father); + ArrayList childAlleles = getAlleles(child); + + //Build all possible child genotypes for the given parent's genotypes + for (AlleleType momAllele : motherAlleles) { + for (AlleleType fatherAllele : fatherAlleles) { + ArrayList possiblePhasedChildAlleles = new ArrayList(2); + possiblePhasedChildAlleles.add(momAllele); + possiblePhasedChildAlleles.add(fatherAllele); + possiblePhasedChildGenotypes.add(possiblePhasedChildAlleles); + } + } + + for (ArrayList phasedChildGenotype : possiblePhasedChildGenotypes) { + int firstAlleleIndex = phasedChildGenotype.indexOf(childAlleles.get(0)); + int secondAlleleIndex = phasedChildGenotype.lastIndexOf(childAlleles.get(1)); + if (firstAlleleIndex != secondAlleleIndex && firstAlleleIndex > -1 && secondAlleleIndex > -1) { + //Add mother's alleles + phasedAlleles.add(phasedChildGenotype.get(0)); + if(motherAlleles.get(0) != phasedAlleles.get(0)) + phasedAlleles.add(motherAlleles.get(0)); + else + phasedAlleles.add(motherAlleles.get(1)); + + //Add father's alleles + phasedAlleles.add(phasedChildGenotype.get(1)); + if(fatherAlleles.get(0) != phasedAlleles.get(2)) + phasedAlleles.add(fatherAlleles.get(0)); + else + phasedAlleles.add(fatherAlleles.get(1)); + + //Add child's alleles + phasedAlleles.addAll(phasedChildGenotype); + return phasedAlleles; + } + } + + //If this is reached then no phasing could be found + motherAlleles.addAll(fatherAlleles); + motherAlleles.addAll(childAlleles); + for(AlleleType allele : motherAlleles){ + if(allele == AlleleType.REF){ + phasedAlleles.add(AlleleType.UNPHASED_REF); + } + else if(allele == AlleleType.VAR){ + phasedAlleles.add(AlleleType.UNPHASED_VAR); + } + else{ + phasedAlleles.add(AlleleType.NO_CALL); + } + } + return phasedAlleles; + } + + + public TrioPhase(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ + + //Take care of cases where one or more family members are no call + if(child == Genotype.Type.NO_CALL){ + trioAlleles.addAll(phaseSingleIndividualAlleles(mother)); + trioAlleles.addAll(phaseSingleIndividualAlleles(father)); + trioAlleles.add(AlleleType.NO_CALL); + trioAlleles.add(AlleleType.NO_CALL); + } + else if(mother == Genotype.Type.NO_CALL){ + trioAlleles.add(AlleleType.NO_CALL); + trioAlleles.add(AlleleType.NO_CALL); + if(father == Genotype.Type.NO_CALL){ + trioAlleles.add(AlleleType.NO_CALL); + trioAlleles.add(AlleleType.NO_CALL); + trioAlleles.addAll(phaseSingleIndividualAlleles(child)); + } + else + trioAlleles.addAll(phaseMonoParentFamilyAlleles(father, child)); + } + else if(father == Genotype.Type.NO_CALL){ + trioAlleles.addAll(phaseMonoParentFamilyAlleles(mother, child)); + trioAlleles.add(2, AlleleType.NO_CALL); + trioAlleles.add(3, AlleleType.NO_CALL); + } + //Special case for Het/Het/Het as it is ambiguous + else if(mother == Genotype.Type.HET && father == Genotype.Type.HET && child == Genotype.Type.HET){ + trioAlleles.add(AlleleType.UNPHASED_REF); + trioAlleles.add(AlleleType.UNPHASED_VAR); + trioAlleles.add(AlleleType.UNPHASED_REF); + trioAlleles.add(AlleleType.UNPHASED_VAR); + trioAlleles.add(AlleleType.UNPHASED_REF); + trioAlleles.add(AlleleType.UNPHASED_VAR); + } + //All family members have genotypes and at least one of them is not Het + else{ + trioAlleles = phaseFamilyAlleles(mother, father, child); + } + } + + public ArrayList getPhasedGenotypes(Allele ref, Allele alt, Genotype motherGenotype, Genotype fatherGenotype, Genotype childGenotype, int transmissionProb,ArrayList phasedGenotypes){ + phasedGenotypes.add(getPhasedGenotype(ref,alt,motherGenotype,transmissionProb,trioAlleles.subList(0,2))); + phasedGenotypes.add(getPhasedGenotype(ref,alt,fatherGenotype,transmissionProb,trioAlleles.subList(2,4))); + phasedGenotypes.add(getPhasedGenotype(ref,alt,childGenotype,transmissionProb,trioAlleles.subList(4,6))); + return phasedGenotypes; + } + + private Genotype getPhasedGenotype(Allele refAllele, Allele altAllele, Genotype genotype, int transmissionProb, List phasedAlleles){ + + //Add the transmission probability + Map genotypeAttributes = new HashMap(); + genotypeAttributes.putAll(genotype.getAttributes()); + genotypeAttributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, transmissionProb); + + boolean isPhased = true; + + List alleles = new ArrayList(2); + + //If unphased, return original genotype + for(AlleleType allele : phasedAlleles){ + if(allele == AlleleType.NO_CALL){ + genotype = Genotype.modifyAttributes(genotype, genotypeAttributes); + return genotype; + } + //Otherwise add the appropriate allele + else if(allele == AlleleType.UNPHASED_REF){ + isPhased = false; + alleles.add(refAllele); + } + else if(allele == AlleleType.UNPHASED_VAR){ + isPhased = false; + alleles.add(altAllele); + } + else if(allele == AlleleType.REF){ + alleles.add(refAllele); + } + else if(allele == AlleleType.VAR){ + alleles.add(altAllele); + } + } + //TODO: Recalculate GQ field for the new genotype + //Remove the GQ attribute as it needs to be recalculated for the new genotype assignment + //double[] likelihoods = genotype.getLikelihoods().getAsVector(); + + //genotypeAttributes.put(VCFConstants.GENOTYPE_QUALITY_KEY,likelihoods[1]); + genotype = Genotype.modifyAttributes(genotype, genotypeAttributes); + return new Genotype(genotype.getSampleName(), alleles, genotype.getNegLog10PError(), null, genotype.getAttributes(), isPhased); + } + + } /** * Parse the familial relationship specification, and initialize VCF writer */ public void initialize() { - trios = getFamilySpecsFromCommandLineInput(familySpecs); - ArrayList rodNames = new ArrayList(); rodNames.add(variantCollection.variants.getName()); - Map vcfRods = VCFUtils.getVCFHeadersFromRods(getToolkit(), rodNames); Set vcfSamples = SampleUtils.getSampleList(vcfRods, VariantContextUtils.GenotypeMergeType.REQUIRE_UNIQUE); + //Get the trios from the families passed as ped + setTrios(); + if(trios.size()<1) + throw new UserException.BadInput("No PED file passed or no trios found in PED file. Aborted."); + + Set headerLines = new HashSet(); headerLines.addAll(VCFUtils.getHeaderFields(this.getToolkit())); - headerLines.add(new VCFFormatHeaderLine(TRANSMISSION_PROBABILITY_TAG_NAME, 1, VCFHeaderLineType.Float, "Probability that the phase is correct given that the genotypes are correct")); + headerLines.add(new VCFFormatHeaderLine(TRANSMISSION_PROBABILITY_TAG_NAME, 1, VCFHeaderLineType.Integer, "Phred score of the phase given that the genotypes are correct")); headerLines.add(new VCFHeaderLine("source", SOURCE_NAME)); vcfWriter.writeHeader(new VCFHeader(headerLines, vcfSamples)); + + buildMatrices(); + + if(mvFile != null) + mvFile.println("#CHROM\tPOS\tFILTER\tAC\tFAMILY\tTP\tMOTHER_GT\tMOTHER_DP\tMOTHER_RAD\tMOTHER_AAD\tMOTHER_HRPL\tMOTHER_HETPL\tMOTHER_HAPL\tFATHER_GT\tFATHER_DP\tFATHER_RAD\tFATHER_AAD\tFATHER_HRPL\tFATHER_HETPL\tFATHER_HAPL\tCHILD_GT\tCHILD_DP\tCHILD_RAD\tCHILD_AAD\tCHILD_HRPL\tCHILD_HETPL\tCHILD_HAPL"); + } - private double computeTransmissionLikelihoodOfGenotypeConfiguration(Genotype mom, Genotype dad, Genotype child) { - double[] momLikelihoods = MathUtils.normalizeFromLog10(mom.getLikelihoods().getAsVector()); - double[] dadLikelihoods = MathUtils.normalizeFromLog10(dad.getLikelihoods().getAsVector()); - double[] childLikelihoods = MathUtils.normalizeFromLog10(child.getLikelihoods().getAsVector()); + /** + * Select Trios only + */ + private void setTrios(){ - int momIndex = mom.getType().ordinal() - 1; - int dadIndex = dad.getType().ordinal() - 1; - int childIndex = child.getType().ordinal() - 1; - - return momLikelihoods[momIndex]*dadLikelihoods[dadIndex]*childLikelihoods[childIndex]; - } - - private ArrayList createAllThreeGenotypes(Allele refAllele, Allele altAllele, Genotype g) { - List homRefAlleles = new ArrayList(); - homRefAlleles.add(refAllele); - homRefAlleles.add(refAllele); - Genotype homRef = new Genotype(g.getSampleName(), homRefAlleles, g.getNegLog10PError(), null, g.getAttributes(), false); - - List hetAlleles = new ArrayList(); - hetAlleles.add(refAllele); - hetAlleles.add(altAllele); - Genotype het = new Genotype(g.getSampleName(), hetAlleles, g.getNegLog10PError(), null, g.getAttributes(), false); - - List homVarAlleles = new ArrayList(); - homVarAlleles.add(altAllele); - homVarAlleles.add(altAllele); - Genotype homVar = new Genotype(g.getSampleName(), homVarAlleles, g.getNegLog10PError(), null, g.getAttributes(), false); - - ArrayList genotypes = new ArrayList(); - genotypes.add(homRef); - genotypes.add(het); - genotypes.add(homVar); - - return genotypes; - } - - private int getNumberOfMatchingAlleles(Allele alleleToMatch, Genotype g) { - List alleles = g.getAlleles(); - int matchingAlleles = 0; - - for (Allele a : alleles) { - if (!alleleToMatch.equals(a)) { - matchingAlleles++; + Map> families = this.getSampleDB().getFamilies(); + Set family; + ArrayList parents; + for(String familyID : families.keySet()){ + family = families.get(familyID); + if(family.size()!=3){ + logger.info(String.format("Caution: Family %s has %d members; At the moment Phase By Transmission only supports trios. Family skipped.",familyID,family.size())); } - } - - return matchingAlleles; - } - - private boolean isMendelianViolation(Allele refAllele, Allele altAllele, Genotype mom, Genotype dad, Genotype child) { - int numMomRefAlleles = getNumberOfMatchingAlleles(refAllele, mom) > 0 ? 1 : 0; - int numMomAltAlleles = getNumberOfMatchingAlleles(altAllele, mom) > 0 ? 1 : 0; - - int numDadRefAlleles = getNumberOfMatchingAlleles(refAllele, dad) > 0 ? 1 : 0; - int numDadAltAlleles = getNumberOfMatchingAlleles(altAllele, dad) > 0 ? 1 : 0; - - int numChildRefAlleles = getNumberOfMatchingAlleles(refAllele, child); - int numChildAltAlleles = getNumberOfMatchingAlleles(altAllele, child); - - return (numMomRefAlleles + numDadRefAlleles < numChildRefAlleles || numMomAltAlleles + numDadAltAlleles < numChildAltAlleles); - } - - private ArrayList getPhasedGenotypes(Genotype mom, Genotype dad, Genotype child) { - Set possiblePhasedChildGenotypes = new HashSet(); - - for (Allele momAllele : mom.getAlleles()) { - for (Allele dadAllele : dad.getAlleles()) { - ArrayList possiblePhasedChildAlleles = new ArrayList(); - possiblePhasedChildAlleles.add(momAllele); - possiblePhasedChildAlleles.add(dadAllele); - - Genotype possiblePhasedChildGenotype = new Genotype(child.getSampleName(), possiblePhasedChildAlleles, child.getNegLog10PError(), child.getFilters(), child.getAttributes(), true); - - possiblePhasedChildGenotypes.add(possiblePhasedChildGenotype); - } - } - - ArrayList finalGenotypes = new ArrayList(); - - for (Genotype phasedChildGenotype : possiblePhasedChildGenotypes) { - if (child.sameGenotype(phasedChildGenotype, true)) { - Allele momTransmittedAllele = phasedChildGenotype.getAllele(0); - Allele momUntransmittedAllele = mom.getAllele(0) != momTransmittedAllele ? mom.getAllele(0) : mom.getAllele(1); - - ArrayList phasedMomAlleles = new ArrayList(); - phasedMomAlleles.add(momTransmittedAllele); - phasedMomAlleles.add(momUntransmittedAllele); - - Genotype phasedMomGenotype = new Genotype(mom.getSampleName(), phasedMomAlleles, mom.getNegLog10PError(), mom.getFilters(), mom.getAttributes(), true); - - Allele dadTransmittedAllele = phasedChildGenotype.getAllele(1); - Allele dadUntransmittedAllele = dad.getAllele(0) != dadTransmittedAllele ? dad.getAllele(0) : dad.getAllele(1); - - ArrayList phasedDadAlleles = new ArrayList(); - phasedDadAlleles.add(dadTransmittedAllele); - phasedDadAlleles.add(dadUntransmittedAllele); - - Genotype phasedDadGenotype = new Genotype(dad.getSampleName(), phasedDadAlleles, dad.getNegLog10PError(), dad.getFilters(), dad.getAttributes(), true); - - finalGenotypes.add(phasedMomGenotype); - finalGenotypes.add(phasedDadGenotype); - finalGenotypes.add(phasedChildGenotype); - - return finalGenotypes; - } - } - - finalGenotypes.add(mom); - finalGenotypes.add(dad); - finalGenotypes.add(child); - - return finalGenotypes; - } - - private ArrayList phaseTrioGenotypes(Allele ref, Allele alt, Genotype mother, Genotype father, Genotype child) { - ArrayList finalGenotypes = new ArrayList(); - finalGenotypes.add(mother); - finalGenotypes.add(father); - finalGenotypes.add(child); - - if (mother.isCalled() && father.isCalled() && child.isCalled()) { - ArrayList possibleMotherGenotypes = createAllThreeGenotypes(ref, alt, mother); - ArrayList possibleFatherGenotypes = createAllThreeGenotypes(ref, alt, father); - ArrayList possibleChildGenotypes = createAllThreeGenotypes(ref, alt, child); - - double bestConfigurationLikelihood = 0.0; - double bestPrior = 0.0; - Genotype bestMotherGenotype = mother; - Genotype bestFatherGenotype = father; - Genotype bestChildGenotype = child; - - double norm = 0.0; - - for (Genotype motherGenotype : possibleMotherGenotypes) { - for (Genotype fatherGenotype : possibleFatherGenotypes) { - for (Genotype childGenotype : possibleChildGenotypes) { - double prior = isMendelianViolation(ref, alt, motherGenotype, fatherGenotype, childGenotype) ? MENDELIAN_VIOLATION_PRIOR : 1.0 - 12*MENDELIAN_VIOLATION_PRIOR; - double configurationLikelihood = computeTransmissionLikelihoodOfGenotypeConfiguration(motherGenotype, fatherGenotype, childGenotype); - norm += prior*configurationLikelihood; - - if (prior*configurationLikelihood > bestPrior*bestConfigurationLikelihood) { - bestConfigurationLikelihood = configurationLikelihood; - bestPrior = prior; - bestMotherGenotype = motherGenotype; - bestFatherGenotype = fatherGenotype; - bestChildGenotype = childGenotype; - } - } + else{ + for(Sample familyMember : family){ + parents = familyMember.getParents(); + if(parents.size()>0){ + if(family.containsAll(parents)) + this.trios.add(familyMember); + else + logger.info(String.format("Caution: Family %s is not a trio; At the moment Phase By Transmission only supports trios. Family skipped.",familyID)); + break; + } } } - if (!(bestMotherGenotype.isHet() && bestFatherGenotype.isHet() && bestChildGenotype.isHet())) { - Map attributes = new HashMap(); - attributes.putAll(bestChildGenotype.getAttributes()); - attributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, bestPrior*bestConfigurationLikelihood / norm); - bestChildGenotype = Genotype.modifyAttributes(bestChildGenotype, attributes); + } - finalGenotypes = getPhasedGenotypes(bestMotherGenotype, bestFatherGenotype, bestChildGenotype); + + + } + + private void buildMatrices(){ + mvCountMatrix = new EnumMap>>(Genotype.Type.class); + transmissionMatrix = new EnumMap>>(Genotype.Type.class); + for(Genotype.Type mother : Genotype.Type.values()){ + mvCountMatrix.put(mother,new EnumMap>(Genotype.Type.class)); + transmissionMatrix.put(mother,new EnumMap>(Genotype.Type.class)); + for(Genotype.Type father : Genotype.Type.values()){ + mvCountMatrix.get(mother).put(father,new EnumMap(Genotype.Type.class)); + transmissionMatrix.get(mother).put(father,new EnumMap(Genotype.Type.class)); + for(Genotype.Type child : Genotype.Type.values()){ + mvCountMatrix.get(mother).get(father).put(child, getCombinationMVCount(mother, father, child)); + transmissionMatrix.get(mother).get(father).put(child,new TrioPhase(mother,father,child)); + } + } + } + } + + private int getCombinationMVCount(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ + + //Child is no call => No MV + if(child == Genotype.Type.NO_CALL || child == Genotype.Type.UNAVAILABLE) + return 0; + //Add parents with genotypes for the evaluation + ArrayList parents = new ArrayList(); + if (!(mother == Genotype.Type.NO_CALL || mother == Genotype.Type.UNAVAILABLE)) + parents.add(mother); + if (!(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE)) + parents.add(father); + + //Both parents no calls => No MV + if (parents.isEmpty()) + return 0; + + //If at least one parent had a genotype, then count the number of ref and alt alleles that can be passed + int parentsNumRefAlleles = 0; + int parentsNumAltAlleles = 0; + + for(Genotype.Type parent : parents){ + if(parent == Genotype.Type.HOM_REF){ + parentsNumRefAlleles++; + } + else if(parent == Genotype.Type.HET){ + parentsNumRefAlleles++; + parentsNumAltAlleles++; + } + else if(parent == Genotype.Type.HOM_VAR){ + parentsNumAltAlleles++; } } - return finalGenotypes; + //Case Child is HomRef + if(child == Genotype.Type.HOM_REF){ + if(parentsNumRefAlleles == parents.size()) + return 0; + else return (parents.size()-parentsNumRefAlleles); + } + + //Case child is HomVar + if(child == Genotype.Type.HOM_VAR){ + if(parentsNumAltAlleles == parents.size()) + return 0; + else return parents.size()-parentsNumAltAlleles; + } + + //Case child is Het + if(child == Genotype.Type.HET && ((parentsNumRefAlleles > 0 && parentsNumAltAlleles > 0) || parents.size()<2)) + return 0; + + //MV + return 1; + } + + private Double getCombinationPrior(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ + + double nonMVPrior = 1.0 - 12*MENDELIAN_VIOLATION_PRIOR; + + //Child is no call => No MV + if(child == Genotype.Type.NO_CALL || child == Genotype.Type.UNAVAILABLE) + return nonMVPrior; + //Add parents with genotypes for the evaluation + ArrayList parents = new ArrayList(); + if (!(mother == Genotype.Type.NO_CALL || mother == Genotype.Type.UNAVAILABLE)) + parents.add(mother); + if (!(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE)) + parents.add(father); + + //Both parents no calls => No MV + if (parents.isEmpty()) + return nonMVPrior; + + //If at least one parent had a genotype, then count the number of ref and alt alleles that can be passed + int parentsNumRefAlleles = 0; + int parentsNumAltAlleles = 0; + + for(Genotype.Type parent : parents){ + if(parent == Genotype.Type.HOM_REF){ + parentsNumRefAlleles++; + } + else if(parent == Genotype.Type.HET){ + parentsNumRefAlleles++; + parentsNumAltAlleles++; + } + else if(parent == Genotype.Type.HOM_VAR){ + parentsNumAltAlleles++; + } + } + + //Case Child is HomRef + if(child == Genotype.Type.HOM_REF){ + if(parentsNumRefAlleles == parents.size()) + return nonMVPrior; + else return Math.pow(MENDELIAN_VIOLATION_PRIOR, parents.size()-parentsNumRefAlleles); + } + + //Case child is HomVar + if(child == Genotype.Type.HOM_VAR){ + if(parentsNumAltAlleles == parents.size()) + return nonMVPrior; + else return Math.pow(MENDELIAN_VIOLATION_PRIOR, parents.size()-parentsNumAltAlleles); + } + + //Case child is Het + if(child == Genotype.Type.HET && ((parentsNumRefAlleles > 0 && parentsNumAltAlleles > 0) || parents.size()<2)) + return nonMVPrior; + + //MV + return MENDELIAN_VIOLATION_PRIOR; + } + + private int countFamilyGenotypeDiff(Genotype.Type motherOriginal,Genotype.Type fatherOriginal,Genotype.Type childOriginal,Genotype.Type motherNew,Genotype.Type fatherNew,Genotype.Type childNew){ + int count = 0; + if(motherOriginal!=motherNew) + count++; + if(fatherOriginal!=fatherNew) + count++; + if(childOriginal!=childNew) + count++; + return count; + } + + + private boolean phaseTrioGenotypes(Allele ref, Allele alt, Genotype mother, Genotype father, Genotype child,ArrayList finalGenotypes) { + + //For now, only consider trios with complete information + //TODO: Phasing of trios with missing information + if(!(mother.isCalled() && father.isCalled() && child.isCalled())) { + finalGenotypes.add(mother); + finalGenotypes.add(father); + finalGenotypes.add(child); + return false; + } + + //Get the PL + Map motherLikelihoods = mother.getLikelihoods().getAsMap(true); + Map fatherLikelihoods = father.getLikelihoods().getAsMap(true); + Map childLikelihoods = child.getLikelihoods().getAsMap(true); + + //Prior vars + double bestConfigurationLikelihood = 0.0; + double norm = 0.0; + boolean isMV = false; + int bestConfigurationGenotypeDiffs=4; + Genotype.Type bestMotherGenotype = mother.getType(); + Genotype.Type bestFatherGenotype = father.getType(); + Genotype.Type bestChildGenotype = child.getType(); + + //Get the most likely combination + int mvCount; + double configurationLikelihood; + int configurationGenotypeDiffs; + for(Map.Entry motherGenotype : motherLikelihoods.entrySet()){ + for(Map.Entry fatherGenotype : fatherLikelihoods.entrySet()){ + for(Map.Entry childGenotype : childLikelihoods.entrySet()){ + mvCount = mvCountMatrix.get(motherGenotype.getKey()).get(fatherGenotype.getKey()).get(childGenotype.getKey()); + configurationLikelihood = mvCount>0 ? Math.pow(MENDELIAN_VIOLATION_PRIOR,mvCount)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue() : (1.0-12*MENDELIAN_VIOLATION_PRIOR)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue(); + norm += configurationLikelihood; + configurationGenotypeDiffs = countFamilyGenotypeDiff(mother.getType(),father.getType(),child.getType(),motherGenotype.getKey(),fatherGenotype.getKey(),childGenotype.getKey()); + //Keep this combination if + //It has a better likelihood + //Or it has the same likelihood but requires less changes from original genotypes + if ((configurationLikelihood > bestConfigurationLikelihood) || + (configurationLikelihood == bestConfigurationLikelihood && configurationGenotypeDiffs < bestConfigurationGenotypeDiffs)) { + bestConfigurationLikelihood = configurationLikelihood; + bestMotherGenotype = motherGenotype.getKey(); + bestFatherGenotype = fatherGenotype.getKey(); + bestChildGenotype = childGenotype.getKey(); + isMV = mvCount>0; + bestConfigurationGenotypeDiffs=configurationGenotypeDiffs; + } + } + } + } + + //Get the phased alleles for the genotype configuration + TrioPhase phasedTrioGenotypes = transmissionMatrix.get(bestMotherGenotype).get(bestFatherGenotype).get(bestChildGenotype); + + //Return the phased genotypes + phasedTrioGenotypes.getPhasedGenotypes(ref,alt,mother,father,child,MathUtils.probabilityToPhredScale(1-(bestConfigurationLikelihood / norm)),finalGenotypes); + return isMV; + } /** @@ -292,18 +576,34 @@ public class PhaseByTransmission extends RodWalker { * @return null */ @Override - public Integer map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { + public HashMap map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { + + //Local cars to avoid lookups on increment + int numTrioGenotypesCalled = 0; + int numTrioGenotypesNoCall = 0; + int numTrioGenotypesPhased = 0; + int numHet = 0 ; + int numHetHetHet = 0; + int numMVs = 0; + if (tracker != null) { VariantContext vc = tracker.getFirstValue(variantCollection.variants, context.getLocation()); Map genotypeMap = vc.getGenotypes(); - for (Trio trio : trios) { - Genotype mother = vc.getGenotype(trio.getMother()); - Genotype father = vc.getGenotype(trio.getFather()); - Genotype child = vc.getGenotype(trio.getChild()); + boolean isMV; - ArrayList trioGenotypes = phaseTrioGenotypes(vc.getReference(), vc.getAltAlleleWithHighestAlleleCount(), mother, father, child); + for (Sample sample : trios) { + Genotype mother = vc.getGenotype(sample.getMaternalID()); + Genotype father = vc.getGenotype(sample.getPaternalID()); + Genotype child = vc.getGenotype(sample.getID()); + + //Skip trios where any of the genotype is missing in the variant context + if(mother == null || father == null | child == null) + continue; + + ArrayList trioGenotypes = new ArrayList(3); + isMV = phaseTrioGenotypes(vc.getReference(), vc.getAltAlleleWithHighestAlleleCount(), mother, father, child,trioGenotypes); Genotype phasedMother = trioGenotypes.get(0); Genotype phasedFather = trioGenotypes.get(1); @@ -312,14 +612,47 @@ public class PhaseByTransmission extends RodWalker { genotypeMap.put(phasedMother.getSampleName(), phasedMother); genotypeMap.put(phasedFather.getSampleName(), phasedFather); genotypeMap.put(phasedChild.getSampleName(), phasedChild); + + //Increment metrics counters + if(phasedMother.isCalled() && phasedFather.isCalled() && phasedChild.isCalled()){ + numTrioGenotypesCalled++; + if(phasedMother.isPhased()) + numTrioGenotypesPhased++; + + if(phasedMother.isHet() || phasedFather.isHet() || phasedChild.isHet()){ + numHet++; + if(phasedMother.isHet() && phasedFather.isHet() && phasedChild.isHet()){ + numHetHetHet++; + }else if(!phasedMother.isPhased()){ + int x =9; + } + } + + if(isMV){ + numMVs++; + if(mvFile != null) + mvFile.println(String.format("%s\t%d\t%s\t%s\t%s\t%s\t%s:%s:%s:%s\t%s:%s:%s:%s\t%s:%s:%s:%s",vc.getChr(),vc.getStart(),vc.getFilters(),vc.getAttribute(VCFConstants.ALLELE_COUNT_KEY),sample.toString(),phasedMother.getAttribute(TRANSMISSION_PROBABILITY_TAG_NAME),phasedMother.getGenotypeString(),phasedMother.getAttribute(VCFConstants.DEPTH_KEY),phasedMother.getAttribute("AD"),phasedMother.getLikelihoods().toString(),phasedFather.getGenotypeString(),phasedFather.getAttribute(VCFConstants.DEPTH_KEY),phasedFather.getAttribute("AD"),phasedFather.getLikelihoods().toString(),phasedChild.getGenotypeString(),phasedChild.getAttribute(VCFConstants.DEPTH_KEY),phasedChild.getAttribute("AD"),phasedChild.getLikelihoods().toString())); + } + }else{ + numTrioGenotypesNoCall++; + } + } + VariantContext newvc = VariantContext.modifyGenotypes(vc, genotypeMap); vcfWriter.add(newvc); } - return null; + HashMap metricsCounters = new HashMap(5); + metricsCounters.put(NUM_TRIO_GENOTYPES_CALLED,numTrioGenotypesCalled); + metricsCounters.put(NUM_TRIO_GENOTYPES_NOCALL,numTrioGenotypesNoCall); + metricsCounters.put(NUM_TRIO_GENOTYPES_PHASED,numTrioGenotypesPhased); + metricsCounters.put(NUM_HET,numHet); + metricsCounters.put(NUM_HET_HET_HET,numHetHetHet); + metricsCounters.put(NUM_VIOLATIONS,numMVs); + return metricsCounters; } /** @@ -328,8 +661,15 @@ public class PhaseByTransmission extends RodWalker { * @return Initial value of reduce. */ @Override - public Integer reduceInit() { - return null; + public HashMap reduceInit() { + HashMap metricsCounters = new HashMap(5); + metricsCounters.put(NUM_TRIO_GENOTYPES_CALLED,0); + metricsCounters.put(NUM_TRIO_GENOTYPES_NOCALL,0); + metricsCounters.put(NUM_TRIO_GENOTYPES_PHASED,0); + metricsCounters.put(NUM_HET,0); + metricsCounters.put(NUM_HET_HET_HET,0); + metricsCounters.put(NUM_VIOLATIONS,0); + return metricsCounters; } /** @@ -340,7 +680,24 @@ public class PhaseByTransmission extends RodWalker { * @return accumulator with result of the map taken into account. */ @Override - public Integer reduce(Integer value, Integer sum) { - return null; + public HashMap reduce(HashMap value, HashMap sum) { + sum.put(NUM_TRIO_GENOTYPES_CALLED,value.get(NUM_TRIO_GENOTYPES_CALLED)+sum.get(NUM_TRIO_GENOTYPES_CALLED)); + sum.put(NUM_TRIO_GENOTYPES_NOCALL,value.get(NUM_TRIO_GENOTYPES_NOCALL)+sum.get(NUM_TRIO_GENOTYPES_NOCALL)); + sum.put(NUM_TRIO_GENOTYPES_PHASED,value.get(NUM_TRIO_GENOTYPES_PHASED)+sum.get(NUM_TRIO_GENOTYPES_PHASED)); + sum.put(NUM_HET,value.get(NUM_HET)+sum.get(NUM_HET)); + sum.put(NUM_HET_HET_HET,value.get(NUM_HET_HET_HET)+sum.get(NUM_HET_HET_HET)); + sum.put(NUM_VIOLATIONS,value.get(NUM_VIOLATIONS)+sum.get(NUM_VIOLATIONS)); + return sum; + } + + + @Override + public void onTraversalDone(HashMap result) { + logger.info("Number of complete trio-genotypes: " + result.get(NUM_TRIO_GENOTYPES_CALLED)); + logger.info("Number of trio-genotypes containing no call(s): " + result.get(NUM_TRIO_GENOTYPES_NOCALL)); + logger.info("Number of trio-genotypes phased: " + result.get(NUM_TRIO_GENOTYPES_PHASED)); + logger.info("Number of resulting Hom/Hom/Hom trios: " + result.get(NUM_HET)); + logger.info("Number of resulting Het/Het/Het trios: " + result.get(NUM_HET_HET_HET)); + logger.info("Number of remaining mendelian violations: " + result.get(NUM_VIOLATIONS)); } } From edea90786a4555ff665b07a3dc2f97ce7cbae24c Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Thu, 20 Oct 2011 17:04:19 +0200 Subject: [PATCH 04/44] Genotype quality is now recalculated for each of the phased Genotypes. Small problem is that we unnecessarily loose a little precision on the genotypes that do not change after assignment. --- .../walkers/phasing/PhaseByTransmission.java | 9 ++---- .../variantcontext/GenotypeLikelihoods.java | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index 12b541526..52629277f 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -266,6 +266,7 @@ public class PhaseByTransmission extends RodWalker, HashMa Map genotypeAttributes = new HashMap(); genotypeAttributes.putAll(genotype.getAttributes()); genotypeAttributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, transmissionProb); + genotype = Genotype.modifyAttributes(genotype, genotypeAttributes); boolean isPhased = true; @@ -274,7 +275,6 @@ public class PhaseByTransmission extends RodWalker, HashMa //If unphased, return original genotype for(AlleleType allele : phasedAlleles){ if(allele == AlleleType.NO_CALL){ - genotype = Genotype.modifyAttributes(genotype, genotypeAttributes); return genotype; } //Otherwise add the appropriate allele @@ -293,13 +293,8 @@ public class PhaseByTransmission extends RodWalker, HashMa alleles.add(altAllele); } } - //TODO: Recalculate GQ field for the new genotype - //Remove the GQ attribute as it needs to be recalculated for the new genotype assignment - //double[] likelihoods = genotype.getLikelihoods().getAsVector(); - //genotypeAttributes.put(VCFConstants.GENOTYPE_QUALITY_KEY,likelihoods[1]); - genotype = Genotype.modifyAttributes(genotype, genotypeAttributes); - return new Genotype(genotype.getSampleName(), alleles, genotype.getNegLog10PError(), null, genotype.getAttributes(), isPhased); + return new Genotype(genotype.getSampleName(), alleles, genotype.getLikelihoods().getLog10GQ(genotype.getType()), null, genotype.getAttributes(), isPhased); } diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java index 292bc17cd..9ed9b41cc 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java @@ -25,8 +25,10 @@ package org.broadinstitute.sting.utils.variantcontext; import org.broad.tribble.TribbleException; +import org.broadinstitute.sting.gatk.io.DirectOutputTracker; import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; +import org.jgrapht.util.MathUtil; import java.util.EnumMap; import java.util.Map; @@ -98,6 +100,7 @@ public class GenotypeLikelihoods { return likelihoodsAsString_PLs; } + //Return genotype likelihoods as an EnumMap with Genotypes as keys and likelihoods as values public EnumMap getAsMap(boolean normalizeFromLog10){ //Make sure that the log10likelihoods are set double[] likelihoods = normalizeFromLog10 ? MathUtils.normalizeFromLog10(getAsVector()) : getAsVector(); @@ -108,6 +111,32 @@ public class GenotypeLikelihoods { return likelihoodsMap; } + //Return the log10 Genotype Quality (GQ) for the given genotype + public double getLog10GQ(Genotype.Type genotype){ + + double qual = Double.NEGATIVE_INFINITY; + EnumMap likelihoods = getAsMap(false); + for(Map.Entry likelihood : likelihoods.entrySet()){ + if(likelihood.getKey() == genotype) + continue; + if(likelihood.getValue() > qual) + qual = likelihood.getValue(); + + } + + qual = likelihoods.get(genotype) - qual; + + if (qual < 0) { + // QUAL can be negative if the chosen genotype is not the most likely one individually. + // In this case, we compute the actual genotype probability and QUAL is the likelihood of it not being the chosen one + double[] normalized = MathUtils.normalizeFromLog10(getAsVector()); + double chosenGenotype = normalized[genotype.ordinal()-1]; + qual = -1.0 * Math.log10(1.0 - chosenGenotype); + } + + return qual; + } + private final static double[] parsePLsIntoLikelihoods(String likelihoodsAsString_PLs) { if ( !likelihoodsAsString_PLs.equals(VCFConstants.MISSING_VALUE_v4) ) { String[] strings = likelihoodsAsString_PLs.split(","); From 01b16abc8dcc233e29258eaa4e1d1c0415180e91 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Mon, 24 Oct 2011 10:24:41 +0200 Subject: [PATCH 05/44] Genotype quality calculation modified to handle all genotypes the same way. This is inconsistent with GQ output by the UG but is correct even for cases of poor quality genotypes. --- .../variantcontext/GenotypeLikelihoods.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java index 9ed9b41cc..b83ffa2fd 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java @@ -111,8 +111,8 @@ public class GenotypeLikelihoods { return likelihoodsMap; } - //Return the log10 Genotype Quality (GQ) for the given genotype - public double getLog10GQ(Genotype.Type genotype){ + //Return the neg log10 Genotype Quality (GQ) for the given genotype + public double getNegLog10GQ(Genotype.Type genotype){ double qual = Double.NEGATIVE_INFINITY; EnumMap likelihoods = getAsMap(false); @@ -124,15 +124,9 @@ public class GenotypeLikelihoods { } - qual = likelihoods.get(genotype) - qual; - - if (qual < 0) { - // QUAL can be negative if the chosen genotype is not the most likely one individually. - // In this case, we compute the actual genotype probability and QUAL is the likelihood of it not being the chosen one - double[] normalized = MathUtils.normalizeFromLog10(getAsVector()); - double chosenGenotype = normalized[genotype.ordinal()-1]; - qual = -1.0 * Math.log10(1.0 - chosenGenotype); - } + double[] normalized = MathUtils.normalizeFromLog10(getAsVector()); + double chosenGenotype = normalized[genotype.ordinal()-1]; + qual = -1.0 * Math.log10(1.0 - chosenGenotype); return qual; } From 7312e35c71167dee3510bf428204b3a4b75ab52f Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Mon, 24 Oct 2011 10:25:53 +0200 Subject: [PATCH 06/44] Now makes use of standard Allele and Genotype classes. This allowed quite some code cleaning. --- .../walkers/phasing/PhaseByTransmission.java | 341 +++++++----------- 1 file changed, 135 insertions(+), 206 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index 52629277f..62b7bfffa 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -56,7 +56,7 @@ public class PhaseByTransmission extends RodWalker, HashMa //Matrix of allele transmission private EnumMap>> transmissionMatrix; - //Metrics counters hashkeys + //Metrics counters hash keys private final Byte NUM_TRIO_GENOTYPES_CALLED = 0; private final Byte NUM_TRIO_GENOTYPES_NOCALL = 1; private final Byte NUM_TRIO_GENOTYPES_PHASED = 2; @@ -64,237 +64,223 @@ public class PhaseByTransmission extends RodWalker, HashMa private final Byte NUM_HET_HET_HET = 4; private final Byte NUM_VIOLATIONS = 5; - private enum AlleleType { - NO_CALL, - REF, - VAR, - UNPHASED_REF, - UNPHASED_VAR + private enum FamilyMember { + MOTHER, + FATHER, + CHILD } //Stores a trio-genotype private class TrioPhase { - private ArrayList trioAlleles = new ArrayList(6); + //Create 2 fake alleles + //The actual bases will never be used but the Genotypes created using the alleles will be. + private final Allele REF = Allele.create("A",true); + private final Allele VAR = Allele.create("A",false); + private final Allele NO_CALL = Allele.create(".",false); + private final String DUMMY_NAME = "DummySample"; - private ArrayList getAlleles(Genotype.Type genotype){ - ArrayList alleles = new ArrayList(2); + private EnumMap trioPhasedGenotypes = new EnumMap(FamilyMember.class); + + private ArrayList getAlleles(Genotype.Type genotype){ + ArrayList alleles = new ArrayList(2); if(genotype == Genotype.Type.HOM_REF){ - alleles.add(AlleleType.REF); - alleles.add(AlleleType.REF); + alleles.add(REF); + alleles.add(REF); } else if(genotype == Genotype.Type.HET){ - alleles.add(AlleleType.REF); - alleles.add(AlleleType.VAR); + alleles.add(REF); + alleles.add(VAR); } else if(genotype == Genotype.Type.HOM_VAR){ - alleles.add(AlleleType.VAR); - alleles.add(AlleleType.VAR); + alleles.add(VAR); + alleles.add(VAR); + } + else if(genotype == Genotype.Type.NO_CALL){ + alleles.add(NO_CALL); + alleles.add(NO_CALL); } else{ - alleles.add(AlleleType.NO_CALL); - alleles.add(AlleleType.NO_CALL); + return null; } return alleles; } - private ArrayList phaseSingleIndividualAlleles(Genotype.Type genotype){ - if(genotype == Genotype.Type.HET){ - ArrayList phasedAlleles = new ArrayList(2); - phasedAlleles.add(AlleleType.UNPHASED_REF); - phasedAlleles.add(AlleleType.UNPHASED_VAR); - return phasedAlleles; + //Create a new Genotype based on information from a single individual + //Homozygous genotypes will be set as phased, heterozygous won't be + private void phaseSingleIndividualAlleles(Genotype.Type genotype, FamilyMember familyMember){ + if(genotype == Genotype.Type.HOM_REF || genotype == Genotype.Type.HOM_VAR){ + trioPhasedGenotypes.put(familyMember, new Genotype(DUMMY_NAME, getAlleles(genotype), Genotype.NO_NEG_LOG_10PERROR, null, null, true)); } else - return getAlleles(genotype); + trioPhasedGenotypes.put(familyMember, new Genotype(DUMMY_NAME,getAlleles(genotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); } - private ArrayList phaseMonoParentFamilyAlleles(Genotype.Type parent, Genotype.Type child){ - ArrayList phasedAlleles = new ArrayList(4); + private void phaseMonoParentFamilyAlleles(Genotype.Type parentGenotype, Genotype.Type childGenotype, FamilyMember parent){ + //Special case for Het/Het as it is ambiguous - if(parent == Genotype.Type.HET && child == Genotype.Type.HET){ - phasedAlleles.add(AlleleType.UNPHASED_REF); - phasedAlleles.add(AlleleType.UNPHASED_VAR); - phasedAlleles.add(AlleleType.UNPHASED_REF); - phasedAlleles.add(AlleleType.UNPHASED_VAR); + if(parentGenotype == Genotype.Type.HET && childGenotype == Genotype.Type.HET){ + trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, getAlleles(parentGenotype), Genotype.NO_NEG_LOG_10PERROR, null, null, false)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(childGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); } - ArrayList parentAlleles = getAlleles(parent); - ArrayList childAlleles = getAlleles(child); + ArrayList parentAlleles = getAlleles(parentGenotype); + ArrayList childAlleles = getAlleles(childGenotype); + ArrayList parentPhasedAlleles = new ArrayList(2); + ArrayList childPhasedAlleles = new ArrayList(2); + //If there is a possible phasing between the mother and child => phase int childTransmittedAlleleIndex = childAlleles.indexOf(parentAlleles.get(0)); if(childTransmittedAlleleIndex > -1){ - phasedAlleles.add(parentAlleles.get(0)); - phasedAlleles.add(parentAlleles.get(1)); - phasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); - phasedAlleles.add(childAlleles.get(0)); + trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, parentAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); + childPhasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); + childPhasedAlleles.add(childAlleles.get(0)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME, childPhasedAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); } else if((childTransmittedAlleleIndex = childAlleles.indexOf(parentAlleles.get(1))) > -1){ - phasedAlleles.add(parentAlleles.get(1)); - phasedAlleles.add(parentAlleles.get(0)); - phasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); - phasedAlleles.add(childAlleles.get(0)); + parentPhasedAlleles.add(parentAlleles.get(1)); + parentPhasedAlleles.add(parentAlleles.get(0)); + trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, parentPhasedAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); + childPhasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); + childPhasedAlleles.add(childAlleles.get(0)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME, childPhasedAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); } + //This is a Mendelian Violation => Do not phase else{ - parentAlleles.addAll(childAlleles); - for(AlleleType allele : parentAlleles){ - if(allele == AlleleType.REF){ - phasedAlleles.add(AlleleType.UNPHASED_REF); - } - else if(allele == AlleleType.VAR){ - phasedAlleles.add(AlleleType.UNPHASED_VAR); - } - else{ - phasedAlleles.add(AlleleType.NO_CALL); - } - } + trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME,getAlleles(parentGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(childGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); } - - return phasedAlleles; } - private ArrayList phaseFamilyAlleles(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ - ArrayList phasedAlleles = new ArrayList(6); + private void phaseFamilyAlleles(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ - Set> possiblePhasedChildGenotypes = new HashSet>(); - ArrayList motherAlleles = getAlleles(mother); - ArrayList fatherAlleles = getAlleles(father); - ArrayList childAlleles = getAlleles(child); + Set> possiblePhasedChildGenotypes = new HashSet>(); + ArrayList motherAlleles = getAlleles(mother); + ArrayList fatherAlleles = getAlleles(father); + ArrayList childAlleles = getAlleles(child); //Build all possible child genotypes for the given parent's genotypes - for (AlleleType momAllele : motherAlleles) { - for (AlleleType fatherAllele : fatherAlleles) { - ArrayList possiblePhasedChildAlleles = new ArrayList(2); + for (Allele momAllele : motherAlleles) { + for (Allele fatherAllele : fatherAlleles) { + ArrayList possiblePhasedChildAlleles = new ArrayList(2); possiblePhasedChildAlleles.add(momAllele); possiblePhasedChildAlleles.add(fatherAllele); possiblePhasedChildGenotypes.add(possiblePhasedChildAlleles); } } - for (ArrayList phasedChildGenotype : possiblePhasedChildGenotypes) { - int firstAlleleIndex = phasedChildGenotype.indexOf(childAlleles.get(0)); - int secondAlleleIndex = phasedChildGenotype.lastIndexOf(childAlleles.get(1)); + for (ArrayList childPhasedAllelesAlleles : possiblePhasedChildGenotypes) { + int firstAlleleIndex = childPhasedAllelesAlleles.indexOf(childAlleles.get(0)); + int secondAlleleIndex = childPhasedAllelesAlleles.lastIndexOf(childAlleles.get(1)); + //If a possible combination has been found, create the genotypes if (firstAlleleIndex != secondAlleleIndex && firstAlleleIndex > -1 && secondAlleleIndex > -1) { - //Add mother's alleles - phasedAlleles.add(phasedChildGenotype.get(0)); - if(motherAlleles.get(0) != phasedAlleles.get(0)) - phasedAlleles.add(motherAlleles.get(0)); + //Create mother's genotype + ArrayList motherPhasedAlleles = new ArrayList(2); + motherPhasedAlleles.add(childPhasedAllelesAlleles.get(0)); + if(motherAlleles.get(0) != motherPhasedAlleles.get(0)) + motherPhasedAlleles.add(motherAlleles.get(0)); else - phasedAlleles.add(motherAlleles.get(1)); + motherPhasedAlleles.add(motherAlleles.get(1)); + trioPhasedGenotypes.put(FamilyMember.MOTHER, new Genotype(DUMMY_NAME,motherPhasedAlleles,Genotype.NO_NEG_LOG_10PERROR,null,null,true)); - //Add father's alleles - phasedAlleles.add(phasedChildGenotype.get(1)); - if(fatherAlleles.get(0) != phasedAlleles.get(2)) - phasedAlleles.add(fatherAlleles.get(0)); + //Create father's genotype + ArrayList fatherPhasedAlleles = new ArrayList(2); + fatherPhasedAlleles.add(childPhasedAllelesAlleles.get(1)); + if(fatherAlleles.get(0) != fatherPhasedAlleles.get(0)) + fatherPhasedAlleles.add(fatherAlleles.get(0)); else - phasedAlleles.add(fatherAlleles.get(1)); + fatherPhasedAlleles.add(fatherAlleles.get(1)); + trioPhasedGenotypes.put(FamilyMember.FATHER, new Genotype(DUMMY_NAME,fatherPhasedAlleles,Genotype.NO_NEG_LOG_10PERROR,null,null,true)); - //Add child's alleles - phasedAlleles.addAll(phasedChildGenotype); - return phasedAlleles; + //Create child's genotype + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,childPhasedAllelesAlleles,Genotype.NO_NEG_LOG_10PERROR,null,null,true)); + + //Once a phased combination is found; exit + return; } } //If this is reached then no phasing could be found - motherAlleles.addAll(fatherAlleles); - motherAlleles.addAll(childAlleles); - for(AlleleType allele : motherAlleles){ - if(allele == AlleleType.REF){ - phasedAlleles.add(AlleleType.UNPHASED_REF); - } - else if(allele == AlleleType.VAR){ - phasedAlleles.add(AlleleType.UNPHASED_VAR); - } - else{ - phasedAlleles.add(AlleleType.NO_CALL); - } - } - return phasedAlleles; + trioPhasedGenotypes.put(FamilyMember.MOTHER, new Genotype(DUMMY_NAME,getAlleles(mother),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); + trioPhasedGenotypes.put(FamilyMember.FATHER, new Genotype(DUMMY_NAME,getAlleles(father),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(child),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); } public TrioPhase(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ //Take care of cases where one or more family members are no call - if(child == Genotype.Type.NO_CALL){ - trioAlleles.addAll(phaseSingleIndividualAlleles(mother)); - trioAlleles.addAll(phaseSingleIndividualAlleles(father)); - trioAlleles.add(AlleleType.NO_CALL); - trioAlleles.add(AlleleType.NO_CALL); + if(child == Genotype.Type.NO_CALL || child == Genotype.Type.UNAVAILABLE){ + phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); + phaseSingleIndividualAlleles(father, FamilyMember.FATHER); + phaseSingleIndividualAlleles(child, FamilyMember.CHILD); } - else if(mother == Genotype.Type.NO_CALL){ - trioAlleles.add(AlleleType.NO_CALL); - trioAlleles.add(AlleleType.NO_CALL); - if(father == Genotype.Type.NO_CALL){ - trioAlleles.add(AlleleType.NO_CALL); - trioAlleles.add(AlleleType.NO_CALL); - trioAlleles.addAll(phaseSingleIndividualAlleles(child)); + else if(mother == Genotype.Type.NO_CALL || mother == Genotype.Type.UNAVAILABLE){ + phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); + if(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE){ + phaseSingleIndividualAlleles(father, FamilyMember.FATHER); + phaseSingleIndividualAlleles(child, FamilyMember.CHILD); } else - trioAlleles.addAll(phaseMonoParentFamilyAlleles(father, child)); + phaseMonoParentFamilyAlleles(father, child, FamilyMember.FATHER); } - else if(father == Genotype.Type.NO_CALL){ - trioAlleles.addAll(phaseMonoParentFamilyAlleles(mother, child)); - trioAlleles.add(2, AlleleType.NO_CALL); - trioAlleles.add(3, AlleleType.NO_CALL); + else if(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE){ + phaseMonoParentFamilyAlleles(mother, child, FamilyMember.MOTHER); + phaseSingleIndividualAlleles(father, FamilyMember.FATHER); } //Special case for Het/Het/Het as it is ambiguous else if(mother == Genotype.Type.HET && father == Genotype.Type.HET && child == Genotype.Type.HET){ - trioAlleles.add(AlleleType.UNPHASED_REF); - trioAlleles.add(AlleleType.UNPHASED_VAR); - trioAlleles.add(AlleleType.UNPHASED_REF); - trioAlleles.add(AlleleType.UNPHASED_VAR); - trioAlleles.add(AlleleType.UNPHASED_REF); - trioAlleles.add(AlleleType.UNPHASED_VAR); + phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); + phaseSingleIndividualAlleles(father, FamilyMember.FATHER); + phaseSingleIndividualAlleles(child, FamilyMember.CHILD); } //All family members have genotypes and at least one of them is not Het else{ - trioAlleles = phaseFamilyAlleles(mother, father, child); + phaseFamilyAlleles(mother, father, child); } } public ArrayList getPhasedGenotypes(Allele ref, Allele alt, Genotype motherGenotype, Genotype fatherGenotype, Genotype childGenotype, int transmissionProb,ArrayList phasedGenotypes){ - phasedGenotypes.add(getPhasedGenotype(ref,alt,motherGenotype,transmissionProb,trioAlleles.subList(0,2))); - phasedGenotypes.add(getPhasedGenotype(ref,alt,fatherGenotype,transmissionProb,trioAlleles.subList(2,4))); - phasedGenotypes.add(getPhasedGenotype(ref,alt,childGenotype,transmissionProb,trioAlleles.subList(4,6))); + phasedGenotypes.add(getPhasedGenotype(ref,alt,motherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.MOTHER))); + phasedGenotypes.add(getPhasedGenotype(ref,alt,fatherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.FATHER))); + phasedGenotypes.add(getPhasedGenotype(ref,alt,childGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.CHILD))); return phasedGenotypes; } - private Genotype getPhasedGenotype(Allele refAllele, Allele altAllele, Genotype genotype, int transmissionProb, List phasedAlleles){ + private Genotype getPhasedGenotype(Allele refAllele, Allele altAllele, Genotype genotype, int transmissionProb, Genotype phasedGenotype){ //Add the transmission probability Map genotypeAttributes = new HashMap(); genotypeAttributes.putAll(genotype.getAttributes()); genotypeAttributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, transmissionProb); - genotype = Genotype.modifyAttributes(genotype, genotypeAttributes); - boolean isPhased = true; + //Handle missing genotype + if(!phasedGenotype.isAvailable()) + return new Genotype(genotype.getSampleName(), null); + //Handle NoCall genotype + else if(phasedGenotype.isNoCall()) + return new Genotype(genotype.getSampleName(), phasedGenotype.getAlleles()); - List alleles = new ArrayList(2); + ArrayList phasedAlleles = new ArrayList(2); + for(Allele allele : phasedGenotype.getAlleles()){ + if(allele.isReference()) + phasedAlleles.add(refAllele); + else if(allele.isNonReference()) + phasedAlleles.add(altAllele); + //At this point there should not be any other alleles left + else + throw new UserException(String.format("BUG: Unexpected allele: %s. Please report.",allele.toString())); - //If unphased, return original genotype - for(AlleleType allele : phasedAlleles){ - if(allele == AlleleType.NO_CALL){ - return genotype; - } - //Otherwise add the appropriate allele - else if(allele == AlleleType.UNPHASED_REF){ - isPhased = false; - alleles.add(refAllele); - } - else if(allele == AlleleType.UNPHASED_VAR){ - isPhased = false; - alleles.add(altAllele); - } - else if(allele == AlleleType.REF){ - alleles.add(refAllele); - } - else if(allele == AlleleType.VAR){ - alleles.add(altAllele); - } - } + } - return new Genotype(genotype.getSampleName(), alleles, genotype.getLikelihoods().getLog10GQ(genotype.getType()), null, genotype.getAttributes(), isPhased); + //Compute the new Log10Error if the genotype is different from the original genotype + double negLog10Error; + if(genotype.getType() == phasedGenotype.getType()) + negLog10Error = genotype.getNegLog10PError(); + else + negLog10Error = genotype.getLikelihoods().getNegLog10GQ(phasedGenotype.getType()); + + return new Genotype(genotype.getSampleName(), phasedAlleles, negLog10Error, null, genotypeAttributes, phasedGenotype.isPhased()); } @@ -432,63 +418,6 @@ public class PhaseByTransmission extends RodWalker, HashMa return 1; } - private Double getCombinationPrior(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ - - double nonMVPrior = 1.0 - 12*MENDELIAN_VIOLATION_PRIOR; - - //Child is no call => No MV - if(child == Genotype.Type.NO_CALL || child == Genotype.Type.UNAVAILABLE) - return nonMVPrior; - //Add parents with genotypes for the evaluation - ArrayList parents = new ArrayList(); - if (!(mother == Genotype.Type.NO_CALL || mother == Genotype.Type.UNAVAILABLE)) - parents.add(mother); - if (!(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE)) - parents.add(father); - - //Both parents no calls => No MV - if (parents.isEmpty()) - return nonMVPrior; - - //If at least one parent had a genotype, then count the number of ref and alt alleles that can be passed - int parentsNumRefAlleles = 0; - int parentsNumAltAlleles = 0; - - for(Genotype.Type parent : parents){ - if(parent == Genotype.Type.HOM_REF){ - parentsNumRefAlleles++; - } - else if(parent == Genotype.Type.HET){ - parentsNumRefAlleles++; - parentsNumAltAlleles++; - } - else if(parent == Genotype.Type.HOM_VAR){ - parentsNumAltAlleles++; - } - } - - //Case Child is HomRef - if(child == Genotype.Type.HOM_REF){ - if(parentsNumRefAlleles == parents.size()) - return nonMVPrior; - else return Math.pow(MENDELIAN_VIOLATION_PRIOR, parents.size()-parentsNumRefAlleles); - } - - //Case child is HomVar - if(child == Genotype.Type.HOM_VAR){ - if(parentsNumAltAlleles == parents.size()) - return nonMVPrior; - else return Math.pow(MENDELIAN_VIOLATION_PRIOR, parents.size()-parentsNumAltAlleles); - } - - //Case child is Het - if(child == Genotype.Type.HET && ((parentsNumRefAlleles > 0 && parentsNumAltAlleles > 0) || parents.size()<2)) - return nonMVPrior; - - //MV - return MENDELIAN_VIOLATION_PRIOR; - } - private int countFamilyGenotypeDiff(Genotype.Type motherOriginal,Genotype.Type fatherOriginal,Genotype.Type childOriginal,Genotype.Type motherNew,Genotype.Type fatherNew,Genotype.Type childNew){ int count = 0; if(motherOriginal!=motherNew) @@ -534,7 +463,7 @@ public class PhaseByTransmission extends RodWalker, HashMa for(Map.Entry fatherGenotype : fatherLikelihoods.entrySet()){ for(Map.Entry childGenotype : childLikelihoods.entrySet()){ mvCount = mvCountMatrix.get(motherGenotype.getKey()).get(fatherGenotype.getKey()).get(childGenotype.getKey()); - configurationLikelihood = mvCount>0 ? Math.pow(MENDELIAN_VIOLATION_PRIOR,mvCount)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue() : (1.0-12*MENDELIAN_VIOLATION_PRIOR)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue(); + configurationLikelihood = mvCount>0 ? Math.pow(MENDELIAN_VIOLATION_PRIOR,mvCount)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue() : (1.0-11*MENDELIAN_VIOLATION_PRIOR)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue(); norm += configurationLikelihood; configurationGenotypeDiffs = countFamilyGenotypeDiff(mother.getType(),father.getType(),child.getType(),motherGenotype.getKey(),fatherGenotype.getKey(),childGenotype.getKey()); //Keep this combination if From 38ebf3141a13ebafcc43b69c3364e0066ba17ea9 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Mon, 24 Oct 2011 12:30:04 +0200 Subject: [PATCH 07/44] - Now supports parent/child pairs - Sites with missing genotypes in pairs/trios are handled as follows: -- Missing child -> Homozygous parents are phased, no transmission probability is emitted -- Two individuals missing -> Phase if homozygous, no transmission probability is emitted -- One parent missing -> Phased / transmission probability emitted - Mutation prior set as argument --- .../walkers/phasing/PhaseByTransmission.java | 134 ++++++++++-------- 1 file changed, 77 insertions(+), 57 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index 62b7bfffa..d128ff40e 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -40,13 +40,16 @@ public class PhaseByTransmission extends RodWalker, HashMa @Argument(shortName = "mvf",required = false,fullName = "MendelianViolationsFile", doc="File to output the mendelian violation details.") private PrintStream mvFile = null; + @Argument(shortName = "prior",required = false,fullName = "DeNovoPrior", doc="Prior for de novo mutations. Default: 1e-8") + private double deNovoPrior=1e-8; + @Output protected VCFWriter vcfWriter = null; private final String TRANSMISSION_PROBABILITY_TAG_NAME = "TP"; private final String SOURCE_NAME = "PhaseByTransmission"; - private final Double MENDELIAN_VIOLATION_PRIOR = 1e-8; + public final double NO_TRANSMISSION_PROB = -1.0; private ArrayList trios = new ArrayList(); @@ -240,26 +243,26 @@ public class PhaseByTransmission extends RodWalker, HashMa } } - public ArrayList getPhasedGenotypes(Allele ref, Allele alt, Genotype motherGenotype, Genotype fatherGenotype, Genotype childGenotype, int transmissionProb,ArrayList phasedGenotypes){ + public ArrayList getPhasedGenotypes(Allele ref, Allele alt, Genotype motherGenotype, Genotype fatherGenotype, Genotype childGenotype, double transmissionProb,ArrayList phasedGenotypes){ phasedGenotypes.add(getPhasedGenotype(ref,alt,motherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.MOTHER))); phasedGenotypes.add(getPhasedGenotype(ref,alt,fatherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.FATHER))); phasedGenotypes.add(getPhasedGenotype(ref,alt,childGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.CHILD))); return phasedGenotypes; } - private Genotype getPhasedGenotype(Allele refAllele, Allele altAllele, Genotype genotype, int transmissionProb, Genotype phasedGenotype){ + private Genotype getPhasedGenotype(Allele refAllele, Allele altAllele, Genotype genotype, double transmissionProb, Genotype phasedGenotype){ + + //Handle null, missing and unavailable genotypes + //Note that only cases where a null/missing/unavailable genotype was passed in the first place can lead to a null/missing/unavailable + //genotype so it is safe to return the original genotype in this case. + if(genotype == null || !phasedGenotype.isAvailable() || phasedGenotype.isNoCall()) + return genotype; //Add the transmission probability Map genotypeAttributes = new HashMap(); genotypeAttributes.putAll(genotype.getAttributes()); - genotypeAttributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, transmissionProb); - - //Handle missing genotype - if(!phasedGenotype.isAvailable()) - return new Genotype(genotype.getSampleName(), null); - //Handle NoCall genotype - else if(phasedGenotype.isNoCall()) - return new Genotype(genotype.getSampleName(), phasedGenotype.getAlleles()); + if(transmissionProb>NO_TRANSMISSION_PROB) + genotypeAttributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, MathUtils.probabilityToPhredScale(1-(transmissionProb))); ArrayList phasedAlleles = new ArrayList(2); for(Allele allele : phasedGenotype.getAlleles()){ @@ -324,8 +327,8 @@ public class PhaseByTransmission extends RodWalker, HashMa ArrayList parents; for(String familyID : families.keySet()){ family = families.get(familyID); - if(family.size()!=3){ - logger.info(String.format("Caution: Family %s has %d members; At the moment Phase By Transmission only supports trios. Family skipped.",familyID,family.size())); + if(family.size()<2 || family.size()>3){ + logger.info(String.format("Caution: Family %s has %d members; At the moment Phase By Transmission only supports trios and parent/child pairs. Family skipped.",familyID,family.size())); } else{ for(Sample familyMember : family){ @@ -334,7 +337,7 @@ public class PhaseByTransmission extends RodWalker, HashMa if(family.containsAll(parents)) this.trios.add(familyMember); else - logger.info(String.format("Caution: Family %s is not a trio; At the moment Phase By Transmission only supports trios. Family skipped.",familyID)); + logger.info(String.format("Caution: Family %s skipped as it is not a trio nor a parent/child pair; At the moment Phase By Transmission only supports trios and parent/child pairs. Family skipped.",familyID)); break; } } @@ -429,64 +432,84 @@ public class PhaseByTransmission extends RodWalker, HashMa return count; } + private EnumMap getLikelihoodsAsMapSafeNull(Genotype genotype){ + if(genotype == null || !genotype.isAvailable()){ + EnumMap likelihoods = new EnumMap(Genotype.Type.class); + likelihoods.put(Genotype.Type.UNAVAILABLE,1.0); + return likelihoods; + } + else if(genotype.isNoCall()){ + EnumMap likelihoods = new EnumMap(Genotype.Type.class); + likelihoods.put(Genotype.Type.NO_CALL,1.0); + return likelihoods; + } + return genotype.getLikelihoods().getAsMap(true); + } + + private Genotype.Type getTypeSafeNull(Genotype genotype){ + if(genotype == null) + return Genotype.Type.UNAVAILABLE; + return genotype.getType(); + } + private boolean phaseTrioGenotypes(Allele ref, Allele alt, Genotype mother, Genotype father, Genotype child,ArrayList finalGenotypes) { - //For now, only consider trios with complete information - //TODO: Phasing of trios with missing information - if(!(mother.isCalled() && father.isCalled() && child.isCalled())) { - finalGenotypes.add(mother); - finalGenotypes.add(father); - finalGenotypes.add(child); - return false; - } - //Get the PL - Map motherLikelihoods = mother.getLikelihoods().getAsMap(true); - Map fatherLikelihoods = father.getLikelihoods().getAsMap(true); - Map childLikelihoods = child.getLikelihoods().getAsMap(true); + Map motherLikelihoods = getLikelihoodsAsMapSafeNull(mother); + Map fatherLikelihoods = getLikelihoodsAsMapSafeNull(father); + Map childLikelihoods = getLikelihoodsAsMapSafeNull(child); //Prior vars double bestConfigurationLikelihood = 0.0; double norm = 0.0; boolean isMV = false; int bestConfigurationGenotypeDiffs=4; - Genotype.Type bestMotherGenotype = mother.getType(); - Genotype.Type bestFatherGenotype = father.getType(); - Genotype.Type bestChildGenotype = child.getType(); + Genotype.Type bestMotherGenotype = getTypeSafeNull(mother); + Genotype.Type bestFatherGenotype = getTypeSafeNull(father); + Genotype.Type bestChildGenotype = getTypeSafeNull(child); //Get the most likely combination - int mvCount; - double configurationLikelihood; - int configurationGenotypeDiffs; - for(Map.Entry motherGenotype : motherLikelihoods.entrySet()){ - for(Map.Entry fatherGenotype : fatherLikelihoods.entrySet()){ - for(Map.Entry childGenotype : childLikelihoods.entrySet()){ - mvCount = mvCountMatrix.get(motherGenotype.getKey()).get(fatherGenotype.getKey()).get(childGenotype.getKey()); - configurationLikelihood = mvCount>0 ? Math.pow(MENDELIAN_VIOLATION_PRIOR,mvCount)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue() : (1.0-11*MENDELIAN_VIOLATION_PRIOR)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue(); - norm += configurationLikelihood; - configurationGenotypeDiffs = countFamilyGenotypeDiff(mother.getType(),father.getType(),child.getType(),motherGenotype.getKey(),fatherGenotype.getKey(),childGenotype.getKey()); - //Keep this combination if - //It has a better likelihood - //Or it has the same likelihood but requires less changes from original genotypes - if ((configurationLikelihood > bestConfigurationLikelihood) || - (configurationLikelihood == bestConfigurationLikelihood && configurationGenotypeDiffs < bestConfigurationGenotypeDiffs)) { - bestConfigurationLikelihood = configurationLikelihood; - bestMotherGenotype = motherGenotype.getKey(); - bestFatherGenotype = fatherGenotype.getKey(); - bestChildGenotype = childGenotype.getKey(); - isMV = mvCount>0; - bestConfigurationGenotypeDiffs=configurationGenotypeDiffs; + //Only check for most likely combination if at least a parent and the child have genotypes + if(childLikelihoods.size()>2 && (motherLikelihoods.size() + fatherLikelihoods.size())>3){ + int mvCount; + double configurationLikelihood; + int configurationGenotypeDiffs; + for(Map.Entry motherGenotype : motherLikelihoods.entrySet()){ + for(Map.Entry fatherGenotype : fatherLikelihoods.entrySet()){ + for(Map.Entry childGenotype : childLikelihoods.entrySet()){ + mvCount = mvCountMatrix.get(motherGenotype.getKey()).get(fatherGenotype.getKey()).get(childGenotype.getKey()); + configurationLikelihood = mvCount>0 ? Math.pow(deNovoPrior,mvCount)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue() : (1.0-11*deNovoPrior)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue(); + norm += configurationLikelihood; + configurationGenotypeDiffs = countFamilyGenotypeDiff(mother.getType(),father.getType(),child.getType(),motherGenotype.getKey(),fatherGenotype.getKey(),childGenotype.getKey()); + //Keep this combination if + //It has a better likelihood + //Or it has the same likelihood but requires less changes from original genotypes + if ((configurationLikelihood > bestConfigurationLikelihood) || + (configurationLikelihood == bestConfigurationLikelihood && configurationGenotypeDiffs < bestConfigurationGenotypeDiffs)) { + bestConfigurationLikelihood = configurationLikelihood; + bestMotherGenotype = motherGenotype.getKey(); + bestFatherGenotype = fatherGenotype.getKey(); + bestChildGenotype = childGenotype.getKey(); + isMV = mvCount>0; + bestConfigurationGenotypeDiffs=configurationGenotypeDiffs; + } } } } + + //normalize the best configuration probability + bestConfigurationLikelihood = bestConfigurationLikelihood / norm; + } + else{ + bestConfigurationLikelihood = NO_TRANSMISSION_PROB; } //Get the phased alleles for the genotype configuration TrioPhase phasedTrioGenotypes = transmissionMatrix.get(bestMotherGenotype).get(bestFatherGenotype).get(bestChildGenotype); //Return the phased genotypes - phasedTrioGenotypes.getPhasedGenotypes(ref,alt,mother,father,child,MathUtils.probabilityToPhredScale(1-(bestConfigurationLikelihood / norm)),finalGenotypes); + phasedTrioGenotypes.getPhasedGenotypes(ref,alt,mother,father,child,bestConfigurationLikelihood,finalGenotypes); return isMV; } @@ -522,8 +545,8 @@ public class PhaseByTransmission extends RodWalker, HashMa Genotype father = vc.getGenotype(sample.getPaternalID()); Genotype child = vc.getGenotype(sample.getID()); - //Skip trios where any of the genotype is missing in the variant context - if(mother == null || father == null | child == null) + //Keep only trios and parent/child pairs + if(mother == null && father == null || child == null) continue; ArrayList trioGenotypes = new ArrayList(3); @@ -545,11 +568,8 @@ public class PhaseByTransmission extends RodWalker, HashMa if(phasedMother.isHet() || phasedFather.isHet() || phasedChild.isHet()){ numHet++; - if(phasedMother.isHet() && phasedFather.isHet() && phasedChild.isHet()){ + if(phasedMother.isHet() && phasedFather.isHet() && phasedChild.isHet()) numHetHetHet++; - }else if(!phasedMother.isPhased()){ - int x =9; - } } if(isMV){ From 62477a08104c3a158ccbb547ab5f347625676809 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Mon, 24 Oct 2011 13:45:21 +0200 Subject: [PATCH 08/44] Added documentation and comments --- .../walkers/phasing/PhaseByTransmission.java | 127 ++++++++++++++---- 1 file changed, 103 insertions(+), 24 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index d128ff40e..c4c6b69b7 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -22,15 +22,57 @@ import java.io.PrintStream; import java.util.*; /** - * Phases a trio VCF (child phased by transmission, implied phase carried over to parents). Given genotypes for a trio, - * this walker modifies the genotypes (if necessary) to reflect the most likely configuration given the genotype - * likelihoods and inheritance constraints, phases child by transmission and carries over implied phase to the parents - * (their alleles in their genotypes are ordered as transmitted|untransmitted). Computes probability that the - * determined phase is correct given that the genotype configuration is correct (useful if you want to use this to - * compare phasing accuracy, but want to break that comparison down by phasing confidence in the truth set). Optionally - * filters out sites where the phasing is indeterminate (site has no-calls), ambiguous (everyone is heterozygous), or - * the genotypes exhibit a Mendelian violation. This walker assumes there are only three samples in the VCF file to - * begin. + * Computes the most likely genotype combination and phases trios and parent/child pairs + * + *

+ * PhaseByTransmission is a GATK tool that 1) computes the most likely genotype combination and phases trios and parent/child pairs given their genotype likelihoods and a mutation prior and 2) phases + * all sites were parent/child transmission can be inferred unambiguously. It reports the genotype combination (and hence phasing) probability. + * Ambiguous sites are: + *

    + *
  • Sites where all individuals are heterozygous
  • + *
  • Sites where there is a Mendelian violation
  • + *
+ * Missing genotypes are handled as follows: + *
    + *
  • In parent/child pairs: If an individual genotype is missing at one site, the other one is phased if it is homozygous. No phasing probability is emitted.
  • + *
  • In trios: If the child is missing, parents are treated as separate individuals and phased if homozygous. No phasing probability is emitted.
  • + *
  • In trios: If one of the parents is missing, it is handled like a parent/child pair. Phasing is done unless both the parent and child are heterozygous and a phasing probabilitt is emitted.
  • + *
  • In trios: If two individuals are missing, the remaining individual is phased if it is homozygous. No phasing probability is emitted.
  • + *
+ * + *

Input

+ *

+ *

    + *
  • A VCF variant set containing trio(s) and/or parent/child pair(s).
  • + *
  • A PED pedigree file containing the description of the individuals relationships.
  • + *
+ *

+ * + *

Options

+ *

+ *

    + *
  • MendelianViolationsFile: An optional argument for reporting. If a file is specified, all sites that remain in mendelian violation after being assigned the most likely genotype + * combination will be reported there. Information reported: chromosome, position, filter, allele count in VCF, family, transmission probability, + * and each individual genotype, depth, allelic depth and likelihoods.
  • + *
  • DeNovoPrior: Mutation prio; default is 1e-8
  • + *
+ *

+ * + *

Output

+ *

+ * An VCF with genotypes recalibrated as most likely under the familial constraint and phased by descent where non ambiguous.. + *

+ * + *

Examples

+ *
+ * java -Xmx2g -jar GenomeAnalysisTK.jar \
+ *   -R ref.fasta \
+ *   -T PhaseByTransmission \
+ *   -V input.vcf \
+ *   -ped input.ped \
+ *   -o output.vcf
+ * 
+ * */ public class PhaseByTransmission extends RodWalker, HashMap> { @@ -73,7 +115,8 @@ public class PhaseByTransmission extends RodWalker, HashMa CHILD } - //Stores a trio-genotype + //Stores a conceptual trio or parent/child pair genotype combination along with its phasing. + //This combination can then be "applied" to a given trio or pair using the getPhasedGenotypes method. private class TrioPhase { //Create 2 fake alleles @@ -119,7 +162,8 @@ public class PhaseByTransmission extends RodWalker, HashMa trioPhasedGenotypes.put(familyMember, new Genotype(DUMMY_NAME,getAlleles(genotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); } - private void phaseMonoParentFamilyAlleles(Genotype.Type parentGenotype, Genotype.Type childGenotype, FamilyMember parent){ + //Find the phase for a parent/child pair + private void phasePairAlleles(Genotype.Type parentGenotype, Genotype.Type childGenotype, FamilyMember parent){ //Special case for Het/Het as it is ambiguous if(parentGenotype == Genotype.Type.HET && childGenotype == Genotype.Type.HET){ @@ -155,6 +199,7 @@ public class PhaseByTransmission extends RodWalker, HashMa } } + //Phases a family by transmission private void phaseFamilyAlleles(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ Set> possiblePhasedChildGenotypes = new HashSet>(); @@ -209,7 +254,10 @@ public class PhaseByTransmission extends RodWalker, HashMa trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(child),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); } - + /* Constructor: Creates a conceptual trio genotype combination from the given genotypes. + If one or more genotypes are set as NO_CALL or UNAVAILABLE, it will phase them like a pair + or single individual. + */ public TrioPhase(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ //Take care of cases where one or more family members are no call @@ -225,10 +273,10 @@ public class PhaseByTransmission extends RodWalker, HashMa phaseSingleIndividualAlleles(child, FamilyMember.CHILD); } else - phaseMonoParentFamilyAlleles(father, child, FamilyMember.FATHER); + phasePairAlleles(father, child, FamilyMember.FATHER); } else if(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE){ - phaseMonoParentFamilyAlleles(mother, child, FamilyMember.MOTHER); + phasePairAlleles(mother, child, FamilyMember.MOTHER); phaseSingleIndividualAlleles(father, FamilyMember.FATHER); } //Special case for Het/Het/Het as it is ambiguous @@ -243,11 +291,20 @@ public class PhaseByTransmission extends RodWalker, HashMa } } - public ArrayList getPhasedGenotypes(Allele ref, Allele alt, Genotype motherGenotype, Genotype fatherGenotype, Genotype childGenotype, double transmissionProb,ArrayList phasedGenotypes){ + /** + * Applies the trio genotype combination to the given trio. + * @param ref: Reference allele + * @param alt: Alternate allele + * @param motherGenotype: Genotype of the mother to phase using this trio genotype combination + * @param fatherGenotype: Genotype of the father to phase using this trio genotype combination + * @param childGenotype: Genotype of the child to phase using this trio genotype combination + * @param transmissionProb: Probability for this trio genotype combination to be correct (pass NO_TRANSMISSION_PROB if unavailable) + * @param phasedGenotypes: An ArrayList to which the newly phased genotypes are added in the following order: Mother, Father, Child + */ + public void getPhasedGenotypes(Allele ref, Allele alt, Genotype motherGenotype, Genotype fatherGenotype, Genotype childGenotype, double transmissionProb,ArrayList phasedGenotypes){ phasedGenotypes.add(getPhasedGenotype(ref,alt,motherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.MOTHER))); phasedGenotypes.add(getPhasedGenotype(ref,alt,fatherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.FATHER))); phasedGenotypes.add(getPhasedGenotype(ref,alt,childGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.CHILD))); - return phasedGenotypes; } private Genotype getPhasedGenotype(Allele refAllele, Allele altAllele, Genotype genotype, double transmissionProb, Genotype phasedGenotype){ @@ -290,7 +347,7 @@ public class PhaseByTransmission extends RodWalker, HashMa } /** - * Parse the familial relationship specification, and initialize VCF writer + * Parse the familial relationship specification, build the transmission matrices and initialize VCF writer */ public void initialize() { ArrayList rodNames = new ArrayList(); @@ -306,7 +363,7 @@ public class PhaseByTransmission extends RodWalker, HashMa Set headerLines = new HashSet(); headerLines.addAll(VCFUtils.getHeaderFields(this.getToolkit())); - headerLines.add(new VCFFormatHeaderLine(TRANSMISSION_PROBABILITY_TAG_NAME, 1, VCFHeaderLineType.Integer, "Phred score of the phase given that the genotypes are correct")); + headerLines.add(new VCFFormatHeaderLine(TRANSMISSION_PROBABILITY_TAG_NAME, 1, VCFHeaderLineType.Integer, "Phred score of the genotype combination and phase given that the genotypes are correct")); headerLines.add(new VCFHeaderLine("source", SOURCE_NAME)); vcfWriter.writeHeader(new VCFHeader(headerLines, vcfSamples)); @@ -318,7 +375,7 @@ public class PhaseByTransmission extends RodWalker, HashMa } /** - * Select Trios only + * Select trios and parent/child pairs only */ private void setTrios(){ @@ -349,6 +406,7 @@ public class PhaseByTransmission extends RodWalker, HashMa } + //Create the transmission matrices private void buildMatrices(){ mvCountMatrix = new EnumMap>>(Genotype.Type.class); transmissionMatrix = new EnumMap>>(Genotype.Type.class); @@ -366,6 +424,9 @@ public class PhaseByTransmission extends RodWalker, HashMa } } + //Returns the number of Mendelian Violations for a given genotype combination. + //If one of the parents genotype is missing, it will consider it as a parent/child pair + //If the child genotype or both parents genotypes are missing, 0 is returned. private int getCombinationMVCount(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ //Child is no call => No MV @@ -421,6 +482,7 @@ public class PhaseByTransmission extends RodWalker, HashMa return 1; } + //Given two trio genotypes combinations, returns the number of different genotypes between the two combinations. private int countFamilyGenotypeDiff(Genotype.Type motherOriginal,Genotype.Type fatherOriginal,Genotype.Type childOriginal,Genotype.Type motherNew,Genotype.Type fatherNew,Genotype.Type childNew){ int count = 0; if(motherOriginal!=motherNew) @@ -432,6 +494,8 @@ public class PhaseByTransmission extends RodWalker, HashMa return count; } + //Get a Map of genotype likelihoods. If the genotype is NO_CALL or UNAVAILABLE, the Map will contain a single + //NO_CALL resp. UNAVAILABLE element with a likelihood of 1.0 private EnumMap getLikelihoodsAsMapSafeNull(Genotype genotype){ if(genotype == null || !genotype.isAvailable()){ EnumMap likelihoods = new EnumMap(Genotype.Type.class); @@ -446,6 +510,7 @@ public class PhaseByTransmission extends RodWalker, HashMa return genotype.getLikelihoods().getAsMap(true); } + //Returns the Genotype.Type; returns UNVAILABLE if given null private Genotype.Type getTypeSafeNull(Genotype genotype){ if(genotype == null) return Genotype.Type.UNAVAILABLE; @@ -453,6 +518,16 @@ public class PhaseByTransmission extends RodWalker, HashMa } + /** + * Phases the genotypes of the given trio. If one of the parents is null, it is considered a parent/child pair. + * @param ref: Reference allele + * @param alt: Alternative allele + * @param mother: Mother's genotype + * @param father: Father's genotype + * @param child: Child's genotype + * @param finalGenotypes: An ArrayList that will be added the genotypes phased by transmission in the following order: Mother, Father, Child + * @return + */ private boolean phaseTrioGenotypes(Allele ref, Allele alt, Genotype mother, Genotype father, Genotype child,ArrayList finalGenotypes) { //Get the PL @@ -600,9 +675,9 @@ public class PhaseByTransmission extends RodWalker, HashMa } /** - * Provide an initial value for reduce computations. + * Initializes the reporting counters. * - * @return Initial value of reduce. + * @return All counters initialized to 0 */ @Override public HashMap reduceInit() { @@ -617,10 +692,10 @@ public class PhaseByTransmission extends RodWalker, HashMa } /** - * Reduces a single map with the accumulator provided as the ReduceType. + * Adds the value of the site phased to the reporting counters. * - * @param value result of the map. - * @param sum accumulator for the reduce. + * @param value Site values + * @param sum accumulator for the reporting counters * @return accumulator with result of the map taken into account. */ @Override @@ -635,6 +710,10 @@ public class PhaseByTransmission extends RodWalker, HashMa } + /** + * Reports statistics on the phasing by transmission process. + * @param result Accumulator with all counters. + */ @Override public void onTraversalDone(HashMap result) { logger.info("Number of complete trio-genotypes: " + result.get(NUM_TRIO_GENOTYPES_CALLED)); From 62cff266d4e20aa2a48f88c47f282d9a6bc25287 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 26 Oct 2011 14:40:04 +0200 Subject: [PATCH 09/44] GQ calculation corrected for most likely genotype --- .../utils/variantcontext/GenotypeLikelihoods.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java index b83ffa2fd..47c93bb1b 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java @@ -124,10 +124,15 @@ public class GenotypeLikelihoods { } - double[] normalized = MathUtils.normalizeFromLog10(getAsVector()); - double chosenGenotype = normalized[genotype.ordinal()-1]; - qual = -1.0 * Math.log10(1.0 - chosenGenotype); + //Quality of the most likely genotype = likelihood(most likely) - likelihood (2nd best) + qual = likelihoods.get(genotype) - qual; + //Quality of other genotypes 1-P(G) + if (qual < 0) { + double[] normalized = MathUtils.normalizeFromLog10(getAsVector()); + double chosenGenotype = normalized[genotype.ordinal()-1]; + qual = -1.0 * Math.log10(1.0 - chosenGenotype); + } return qual; } From 81b163ff4d6a8203cb4cb155a92a751ee1b2349d Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 26 Oct 2011 14:49:12 +0200 Subject: [PATCH 10/44] Indentation --- .../walkers/phasing/PhaseByTransmission.java | 370 +++++++++--------- 1 file changed, 185 insertions(+), 185 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index c4c6b69b7..956cf7c89 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -129,220 +129,220 @@ public class PhaseByTransmission extends RodWalker, HashMa private EnumMap trioPhasedGenotypes = new EnumMap(FamilyMember.class); private ArrayList getAlleles(Genotype.Type genotype){ - ArrayList alleles = new ArrayList(2); - if(genotype == Genotype.Type.HOM_REF){ - alleles.add(REF); - alleles.add(REF); - } - else if(genotype == Genotype.Type.HET){ - alleles.add(REF); - alleles.add(VAR); - } - else if(genotype == Genotype.Type.HOM_VAR){ - alleles.add(VAR); - alleles.add(VAR); - } - else if(genotype == Genotype.Type.NO_CALL){ - alleles.add(NO_CALL); - alleles.add(NO_CALL); - } - else{ - return null; - } - return alleles; - } - - //Create a new Genotype based on information from a single individual - //Homozygous genotypes will be set as phased, heterozygous won't be - private void phaseSingleIndividualAlleles(Genotype.Type genotype, FamilyMember familyMember){ - if(genotype == Genotype.Type.HOM_REF || genotype == Genotype.Type.HOM_VAR){ - trioPhasedGenotypes.put(familyMember, new Genotype(DUMMY_NAME, getAlleles(genotype), Genotype.NO_NEG_LOG_10PERROR, null, null, true)); - } - else - trioPhasedGenotypes.put(familyMember, new Genotype(DUMMY_NAME,getAlleles(genotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); - } - - //Find the phase for a parent/child pair - private void phasePairAlleles(Genotype.Type parentGenotype, Genotype.Type childGenotype, FamilyMember parent){ - - //Special case for Het/Het as it is ambiguous - if(parentGenotype == Genotype.Type.HET && childGenotype == Genotype.Type.HET){ - trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, getAlleles(parentGenotype), Genotype.NO_NEG_LOG_10PERROR, null, null, false)); - trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(childGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); + ArrayList alleles = new ArrayList(2); + if(genotype == Genotype.Type.HOM_REF){ + alleles.add(REF); + alleles.add(REF); + } + else if(genotype == Genotype.Type.HET){ + alleles.add(REF); + alleles.add(VAR); + } + else if(genotype == Genotype.Type.HOM_VAR){ + alleles.add(VAR); + alleles.add(VAR); + } + else if(genotype == Genotype.Type.NO_CALL){ + alleles.add(NO_CALL); + alleles.add(NO_CALL); + } + else{ + return null; + } + return alleles; } - ArrayList parentAlleles = getAlleles(parentGenotype); - ArrayList childAlleles = getAlleles(childGenotype); - ArrayList parentPhasedAlleles = new ArrayList(2); - ArrayList childPhasedAlleles = new ArrayList(2); - - //If there is a possible phasing between the mother and child => phase - int childTransmittedAlleleIndex = childAlleles.indexOf(parentAlleles.get(0)); - if(childTransmittedAlleleIndex > -1){ - trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, parentAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); - childPhasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); - childPhasedAlleles.add(childAlleles.get(0)); - trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME, childPhasedAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); + //Create a new Genotype based on information from a single individual + //Homozygous genotypes will be set as phased, heterozygous won't be + private void phaseSingleIndividualAlleles(Genotype.Type genotype, FamilyMember familyMember){ + if(genotype == Genotype.Type.HOM_REF || genotype == Genotype.Type.HOM_VAR){ + trioPhasedGenotypes.put(familyMember, new Genotype(DUMMY_NAME, getAlleles(genotype), Genotype.NO_NEG_LOG_10PERROR, null, null, true)); + } + else + trioPhasedGenotypes.put(familyMember, new Genotype(DUMMY_NAME,getAlleles(genotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); } - else if((childTransmittedAlleleIndex = childAlleles.indexOf(parentAlleles.get(1))) > -1){ - parentPhasedAlleles.add(parentAlleles.get(1)); - parentPhasedAlleles.add(parentAlleles.get(0)); - trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, parentPhasedAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); - childPhasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); - childPhasedAlleles.add(childAlleles.get(0)); - trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME, childPhasedAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); - } - //This is a Mendelian Violation => Do not phase - else{ - trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME,getAlleles(parentGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); - trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(childGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); - } - } - //Phases a family by transmission - private void phaseFamilyAlleles(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ + //Find the phase for a parent/child pair + private void phasePairAlleles(Genotype.Type parentGenotype, Genotype.Type childGenotype, FamilyMember parent){ - Set> possiblePhasedChildGenotypes = new HashSet>(); - ArrayList motherAlleles = getAlleles(mother); - ArrayList fatherAlleles = getAlleles(father); - ArrayList childAlleles = getAlleles(child); + //Special case for Het/Het as it is ambiguous + if(parentGenotype == Genotype.Type.HET && childGenotype == Genotype.Type.HET){ + trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, getAlleles(parentGenotype), Genotype.NO_NEG_LOG_10PERROR, null, null, false)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(childGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); + } - //Build all possible child genotypes for the given parent's genotypes - for (Allele momAllele : motherAlleles) { - for (Allele fatherAllele : fatherAlleles) { - ArrayList possiblePhasedChildAlleles = new ArrayList(2); - possiblePhasedChildAlleles.add(momAllele); - possiblePhasedChildAlleles.add(fatherAllele); - possiblePhasedChildGenotypes.add(possiblePhasedChildAlleles); + ArrayList parentAlleles = getAlleles(parentGenotype); + ArrayList childAlleles = getAlleles(childGenotype); + ArrayList parentPhasedAlleles = new ArrayList(2); + ArrayList childPhasedAlleles = new ArrayList(2); + + //If there is a possible phasing between the mother and child => phase + int childTransmittedAlleleIndex = childAlleles.indexOf(parentAlleles.get(0)); + if(childTransmittedAlleleIndex > -1){ + trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, parentAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); + childPhasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); + childPhasedAlleles.add(childAlleles.get(0)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME, childPhasedAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); + } + else if((childTransmittedAlleleIndex = childAlleles.indexOf(parentAlleles.get(1))) > -1){ + parentPhasedAlleles.add(parentAlleles.get(1)); + parentPhasedAlleles.add(parentAlleles.get(0)); + trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, parentPhasedAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); + childPhasedAlleles.add(childAlleles.remove(childTransmittedAlleleIndex)); + childPhasedAlleles.add(childAlleles.get(0)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME, childPhasedAlleles, Genotype.NO_NEG_LOG_10PERROR, null, null, true)); + } + //This is a Mendelian Violation => Do not phase + else{ + trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME,getAlleles(parentGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(childGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); } } - for (ArrayList childPhasedAllelesAlleles : possiblePhasedChildGenotypes) { - int firstAlleleIndex = childPhasedAllelesAlleles.indexOf(childAlleles.get(0)); - int secondAlleleIndex = childPhasedAllelesAlleles.lastIndexOf(childAlleles.get(1)); - //If a possible combination has been found, create the genotypes - if (firstAlleleIndex != secondAlleleIndex && firstAlleleIndex > -1 && secondAlleleIndex > -1) { - //Create mother's genotype - ArrayList motherPhasedAlleles = new ArrayList(2); - motherPhasedAlleles.add(childPhasedAllelesAlleles.get(0)); - if(motherAlleles.get(0) != motherPhasedAlleles.get(0)) - motherPhasedAlleles.add(motherAlleles.get(0)); - else - motherPhasedAlleles.add(motherAlleles.get(1)); - trioPhasedGenotypes.put(FamilyMember.MOTHER, new Genotype(DUMMY_NAME,motherPhasedAlleles,Genotype.NO_NEG_LOG_10PERROR,null,null,true)); + //Phases a family by transmission + private void phaseFamilyAlleles(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ - //Create father's genotype - ArrayList fatherPhasedAlleles = new ArrayList(2); - fatherPhasedAlleles.add(childPhasedAllelesAlleles.get(1)); - if(fatherAlleles.get(0) != fatherPhasedAlleles.get(0)) - fatherPhasedAlleles.add(fatherAlleles.get(0)); - else - fatherPhasedAlleles.add(fatherAlleles.get(1)); - trioPhasedGenotypes.put(FamilyMember.FATHER, new Genotype(DUMMY_NAME,fatherPhasedAlleles,Genotype.NO_NEG_LOG_10PERROR,null,null,true)); + Set> possiblePhasedChildGenotypes = new HashSet>(); + ArrayList motherAlleles = getAlleles(mother); + ArrayList fatherAlleles = getAlleles(father); + ArrayList childAlleles = getAlleles(child); - //Create child's genotype - trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,childPhasedAllelesAlleles,Genotype.NO_NEG_LOG_10PERROR,null,null,true)); - - //Once a phased combination is found; exit - return; + //Build all possible child genotypes for the given parent's genotypes + for (Allele momAllele : motherAlleles) { + for (Allele fatherAllele : fatherAlleles) { + ArrayList possiblePhasedChildAlleles = new ArrayList(2); + possiblePhasedChildAlleles.add(momAllele); + possiblePhasedChildAlleles.add(fatherAllele); + possiblePhasedChildGenotypes.add(possiblePhasedChildAlleles); + } } + + for (ArrayList childPhasedAllelesAlleles : possiblePhasedChildGenotypes) { + int firstAlleleIndex = childPhasedAllelesAlleles.indexOf(childAlleles.get(0)); + int secondAlleleIndex = childPhasedAllelesAlleles.lastIndexOf(childAlleles.get(1)); + //If a possible combination has been found, create the genotypes + if (firstAlleleIndex != secondAlleleIndex && firstAlleleIndex > -1 && secondAlleleIndex > -1) { + //Create mother's genotype + ArrayList motherPhasedAlleles = new ArrayList(2); + motherPhasedAlleles.add(childPhasedAllelesAlleles.get(0)); + if(motherAlleles.get(0) != motherPhasedAlleles.get(0)) + motherPhasedAlleles.add(motherAlleles.get(0)); + else + motherPhasedAlleles.add(motherAlleles.get(1)); + trioPhasedGenotypes.put(FamilyMember.MOTHER, new Genotype(DUMMY_NAME,motherPhasedAlleles,Genotype.NO_NEG_LOG_10PERROR,null,null,true)); + + //Create father's genotype + ArrayList fatherPhasedAlleles = new ArrayList(2); + fatherPhasedAlleles.add(childPhasedAllelesAlleles.get(1)); + if(fatherAlleles.get(0) != fatherPhasedAlleles.get(0)) + fatherPhasedAlleles.add(fatherAlleles.get(0)); + else + fatherPhasedAlleles.add(fatherAlleles.get(1)); + trioPhasedGenotypes.put(FamilyMember.FATHER, new Genotype(DUMMY_NAME,fatherPhasedAlleles,Genotype.NO_NEG_LOG_10PERROR,null,null,true)); + + //Create child's genotype + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,childPhasedAllelesAlleles,Genotype.NO_NEG_LOG_10PERROR,null,null,true)); + + //Once a phased combination is found; exit + return; + } + } + + //If this is reached then no phasing could be found + trioPhasedGenotypes.put(FamilyMember.MOTHER, new Genotype(DUMMY_NAME,getAlleles(mother),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); + trioPhasedGenotypes.put(FamilyMember.FATHER, new Genotype(DUMMY_NAME,getAlleles(father),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); + trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(child),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); } - //If this is reached then no phasing could be found - trioPhasedGenotypes.put(FamilyMember.MOTHER, new Genotype(DUMMY_NAME,getAlleles(mother),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); - trioPhasedGenotypes.put(FamilyMember.FATHER, new Genotype(DUMMY_NAME,getAlleles(father),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); - trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(child),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); - } + /* Constructor: Creates a conceptual trio genotype combination from the given genotypes. + If one or more genotypes are set as NO_CALL or UNAVAILABLE, it will phase them like a pair + or single individual. + */ + public TrioPhase(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ - /* Constructor: Creates a conceptual trio genotype combination from the given genotypes. - If one or more genotypes are set as NO_CALL or UNAVAILABLE, it will phase them like a pair - or single individual. - */ - public TrioPhase(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ - - //Take care of cases where one or more family members are no call - if(child == Genotype.Type.NO_CALL || child == Genotype.Type.UNAVAILABLE){ - phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); - phaseSingleIndividualAlleles(father, FamilyMember.FATHER); - phaseSingleIndividualAlleles(child, FamilyMember.CHILD); - } - else if(mother == Genotype.Type.NO_CALL || mother == Genotype.Type.UNAVAILABLE){ - phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); - if(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE){ + //Take care of cases where one or more family members are no call + if(child == Genotype.Type.NO_CALL || child == Genotype.Type.UNAVAILABLE){ + phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); phaseSingleIndividualAlleles(father, FamilyMember.FATHER); phaseSingleIndividualAlleles(child, FamilyMember.CHILD); } - else - phasePairAlleles(father, child, FamilyMember.FATHER); + else if(mother == Genotype.Type.NO_CALL || mother == Genotype.Type.UNAVAILABLE){ + phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); + if(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE){ + phaseSingleIndividualAlleles(father, FamilyMember.FATHER); + phaseSingleIndividualAlleles(child, FamilyMember.CHILD); + } + else + phasePairAlleles(father, child, FamilyMember.FATHER); + } + else if(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE){ + phasePairAlleles(mother, child, FamilyMember.MOTHER); + phaseSingleIndividualAlleles(father, FamilyMember.FATHER); + } + //Special case for Het/Het/Het as it is ambiguous + else if(mother == Genotype.Type.HET && father == Genotype.Type.HET && child == Genotype.Type.HET){ + phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); + phaseSingleIndividualAlleles(father, FamilyMember.FATHER); + phaseSingleIndividualAlleles(child, FamilyMember.CHILD); + } + //All family members have genotypes and at least one of them is not Het + else{ + phaseFamilyAlleles(mother, father, child); + } } - else if(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE){ - phasePairAlleles(mother, child, FamilyMember.MOTHER); - phaseSingleIndividualAlleles(father, FamilyMember.FATHER); + + /** + * Applies the trio genotype combination to the given trio. + * @param ref: Reference allele + * @param alt: Alternate allele + * @param motherGenotype: Genotype of the mother to phase using this trio genotype combination + * @param fatherGenotype: Genotype of the father to phase using this trio genotype combination + * @param childGenotype: Genotype of the child to phase using this trio genotype combination + * @param transmissionProb: Probability for this trio genotype combination to be correct (pass NO_TRANSMISSION_PROB if unavailable) + * @param phasedGenotypes: An ArrayList to which the newly phased genotypes are added in the following order: Mother, Father, Child + */ + public void getPhasedGenotypes(Allele ref, Allele alt, Genotype motherGenotype, Genotype fatherGenotype, Genotype childGenotype, double transmissionProb,ArrayList phasedGenotypes){ + phasedGenotypes.add(getPhasedGenotype(ref,alt,motherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.MOTHER))); + phasedGenotypes.add(getPhasedGenotype(ref,alt,fatherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.FATHER))); + phasedGenotypes.add(getPhasedGenotype(ref,alt,childGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.CHILD))); } - //Special case for Het/Het/Het as it is ambiguous - else if(mother == Genotype.Type.HET && father == Genotype.Type.HET && child == Genotype.Type.HET){ - phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); - phaseSingleIndividualAlleles(father, FamilyMember.FATHER); - phaseSingleIndividualAlleles(child, FamilyMember.CHILD); - } - //All family members have genotypes and at least one of them is not Het - else{ - phaseFamilyAlleles(mother, father, child); - } - } - /** - * Applies the trio genotype combination to the given trio. - * @param ref: Reference allele - * @param alt: Alternate allele - * @param motherGenotype: Genotype of the mother to phase using this trio genotype combination - * @param fatherGenotype: Genotype of the father to phase using this trio genotype combination - * @param childGenotype: Genotype of the child to phase using this trio genotype combination - * @param transmissionProb: Probability for this trio genotype combination to be correct (pass NO_TRANSMISSION_PROB if unavailable) - * @param phasedGenotypes: An ArrayList to which the newly phased genotypes are added in the following order: Mother, Father, Child - */ - public void getPhasedGenotypes(Allele ref, Allele alt, Genotype motherGenotype, Genotype fatherGenotype, Genotype childGenotype, double transmissionProb,ArrayList phasedGenotypes){ - phasedGenotypes.add(getPhasedGenotype(ref,alt,motherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.MOTHER))); - phasedGenotypes.add(getPhasedGenotype(ref,alt,fatherGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.FATHER))); - phasedGenotypes.add(getPhasedGenotype(ref,alt,childGenotype,transmissionProb,this.trioPhasedGenotypes.get(FamilyMember.CHILD))); - } + private Genotype getPhasedGenotype(Allele refAllele, Allele altAllele, Genotype genotype, double transmissionProb, Genotype phasedGenotype){ - private Genotype getPhasedGenotype(Allele refAllele, Allele altAllele, Genotype genotype, double transmissionProb, Genotype phasedGenotype){ + //Handle null, missing and unavailable genotypes + //Note that only cases where a null/missing/unavailable genotype was passed in the first place can lead to a null/missing/unavailable + //genotype so it is safe to return the original genotype in this case. + if(genotype == null || !phasedGenotype.isAvailable() || phasedGenotype.isNoCall()) + return genotype; - //Handle null, missing and unavailable genotypes - //Note that only cases where a null/missing/unavailable genotype was passed in the first place can lead to a null/missing/unavailable - //genotype so it is safe to return the original genotype in this case. - if(genotype == null || !phasedGenotype.isAvailable() || phasedGenotype.isNoCall()) - return genotype; + //Add the transmission probability + Map genotypeAttributes = new HashMap(); + genotypeAttributes.putAll(genotype.getAttributes()); + if(transmissionProb>NO_TRANSMISSION_PROB) + genotypeAttributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, MathUtils.probabilityToPhredScale(1-(transmissionProb))); - //Add the transmission probability - Map genotypeAttributes = new HashMap(); - genotypeAttributes.putAll(genotype.getAttributes()); - if(transmissionProb>NO_TRANSMISSION_PROB) - genotypeAttributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, MathUtils.probabilityToPhredScale(1-(transmissionProb))); + ArrayList phasedAlleles = new ArrayList(2); + for(Allele allele : phasedGenotype.getAlleles()){ + if(allele.isReference()) + phasedAlleles.add(refAllele); + else if(allele.isNonReference()) + phasedAlleles.add(altAllele); + //At this point there should not be any other alleles left + else + throw new UserException(String.format("BUG: Unexpected allele: %s. Please report.",allele.toString())); - ArrayList phasedAlleles = new ArrayList(2); - for(Allele allele : phasedGenotype.getAlleles()){ - if(allele.isReference()) - phasedAlleles.add(refAllele); - else if(allele.isNonReference()) - phasedAlleles.add(altAllele); - //At this point there should not be any other alleles left + } + + //Compute the new Log10Error if the genotype is different from the original genotype + double negLog10Error; + if(genotype.getType() == phasedGenotype.getType()) + negLog10Error = genotype.getNegLog10PError(); else - throw new UserException(String.format("BUG: Unexpected allele: %s. Please report.",allele.toString())); + negLog10Error = genotype.getLikelihoods().getNegLog10GQ(phasedGenotype.getType()); + return new Genotype(genotype.getSampleName(), phasedAlleles, negLog10Error, null, genotypeAttributes, phasedGenotype.isPhased()); } - //Compute the new Log10Error if the genotype is different from the original genotype - double negLog10Error; - if(genotype.getType() == phasedGenotype.getType()) - negLog10Error = genotype.getNegLog10PError(); - else - negLog10Error = genotype.getLikelihoods().getNegLog10GQ(phasedGenotype.getType()); - - return new Genotype(genotype.getSampleName(), phasedAlleles, negLog10Error, null, genotypeAttributes, phasedGenotype.isPhased()); - } - } From 1f044faedd01317b71c3abf91ac935beb6581223 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 26 Oct 2011 19:57:09 +0200 Subject: [PATCH 11/44] - Genotype assignment in case of equally likeli combination is now random - Genotype combinations with 0 confidence are now left unphased --- .../walkers/phasing/PhaseByTransmission.java | 65 +++++++++++++------ 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index 956cf7c89..f5e9ce6ea 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -109,6 +109,9 @@ public class PhaseByTransmission extends RodWalker, HashMa private final Byte NUM_HET_HET_HET = 4; private final Byte NUM_VIOLATIONS = 5; + //Random number generator + private Random rand = new Random(); + private enum FamilyMember { MOTHER, FATHER, @@ -309,17 +312,22 @@ public class PhaseByTransmission extends RodWalker, HashMa private Genotype getPhasedGenotype(Allele refAllele, Allele altAllele, Genotype genotype, double transmissionProb, Genotype phasedGenotype){ + int phredScoreTransmission = -1; + if(transmissionProb != NO_TRANSMISSION_PROB) + phredScoreTransmission = MathUtils.probabilityToPhredScale(1-(transmissionProb)); + //Handle null, missing and unavailable genotypes //Note that only cases where a null/missing/unavailable genotype was passed in the first place can lead to a null/missing/unavailable //genotype so it is safe to return the original genotype in this case. - if(genotype == null || !phasedGenotype.isAvailable() || phasedGenotype.isNoCall()) + //In addition, if the phasing confidence is 0, then return the unphased, original genotypes. + if(phredScoreTransmission ==0 || genotype == null || !phasedGenotype.isAvailable() || phasedGenotype.isNoCall()) return genotype; //Add the transmission probability Map genotypeAttributes = new HashMap(); genotypeAttributes.putAll(genotype.getAttributes()); if(transmissionProb>NO_TRANSMISSION_PROB) - genotypeAttributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, MathUtils.probabilityToPhredScale(1-(transmissionProb))); + genotypeAttributes.put(TRANSMISSION_PROBABILITY_TAG_NAME, phredScoreTransmission); ArrayList phasedAlleles = new ArrayList(2); for(Allele allele : phasedGenotype.getAlleles()){ @@ -538,36 +546,46 @@ public class PhaseByTransmission extends RodWalker, HashMa //Prior vars double bestConfigurationLikelihood = 0.0; double norm = 0.0; - boolean isMV = false; - int bestConfigurationGenotypeDiffs=4; - Genotype.Type bestMotherGenotype = getTypeSafeNull(mother); - Genotype.Type bestFatherGenotype = getTypeSafeNull(father); - Genotype.Type bestChildGenotype = getTypeSafeNull(child); + int configuration_index =0; + ArrayList isMV = new ArrayList(); + isMV.add(false); + ArrayList bestMotherGenotype = new ArrayList(); + bestMotherGenotype.add(getTypeSafeNull(mother)); + ArrayList bestFatherGenotype = new ArrayList(); + bestFatherGenotype.add(getTypeSafeNull(father)); + ArrayList bestChildGenotype = new ArrayList(); + bestChildGenotype.add(getTypeSafeNull(child)); //Get the most likely combination //Only check for most likely combination if at least a parent and the child have genotypes if(childLikelihoods.size()>2 && (motherLikelihoods.size() + fatherLikelihoods.size())>3){ int mvCount; double configurationLikelihood; - int configurationGenotypeDiffs; for(Map.Entry motherGenotype : motherLikelihoods.entrySet()){ for(Map.Entry fatherGenotype : fatherLikelihoods.entrySet()){ for(Map.Entry childGenotype : childLikelihoods.entrySet()){ mvCount = mvCountMatrix.get(motherGenotype.getKey()).get(fatherGenotype.getKey()).get(childGenotype.getKey()); configurationLikelihood = mvCount>0 ? Math.pow(deNovoPrior,mvCount)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue() : (1.0-11*deNovoPrior)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue(); norm += configurationLikelihood; - configurationGenotypeDiffs = countFamilyGenotypeDiff(mother.getType(),father.getType(),child.getType(),motherGenotype.getKey(),fatherGenotype.getKey(),childGenotype.getKey()); //Keep this combination if //It has a better likelihood //Or it has the same likelihood but requires less changes from original genotypes - if ((configurationLikelihood > bestConfigurationLikelihood) || - (configurationLikelihood == bestConfigurationLikelihood && configurationGenotypeDiffs < bestConfigurationGenotypeDiffs)) { + if (configurationLikelihood > bestConfigurationLikelihood){ bestConfigurationLikelihood = configurationLikelihood; - bestMotherGenotype = motherGenotype.getKey(); - bestFatherGenotype = fatherGenotype.getKey(); - bestChildGenotype = childGenotype.getKey(); - isMV = mvCount>0; - bestConfigurationGenotypeDiffs=configurationGenotypeDiffs; + isMV.clear(); + isMV.add(mvCount>0); + bestMotherGenotype.clear(); + bestMotherGenotype.add(motherGenotype.getKey()); + bestFatherGenotype.clear(); + bestFatherGenotype.add(fatherGenotype.getKey()); + bestChildGenotype.clear(); + bestChildGenotype.add(childGenotype.getKey()); + } + else if(configurationLikelihood == bestConfigurationLikelihood) { + bestMotherGenotype.add(motherGenotype.getKey()); + bestFatherGenotype.add(fatherGenotype.getKey()); + bestChildGenotype.add(childGenotype.getKey()); + isMV.add(mvCount>0); } } } @@ -575,17 +593,22 @@ public class PhaseByTransmission extends RodWalker, HashMa //normalize the best configuration probability bestConfigurationLikelihood = bestConfigurationLikelihood / norm; + + //In case of multiple equally likely combinations, take a random one + if(bestMotherGenotype.size()>1){ + configuration_index = rand.nextInt(bestMotherGenotype.size()-1); + } + } else{ bestConfigurationLikelihood = NO_TRANSMISSION_PROB; } - //Get the phased alleles for the genotype configuration - TrioPhase phasedTrioGenotypes = transmissionMatrix.get(bestMotherGenotype).get(bestFatherGenotype).get(bestChildGenotype); + TrioPhase phasedTrioGenotypes = transmissionMatrix.get(bestMotherGenotype.get(configuration_index)).get(bestFatherGenotype.get(configuration_index)).get(bestChildGenotype.get(configuration_index)); - //Return the phased genotypes - phasedTrioGenotypes.getPhasedGenotypes(ref,alt,mother,father,child,bestConfigurationLikelihood,finalGenotypes); - return isMV; + //Return the phased genotypes + phasedTrioGenotypes.getPhasedGenotypes(ref,alt,mother,father,child,bestConfigurationLikelihood,finalGenotypes); + return isMV.get(configuration_index); } From b91a9c4711d60b361a226a517a97e68217fdd5cf Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 2 Nov 2011 08:04:01 +0100 Subject: [PATCH 12/44] - Fixed parent/child pairs handling (was crashing before) - Added parent/child pair reporting --- .../walkers/phasing/PhaseByTransmission.java | 151 ++++++++++++------ 1 file changed, 101 insertions(+), 50 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index f5e9ce6ea..5b2024f96 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -105,9 +105,13 @@ public class PhaseByTransmission extends RodWalker, HashMa private final Byte NUM_TRIO_GENOTYPES_CALLED = 0; private final Byte NUM_TRIO_GENOTYPES_NOCALL = 1; private final Byte NUM_TRIO_GENOTYPES_PHASED = 2; - private final Byte NUM_HET = 3; - private final Byte NUM_HET_HET_HET = 4; - private final Byte NUM_VIOLATIONS = 5; + private final Byte NUM_TRIO_HET_HET_HET = 3; + private final Byte NUM_TRIO_VIOLATIONS = 4; + private final Byte NUM_PAIR_GENOTYPES_CALLED = 5; + private final Byte NUM_PAIR_GENOTYPES_NOCALL = 6; + private final Byte NUM_PAIR_GENOTYPES_PHASED = 7; + private final Byte NUM_PAIR_HET_HET = 8; + private final Byte NUM_PAIR_VIOLATIONS = 9; //Random number generator private Random rand = new Random(); @@ -172,6 +176,7 @@ public class PhaseByTransmission extends RodWalker, HashMa if(parentGenotype == Genotype.Type.HET && childGenotype == Genotype.Type.HET){ trioPhasedGenotypes.put(parent, new Genotype(DUMMY_NAME, getAlleles(parentGenotype), Genotype.NO_NEG_LOG_10PERROR, null, null, false)); trioPhasedGenotypes.put(FamilyMember.CHILD, new Genotype(DUMMY_NAME,getAlleles(childGenotype),Genotype.NO_NEG_LOG_10PERROR,null,null,false)); + return; } ArrayList parentAlleles = getAlleles(parentGenotype); @@ -612,6 +617,48 @@ public class PhaseByTransmission extends RodWalker, HashMa } + + private void updatePairMetricsCounters(Genotype parent, Genotype child, boolean isMV, HashMap counters){ + + //Increment metrics counters + if(parent.isCalled() && child.isCalled()){ + counters.put(NUM_PAIR_GENOTYPES_CALLED,counters.get(NUM_PAIR_GENOTYPES_CALLED)+1); + if(parent.isPhased()) + counters.put(NUM_PAIR_GENOTYPES_PHASED,counters.get(NUM_PAIR_GENOTYPES_PHASED)+1); + else{ + if(parent.isHet() && child.isHet()) + counters.put(NUM_PAIR_HET_HET,counters.get(NUM_PAIR_HET_HET)+1); + + else if(isMV) + counters.put(NUM_PAIR_VIOLATIONS,counters.get(NUM_PAIR_VIOLATIONS)+1); + } + }else{ + counters.put(NUM_PAIR_GENOTYPES_NOCALL,counters.get(NUM_PAIR_GENOTYPES_NOCALL)+1); + } + + } + + private void updateTrioMetricsCounters(Genotype mother, Genotype father, Genotype child, boolean isMV, HashMap counters){ + + //Increment metrics counters + if(mother.isCalled() && father.isCalled() && child.isCalled()){ + counters.put(NUM_TRIO_GENOTYPES_CALLED,counters.get(NUM_TRIO_GENOTYPES_CALLED)+1); + if(mother.isPhased()) + counters.put(NUM_TRIO_GENOTYPES_PHASED,counters.get(NUM_TRIO_GENOTYPES_PHASED)+1); + + else{ + if(mother.isHet() && father.isHet() && child.isHet()) + counters.put(NUM_TRIO_HET_HET_HET,counters.get(NUM_TRIO_HET_HET_HET)+1); + + else if(isMV) + counters.put(NUM_TRIO_VIOLATIONS,counters.get(NUM_TRIO_VIOLATIONS)+1); + + } + }else{ + counters.put(NUM_TRIO_GENOTYPES_NOCALL,counters.get(NUM_TRIO_GENOTYPES_NOCALL)+1); + } + } + /** * For each variant in the file, determine the phasing for the child and replace the child's genotype with the trio's genotype * @@ -623,13 +670,17 @@ public class PhaseByTransmission extends RodWalker, HashMa @Override public HashMap map(RefMetaDataTracker tracker, ReferenceContext ref, AlignmentContext context) { - //Local cars to avoid lookups on increment - int numTrioGenotypesCalled = 0; - int numTrioGenotypesNoCall = 0; - int numTrioGenotypesPhased = 0; - int numHet = 0 ; - int numHetHetHet = 0; - int numMVs = 0; + HashMap metricsCounters = new HashMap(10); + metricsCounters.put(NUM_TRIO_GENOTYPES_CALLED,0); + metricsCounters.put(NUM_TRIO_GENOTYPES_NOCALL,0); + metricsCounters.put(NUM_TRIO_GENOTYPES_PHASED,0); + metricsCounters.put(NUM_TRIO_HET_HET_HET,0); + metricsCounters.put(NUM_TRIO_VIOLATIONS,0); + metricsCounters.put(NUM_PAIR_GENOTYPES_CALLED,0); + metricsCounters.put(NUM_PAIR_GENOTYPES_NOCALL,0); + metricsCounters.put(NUM_PAIR_GENOTYPES_PHASED,0); + metricsCounters.put(NUM_PAIR_HET_HET,0); + metricsCounters.put(NUM_PAIR_VIOLATIONS,0); if (tracker != null) { VariantContext vc = tracker.getFirstValue(variantCollection.variants, context.getLocation()); @@ -654,30 +705,26 @@ public class PhaseByTransmission extends RodWalker, HashMa Genotype phasedFather = trioGenotypes.get(1); Genotype phasedChild = trioGenotypes.get(2); - genotypeMap.put(phasedMother.getSampleName(), phasedMother); - genotypeMap.put(phasedFather.getSampleName(), phasedFather); - genotypeMap.put(phasedChild.getSampleName(), phasedChild); - - //Increment metrics counters - if(phasedMother.isCalled() && phasedFather.isCalled() && phasedChild.isCalled()){ - numTrioGenotypesCalled++; - if(phasedMother.isPhased()) - numTrioGenotypesPhased++; - - if(phasedMother.isHet() || phasedFather.isHet() || phasedChild.isHet()){ - numHet++; - if(phasedMother.isHet() && phasedFather.isHet() && phasedChild.isHet()) - numHetHetHet++; + //Fill the genotype map with the new genotypes and increment metrics counters + genotypeMap.put(phasedChild.getSampleName(),phasedChild); + if(mother != null){ + genotypeMap.put(phasedMother.getSampleName(), phasedMother); + if(father != null){ + genotypeMap.put(phasedFather.getSampleName(), phasedFather); + updateTrioMetricsCounters(phasedMother,phasedFather,phasedChild,isMV,metricsCounters); } - - if(isMV){ - numMVs++; - if(mvFile != null) - mvFile.println(String.format("%s\t%d\t%s\t%s\t%s\t%s\t%s:%s:%s:%s\t%s:%s:%s:%s\t%s:%s:%s:%s",vc.getChr(),vc.getStart(),vc.getFilters(),vc.getAttribute(VCFConstants.ALLELE_COUNT_KEY),sample.toString(),phasedMother.getAttribute(TRANSMISSION_PROBABILITY_TAG_NAME),phasedMother.getGenotypeString(),phasedMother.getAttribute(VCFConstants.DEPTH_KEY),phasedMother.getAttribute("AD"),phasedMother.getLikelihoods().toString(),phasedFather.getGenotypeString(),phasedFather.getAttribute(VCFConstants.DEPTH_KEY),phasedFather.getAttribute("AD"),phasedFather.getLikelihoods().toString(),phasedChild.getGenotypeString(),phasedChild.getAttribute(VCFConstants.DEPTH_KEY),phasedChild.getAttribute("AD"),phasedChild.getLikelihoods().toString())); - } - }else{ - numTrioGenotypesNoCall++; + else + updatePairMetricsCounters(phasedMother,phasedChild,isMV,metricsCounters); } + else{ + genotypeMap.put(phasedFather.getSampleName(),phasedFather); + updatePairMetricsCounters(phasedFather,phasedChild,isMV,metricsCounters); + } + + //Report violation if set so + //TODO: ADAPT FOR PAIRS TOO!! + if(isMV && mvFile != null) + mvFile.println(String.format("%s\t%d\t%s\t%s\t%s\t%s\t%s:%s:%s:%s\t%s:%s:%s:%s\t%s:%s:%s:%s",vc.getChr(),vc.getStart(),vc.getFilters(),vc.getAttribute(VCFConstants.ALLELE_COUNT_KEY),sample.toString(),phasedMother.getAttribute(TRANSMISSION_PROBABILITY_TAG_NAME),phasedMother.getGenotypeString(),phasedMother.getAttribute(VCFConstants.DEPTH_KEY),phasedMother.getAttribute("AD"),phasedMother.getLikelihoods().toString(),phasedFather.getGenotypeString(),phasedFather.getAttribute(VCFConstants.DEPTH_KEY),phasedFather.getAttribute("AD"),phasedFather.getLikelihoods().toString(),phasedChild.getGenotypeString(),phasedChild.getAttribute(VCFConstants.DEPTH_KEY),phasedChild.getAttribute("AD"),phasedChild.getLikelihoods().toString())); } @@ -686,14 +733,6 @@ public class PhaseByTransmission extends RodWalker, HashMa vcfWriter.add(newvc); } - - HashMap metricsCounters = new HashMap(5); - metricsCounters.put(NUM_TRIO_GENOTYPES_CALLED,numTrioGenotypesCalled); - metricsCounters.put(NUM_TRIO_GENOTYPES_NOCALL,numTrioGenotypesNoCall); - metricsCounters.put(NUM_TRIO_GENOTYPES_PHASED,numTrioGenotypesPhased); - metricsCounters.put(NUM_HET,numHet); - metricsCounters.put(NUM_HET_HET_HET,numHetHetHet); - metricsCounters.put(NUM_VIOLATIONS,numMVs); return metricsCounters; } @@ -704,13 +743,17 @@ public class PhaseByTransmission extends RodWalker, HashMa */ @Override public HashMap reduceInit() { - HashMap metricsCounters = new HashMap(5); + HashMap metricsCounters = new HashMap(10); metricsCounters.put(NUM_TRIO_GENOTYPES_CALLED,0); metricsCounters.put(NUM_TRIO_GENOTYPES_NOCALL,0); metricsCounters.put(NUM_TRIO_GENOTYPES_PHASED,0); - metricsCounters.put(NUM_HET,0); - metricsCounters.put(NUM_HET_HET_HET,0); - metricsCounters.put(NUM_VIOLATIONS,0); + metricsCounters.put(NUM_TRIO_HET_HET_HET,0); + metricsCounters.put(NUM_TRIO_VIOLATIONS,0); + metricsCounters.put(NUM_PAIR_GENOTYPES_CALLED,0); + metricsCounters.put(NUM_PAIR_GENOTYPES_NOCALL,0); + metricsCounters.put(NUM_PAIR_GENOTYPES_PHASED,0); + metricsCounters.put(NUM_PAIR_HET_HET,0); + metricsCounters.put(NUM_PAIR_VIOLATIONS,0); return metricsCounters; } @@ -726,9 +769,13 @@ public class PhaseByTransmission extends RodWalker, HashMa sum.put(NUM_TRIO_GENOTYPES_CALLED,value.get(NUM_TRIO_GENOTYPES_CALLED)+sum.get(NUM_TRIO_GENOTYPES_CALLED)); sum.put(NUM_TRIO_GENOTYPES_NOCALL,value.get(NUM_TRIO_GENOTYPES_NOCALL)+sum.get(NUM_TRIO_GENOTYPES_NOCALL)); sum.put(NUM_TRIO_GENOTYPES_PHASED,value.get(NUM_TRIO_GENOTYPES_PHASED)+sum.get(NUM_TRIO_GENOTYPES_PHASED)); - sum.put(NUM_HET,value.get(NUM_HET)+sum.get(NUM_HET)); - sum.put(NUM_HET_HET_HET,value.get(NUM_HET_HET_HET)+sum.get(NUM_HET_HET_HET)); - sum.put(NUM_VIOLATIONS,value.get(NUM_VIOLATIONS)+sum.get(NUM_VIOLATIONS)); + sum.put(NUM_TRIO_HET_HET_HET,value.get(NUM_TRIO_HET_HET_HET)+sum.get(NUM_TRIO_HET_HET_HET)); + sum.put(NUM_TRIO_VIOLATIONS,value.get(NUM_TRIO_VIOLATIONS)+sum.get(NUM_TRIO_VIOLATIONS)); + sum.put(NUM_PAIR_GENOTYPES_CALLED,value.get(NUM_PAIR_GENOTYPES_CALLED)+sum.get(NUM_PAIR_GENOTYPES_CALLED)); + sum.put(NUM_PAIR_GENOTYPES_NOCALL,value.get(NUM_PAIR_GENOTYPES_NOCALL)+sum.get(NUM_PAIR_GENOTYPES_NOCALL)); + sum.put(NUM_PAIR_GENOTYPES_PHASED,value.get(NUM_PAIR_GENOTYPES_PHASED)+sum.get(NUM_PAIR_GENOTYPES_PHASED)); + sum.put(NUM_PAIR_HET_HET,value.get(NUM_PAIR_HET_HET)+sum.get(NUM_PAIR_HET_HET)); + sum.put(NUM_PAIR_VIOLATIONS,value.get(NUM_PAIR_VIOLATIONS)+sum.get(NUM_PAIR_VIOLATIONS)); return sum; } @@ -742,8 +789,12 @@ public class PhaseByTransmission extends RodWalker, HashMa logger.info("Number of complete trio-genotypes: " + result.get(NUM_TRIO_GENOTYPES_CALLED)); logger.info("Number of trio-genotypes containing no call(s): " + result.get(NUM_TRIO_GENOTYPES_NOCALL)); logger.info("Number of trio-genotypes phased: " + result.get(NUM_TRIO_GENOTYPES_PHASED)); - logger.info("Number of resulting Hom/Hom/Hom trios: " + result.get(NUM_HET)); - logger.info("Number of resulting Het/Het/Het trios: " + result.get(NUM_HET_HET_HET)); - logger.info("Number of remaining mendelian violations: " + result.get(NUM_VIOLATIONS)); + logger.info("Number of resulting Het/Het/Het trios: " + result.get(NUM_TRIO_HET_HET_HET)); + logger.info("Number of remaining mendelian violations in trios: " + result.get(NUM_TRIO_VIOLATIONS)); + logger.info("Number of complete pair-genotypes: " + result.get(NUM_PAIR_GENOTYPES_CALLED)); + logger.info("Number of pair-genotypes containing no call(s): " + result.get(NUM_PAIR_GENOTYPES_NOCALL)); + logger.info("Number of pair-genotypes phased: " + result.get(NUM_PAIR_GENOTYPES_PHASED)); + logger.info("Number of resulting Het/Het pairs: " + result.get(NUM_PAIR_HET_HET)); + logger.info("Number of remaining mendelian violations in pairs: " + result.get(NUM_PAIR_VIOLATIONS)); } } From 119ca7d742371423060bcc7150f908e1cb04de8a Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 2 Nov 2011 08:22:33 +0100 Subject: [PATCH 13/44] Fixed a bug in parent/child pairs reporting causing a crash in case the -mvf option was used and mother was not provided --- .../sting/gatk/walkers/phasing/PhaseByTransmission.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index 5b2024f96..806a84e9d 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -681,6 +681,7 @@ public class PhaseByTransmission extends RodWalker, HashMa metricsCounters.put(NUM_PAIR_GENOTYPES_PHASED,0); metricsCounters.put(NUM_PAIR_HET_HET,0); metricsCounters.put(NUM_PAIR_VIOLATIONS,0); + String mvfLine = ""; if (tracker != null) { VariantContext vc = tracker.getFirstValue(variantCollection.variants, context.getLocation()); @@ -712,19 +713,23 @@ public class PhaseByTransmission extends RodWalker, HashMa if(father != null){ genotypeMap.put(phasedFather.getSampleName(), phasedFather); updateTrioMetricsCounters(phasedMother,phasedFather,phasedChild,isMV,metricsCounters); + mvfLine = String.format("%s\t%d\t%s\t%s\t%s\t%s\t%s:%s:%s:%s\t%s:%s:%s:%s\t%s:%s:%s:%s",vc.getChr(),vc.getStart(),vc.getFilters(),vc.getAttribute(VCFConstants.ALLELE_COUNT_KEY),sample.toString(),phasedMother.getAttribute(TRANSMISSION_PROBABILITY_TAG_NAME),phasedMother.getGenotypeString(),phasedMother.getAttribute(VCFConstants.DEPTH_KEY),phasedMother.getAttribute("AD"),phasedMother.getLikelihoods().toString(),phasedFather.getGenotypeString(),phasedFather.getAttribute(VCFConstants.DEPTH_KEY),phasedFather.getAttribute("AD"),phasedFather.getLikelihoods().toString(),phasedChild.getGenotypeString(),phasedChild.getAttribute(VCFConstants.DEPTH_KEY),phasedChild.getAttribute("AD"),phasedChild.getLikelihoods().toString()); } - else + else{ updatePairMetricsCounters(phasedMother,phasedChild,isMV,metricsCounters); + mvfLine = String.format("%s\t%d\t%s\t%s\t%s\t%s\t%s:%s:%s:%s\t.:.:.:.\t%s:%s:%s:%s",vc.getChr(),vc.getStart(),vc.getFilters(),vc.getAttribute(VCFConstants.ALLELE_COUNT_KEY),sample.toString(),phasedMother.getAttribute(TRANSMISSION_PROBABILITY_TAG_NAME),phasedMother.getGenotypeString(),phasedMother.getAttribute(VCFConstants.DEPTH_KEY),phasedMother.getAttribute("AD"),phasedMother.getLikelihoods().toString(),phasedChild.getGenotypeString(),phasedChild.getAttribute(VCFConstants.DEPTH_KEY),phasedChild.getAttribute("AD"),phasedChild.getLikelihoods().toString()); + } } else{ genotypeMap.put(phasedFather.getSampleName(),phasedFather); updatePairMetricsCounters(phasedFather,phasedChild,isMV,metricsCounters); + mvfLine = String.format("%s\t%d\t%s\t%s\t%s\t%s\t.:.:.:.\t%s:%s:%s:%s\t%s:%s:%s:%s",vc.getChr(),vc.getStart(),vc.getFilters(),vc.getAttribute(VCFConstants.ALLELE_COUNT_KEY),sample.toString(),phasedFather.getAttribute(TRANSMISSION_PROBABILITY_TAG_NAME),phasedFather.getGenotypeString(),phasedFather.getAttribute(VCFConstants.DEPTH_KEY),phasedFather.getAttribute("AD"),phasedFather.getLikelihoods().toString(),phasedChild.getGenotypeString(),phasedChild.getAttribute(VCFConstants.DEPTH_KEY),phasedChild.getAttribute("AD"),phasedChild.getLikelihoods().toString()); } //Report violation if set so //TODO: ADAPT FOR PAIRS TOO!! if(isMV && mvFile != null) - mvFile.println(String.format("%s\t%d\t%s\t%s\t%s\t%s\t%s:%s:%s:%s\t%s:%s:%s:%s\t%s:%s:%s:%s",vc.getChr(),vc.getStart(),vc.getFilters(),vc.getAttribute(VCFConstants.ALLELE_COUNT_KEY),sample.toString(),phasedMother.getAttribute(TRANSMISSION_PROBABILITY_TAG_NAME),phasedMother.getGenotypeString(),phasedMother.getAttribute(VCFConstants.DEPTH_KEY),phasedMother.getAttribute("AD"),phasedMother.getLikelihoods().toString(),phasedFather.getGenotypeString(),phasedFather.getAttribute(VCFConstants.DEPTH_KEY),phasedFather.getAttribute("AD"),phasedFather.getLikelihoods().toString(),phasedChild.getGenotypeString(),phasedChild.getAttribute(VCFConstants.DEPTH_KEY),phasedChild.getAttribute("AD"),phasedChild.getLikelihoods().toString())); + mvFile.println(mvfLine); } From 19ad5b635ad46f119ed1beccccf7bb2bc6a18aef Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 2 Nov 2011 18:35:31 +0100 Subject: [PATCH 14/44] - Calculation of parent/child pairs corrected - Separated the reporting of single and double mendelian violations in trios --- .../walkers/phasing/PhaseByTransmission.java | 177 ++++++++++++------ 1 file changed, 120 insertions(+), 57 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index 806a84e9d..3c39b58d3 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -107,6 +107,7 @@ public class PhaseByTransmission extends RodWalker, HashMa private final Byte NUM_TRIO_GENOTYPES_PHASED = 2; private final Byte NUM_TRIO_HET_HET_HET = 3; private final Byte NUM_TRIO_VIOLATIONS = 4; + private final Byte NUM_TRIO_DOUBLE_VIOLATIONS = 10; private final Byte NUM_PAIR_GENOTYPES_CALLED = 5; private final Byte NUM_PAIR_GENOTYPES_NOCALL = 6; private final Byte NUM_PAIR_GENOTYPES_PHASED = 7; @@ -507,17 +508,14 @@ public class PhaseByTransmission extends RodWalker, HashMa return count; } - //Get a Map of genotype likelihoods. If the genotype is NO_CALL or UNAVAILABLE, the Map will contain a single - //NO_CALL resp. UNAVAILABLE element with a likelihood of 1.0 + //Get a Map of genotype likelihoods. + //In case of null, unavailable or no call, all likelihoods are 1/3. private EnumMap getLikelihoodsAsMapSafeNull(Genotype genotype){ - if(genotype == null || !genotype.isAvailable()){ + if(genotype == null || !genotype.isCalled()){ EnumMap likelihoods = new EnumMap(Genotype.Type.class); - likelihoods.put(Genotype.Type.UNAVAILABLE,1.0); - return likelihoods; - } - else if(genotype.isNoCall()){ - EnumMap likelihoods = new EnumMap(Genotype.Type.class); - likelihoods.put(Genotype.Type.NO_CALL,1.0); + likelihoods.put(Genotype.Type.HOM_REF,1.0/3.0); + likelihoods.put(Genotype.Type.HET,1.0/3.0); + likelihoods.put(Genotype.Type.HOM_VAR,1.0/3.0); return likelihoods; } return genotype.getLikelihoods().getAsMap(true); @@ -541,57 +539,113 @@ public class PhaseByTransmission extends RodWalker, HashMa * @param finalGenotypes: An ArrayList that will be added the genotypes phased by transmission in the following order: Mother, Father, Child * @return */ - private boolean phaseTrioGenotypes(Allele ref, Allele alt, Genotype mother, Genotype father, Genotype child,ArrayList finalGenotypes) { + private int phaseTrioGenotypes(Allele ref, Allele alt, Genotype mother, Genotype father, Genotype child,ArrayList finalGenotypes) { - //Get the PL - Map motherLikelihoods = getLikelihoodsAsMapSafeNull(mother); - Map fatherLikelihoods = getLikelihoodsAsMapSafeNull(father); + //Check whether it is a pair or trio + //Always assign the first parent as the parent having genotype information in pairs + //Always assign the mother as the first parent in trios + int parentsCalled = 0; + Map firstParentLikelihoods; + Map secondParentLikelihoods; + Genotype.Type pairSecondParentGenotype = null; + if(mother == null || !mother.isCalled()){ + firstParentLikelihoods = getLikelihoodsAsMapSafeNull(father); + secondParentLikelihoods = getLikelihoodsAsMapSafeNull(mother); + pairSecondParentGenotype = mother == null ? Genotype.Type.UNAVAILABLE : mother.getType(); + if(father != null && father.isCalled()) + parentsCalled = 1; + } + else{ + firstParentLikelihoods = getLikelihoodsAsMapSafeNull(mother); + secondParentLikelihoods = getLikelihoodsAsMapSafeNull(father); + if(father == null || !father.isCalled()){ + parentsCalled = 1; + pairSecondParentGenotype = father == null ? Genotype.Type.UNAVAILABLE : father.getType(); + }else{ + parentsCalled = 2; + } + } Map childLikelihoods = getLikelihoodsAsMapSafeNull(child); //Prior vars double bestConfigurationLikelihood = 0.0; double norm = 0.0; int configuration_index =0; - ArrayList isMV = new ArrayList(); - isMV.add(false); - ArrayList bestMotherGenotype = new ArrayList(); - bestMotherGenotype.add(getTypeSafeNull(mother)); - ArrayList bestFatherGenotype = new ArrayList(); - bestFatherGenotype.add(getTypeSafeNull(father)); + ArrayList bestMVCount = new ArrayList(); + bestMVCount.add(0); + + ArrayList bestFirstParentGenotype = new ArrayList(); + ArrayList bestSecondParentGenotype = new ArrayList(); ArrayList bestChildGenotype = new ArrayList(); + bestFirstParentGenotype.add(getTypeSafeNull(mother)); + bestSecondParentGenotype.add(getTypeSafeNull(father)); bestChildGenotype.add(getTypeSafeNull(child)); //Get the most likely combination //Only check for most likely combination if at least a parent and the child have genotypes - if(childLikelihoods.size()>2 && (motherLikelihoods.size() + fatherLikelihoods.size())>3){ + if(child.isCalled() && parentsCalled > 0){ int mvCount; - double configurationLikelihood; - for(Map.Entry motherGenotype : motherLikelihoods.entrySet()){ - for(Map.Entry fatherGenotype : fatherLikelihoods.entrySet()){ - for(Map.Entry childGenotype : childLikelihoods.entrySet()){ - mvCount = mvCountMatrix.get(motherGenotype.getKey()).get(fatherGenotype.getKey()).get(childGenotype.getKey()); - configurationLikelihood = mvCount>0 ? Math.pow(deNovoPrior,mvCount)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue() : (1.0-11*deNovoPrior)*motherGenotype.getValue()*fatherGenotype.getValue()*childGenotype.getValue(); + int cumulativeMVCount = 0; + double configurationLikelihood = 0; + for(Map.Entry childGenotype : childLikelihoods.entrySet()){ + for(Map.Entry firstParentGenotype : firstParentLikelihoods.entrySet()){ + for(Map.Entry secondParentGenotype : secondParentLikelihoods.entrySet()){ + mvCount = mvCountMatrix.get(firstParentGenotype.getKey()).get(secondParentGenotype.getKey()).get(childGenotype.getKey()); + //For parent/child pairs, sum over the possible genotype configurations of the missing parent + if(parentsCalled<2){ + cumulativeMVCount += mvCount; + configurationLikelihood += mvCount>0 ? Math.pow(deNovoPrior,mvCount)*firstParentGenotype.getValue()*secondParentGenotype.getValue()*childGenotype.getValue() : (1.0-11*deNovoPrior)*firstParentGenotype.getValue()*secondParentGenotype.getValue()*childGenotype.getValue(); + } + //Evaluate configurations of trios + else{ + configurationLikelihood = mvCount>0 ? Math.pow(deNovoPrior,mvCount)*firstParentGenotype.getValue()*secondParentGenotype.getValue()*childGenotype.getValue() : (1.0-11*deNovoPrior)*firstParentGenotype.getValue()*secondParentGenotype.getValue()*childGenotype.getValue(); + norm += configurationLikelihood; + //Keep this combination if + //It has a better likelihood + //Or it has the same likelihood but requires less changes from original genotypes + if (configurationLikelihood > bestConfigurationLikelihood){ + bestConfigurationLikelihood = configurationLikelihood; + bestMVCount.clear(); + bestMVCount.add(mvCount); + bestFirstParentGenotype.clear(); + bestFirstParentGenotype.add(firstParentGenotype.getKey()); + bestSecondParentGenotype.clear(); + bestSecondParentGenotype.add(secondParentGenotype.getKey()); + bestChildGenotype.clear(); + bestChildGenotype.add(childGenotype.getKey()); + } + else if(configurationLikelihood == bestConfigurationLikelihood) { + bestFirstParentGenotype.add(firstParentGenotype.getKey()); + bestSecondParentGenotype.add(secondParentGenotype.getKey()); + bestChildGenotype.add(childGenotype.getKey()); + bestMVCount.add(mvCount); + } + } + } + //Evaluate configurations of parent/child pairs + if(parentsCalled<2){ norm += configurationLikelihood; //Keep this combination if //It has a better likelihood //Or it has the same likelihood but requires less changes from original genotypes if (configurationLikelihood > bestConfigurationLikelihood){ bestConfigurationLikelihood = configurationLikelihood; - isMV.clear(); - isMV.add(mvCount>0); - bestMotherGenotype.clear(); - bestMotherGenotype.add(motherGenotype.getKey()); - bestFatherGenotype.clear(); - bestFatherGenotype.add(fatherGenotype.getKey()); + bestMVCount.clear(); + bestMVCount.add(cumulativeMVCount/3); bestChildGenotype.clear(); + bestFirstParentGenotype.clear(); + bestSecondParentGenotype.clear(); bestChildGenotype.add(childGenotype.getKey()); + bestFirstParentGenotype.add(firstParentGenotype.getKey()); + bestSecondParentGenotype.add(pairSecondParentGenotype); } else if(configurationLikelihood == bestConfigurationLikelihood) { - bestMotherGenotype.add(motherGenotype.getKey()); - bestFatherGenotype.add(fatherGenotype.getKey()); + bestFirstParentGenotype.add(firstParentGenotype.getKey()); + bestSecondParentGenotype.add(pairSecondParentGenotype); bestChildGenotype.add(childGenotype.getKey()); - isMV.add(mvCount>0); + bestMVCount.add(cumulativeMVCount/3); } + configurationLikelihood = 0; } } } @@ -600,8 +654,8 @@ public class PhaseByTransmission extends RodWalker, HashMa bestConfigurationLikelihood = bestConfigurationLikelihood / norm; //In case of multiple equally likely combinations, take a random one - if(bestMotherGenotype.size()>1){ - configuration_index = rand.nextInt(bestMotherGenotype.size()-1); + if(bestFirstParentGenotype.size()>1){ + configuration_index = rand.nextInt(bestFirstParentGenotype.size()-1); } } @@ -609,16 +663,20 @@ public class PhaseByTransmission extends RodWalker, HashMa bestConfigurationLikelihood = NO_TRANSMISSION_PROB; } - TrioPhase phasedTrioGenotypes = transmissionMatrix.get(bestMotherGenotype.get(configuration_index)).get(bestFatherGenotype.get(configuration_index)).get(bestChildGenotype.get(configuration_index)); + TrioPhase phasedTrioGenotypes; + if(parentsCalled < 2 && mother == null || !mother.isCalled()) + phasedTrioGenotypes = transmissionMatrix.get(bestSecondParentGenotype.get(configuration_index)).get(bestFirstParentGenotype.get(configuration_index)).get(bestChildGenotype.get(configuration_index)); + else + phasedTrioGenotypes = transmissionMatrix.get(bestFirstParentGenotype.get(configuration_index)).get(bestSecondParentGenotype.get(configuration_index)).get(bestChildGenotype.get(configuration_index)); //Return the phased genotypes phasedTrioGenotypes.getPhasedGenotypes(ref,alt,mother,father,child,bestConfigurationLikelihood,finalGenotypes); - return isMV.get(configuration_index); + return bestMVCount.get(configuration_index); } - private void updatePairMetricsCounters(Genotype parent, Genotype child, boolean isMV, HashMap counters){ + private void updatePairMetricsCounters(Genotype parent, Genotype child, int mvCount, HashMap counters){ //Increment metrics counters if(parent.isCalled() && child.isCalled()){ @@ -626,11 +684,9 @@ public class PhaseByTransmission extends RodWalker, HashMa if(parent.isPhased()) counters.put(NUM_PAIR_GENOTYPES_PHASED,counters.get(NUM_PAIR_GENOTYPES_PHASED)+1); else{ + counters.put(NUM_PAIR_VIOLATIONS,counters.get(NUM_PAIR_VIOLATIONS)+mvCount); if(parent.isHet() && child.isHet()) counters.put(NUM_PAIR_HET_HET,counters.get(NUM_PAIR_HET_HET)+1); - - else if(isMV) - counters.put(NUM_PAIR_VIOLATIONS,counters.get(NUM_PAIR_VIOLATIONS)+1); } }else{ counters.put(NUM_PAIR_GENOTYPES_NOCALL,counters.get(NUM_PAIR_GENOTYPES_NOCALL)+1); @@ -638,7 +694,7 @@ public class PhaseByTransmission extends RodWalker, HashMa } - private void updateTrioMetricsCounters(Genotype mother, Genotype father, Genotype child, boolean isMV, HashMap counters){ + private void updateTrioMetricsCounters(Genotype mother, Genotype father, Genotype child, int mvCount, HashMap counters){ //Increment metrics counters if(mother.isCalled() && father.isCalled() && child.isCalled()){ @@ -647,11 +703,14 @@ public class PhaseByTransmission extends RodWalker, HashMa counters.put(NUM_TRIO_GENOTYPES_PHASED,counters.get(NUM_TRIO_GENOTYPES_PHASED)+1); else{ - if(mother.isHet() && father.isHet() && child.isHet()) - counters.put(NUM_TRIO_HET_HET_HET,counters.get(NUM_TRIO_HET_HET_HET)+1); - - else if(isMV) - counters.put(NUM_TRIO_VIOLATIONS,counters.get(NUM_TRIO_VIOLATIONS)+1); + if(mvCount > 0){ + if(mvCount >1) + counters.put(NUM_TRIO_DOUBLE_VIOLATIONS,counters.get(NUM_TRIO_DOUBLE_VIOLATIONS)+1); + else + counters.put(NUM_TRIO_VIOLATIONS,counters.get(NUM_TRIO_VIOLATIONS)+1); + } + else if(mother.isHet() && father.isHet() && child.isHet()) + counters.put(NUM_TRIO_HET_HET_HET,counters.get(NUM_TRIO_HET_HET_HET)+1); } }else{ @@ -681,14 +740,15 @@ public class PhaseByTransmission extends RodWalker, HashMa metricsCounters.put(NUM_PAIR_GENOTYPES_PHASED,0); metricsCounters.put(NUM_PAIR_HET_HET,0); metricsCounters.put(NUM_PAIR_VIOLATIONS,0); - String mvfLine = ""; + metricsCounters.put(NUM_TRIO_DOUBLE_VIOLATIONS,0); + String mvfLine; if (tracker != null) { VariantContext vc = tracker.getFirstValue(variantCollection.variants, context.getLocation()); Map genotypeMap = vc.getGenotypes(); - boolean isMV; + int mvCount; for (Sample sample : trios) { Genotype mother = vc.getGenotype(sample.getMaternalID()); @@ -700,7 +760,7 @@ public class PhaseByTransmission extends RodWalker, HashMa continue; ArrayList trioGenotypes = new ArrayList(3); - isMV = phaseTrioGenotypes(vc.getReference(), vc.getAltAlleleWithHighestAlleleCount(), mother, father, child,trioGenotypes); + mvCount = phaseTrioGenotypes(vc.getReference(), vc.getAltAlleleWithHighestAlleleCount(), mother, father, child,trioGenotypes); Genotype phasedMother = trioGenotypes.get(0); Genotype phasedFather = trioGenotypes.get(1); @@ -712,23 +772,23 @@ public class PhaseByTransmission extends RodWalker, HashMa genotypeMap.put(phasedMother.getSampleName(), phasedMother); if(father != null){ genotypeMap.put(phasedFather.getSampleName(), phasedFather); - updateTrioMetricsCounters(phasedMother,phasedFather,phasedChild,isMV,metricsCounters); + updateTrioMetricsCounters(phasedMother,phasedFather,phasedChild,mvCount,metricsCounters); mvfLine = String.format("%s\t%d\t%s\t%s\t%s\t%s\t%s:%s:%s:%s\t%s:%s:%s:%s\t%s:%s:%s:%s",vc.getChr(),vc.getStart(),vc.getFilters(),vc.getAttribute(VCFConstants.ALLELE_COUNT_KEY),sample.toString(),phasedMother.getAttribute(TRANSMISSION_PROBABILITY_TAG_NAME),phasedMother.getGenotypeString(),phasedMother.getAttribute(VCFConstants.DEPTH_KEY),phasedMother.getAttribute("AD"),phasedMother.getLikelihoods().toString(),phasedFather.getGenotypeString(),phasedFather.getAttribute(VCFConstants.DEPTH_KEY),phasedFather.getAttribute("AD"),phasedFather.getLikelihoods().toString(),phasedChild.getGenotypeString(),phasedChild.getAttribute(VCFConstants.DEPTH_KEY),phasedChild.getAttribute("AD"),phasedChild.getLikelihoods().toString()); } else{ - updatePairMetricsCounters(phasedMother,phasedChild,isMV,metricsCounters); + updatePairMetricsCounters(phasedMother,phasedChild,mvCount,metricsCounters); mvfLine = String.format("%s\t%d\t%s\t%s\t%s\t%s\t%s:%s:%s:%s\t.:.:.:.\t%s:%s:%s:%s",vc.getChr(),vc.getStart(),vc.getFilters(),vc.getAttribute(VCFConstants.ALLELE_COUNT_KEY),sample.toString(),phasedMother.getAttribute(TRANSMISSION_PROBABILITY_TAG_NAME),phasedMother.getGenotypeString(),phasedMother.getAttribute(VCFConstants.DEPTH_KEY),phasedMother.getAttribute("AD"),phasedMother.getLikelihoods().toString(),phasedChild.getGenotypeString(),phasedChild.getAttribute(VCFConstants.DEPTH_KEY),phasedChild.getAttribute("AD"),phasedChild.getLikelihoods().toString()); } } else{ genotypeMap.put(phasedFather.getSampleName(),phasedFather); - updatePairMetricsCounters(phasedFather,phasedChild,isMV,metricsCounters); + updatePairMetricsCounters(phasedFather,phasedChild,mvCount,metricsCounters); mvfLine = String.format("%s\t%d\t%s\t%s\t%s\t%s\t.:.:.:.\t%s:%s:%s:%s\t%s:%s:%s:%s",vc.getChr(),vc.getStart(),vc.getFilters(),vc.getAttribute(VCFConstants.ALLELE_COUNT_KEY),sample.toString(),phasedFather.getAttribute(TRANSMISSION_PROBABILITY_TAG_NAME),phasedFather.getGenotypeString(),phasedFather.getAttribute(VCFConstants.DEPTH_KEY),phasedFather.getAttribute("AD"),phasedFather.getLikelihoods().toString(),phasedChild.getGenotypeString(),phasedChild.getAttribute(VCFConstants.DEPTH_KEY),phasedChild.getAttribute("AD"),phasedChild.getLikelihoods().toString()); } //Report violation if set so //TODO: ADAPT FOR PAIRS TOO!! - if(isMV && mvFile != null) + if(mvCount>0 && mvFile != null) mvFile.println(mvfLine); } @@ -759,6 +819,7 @@ public class PhaseByTransmission extends RodWalker, HashMa metricsCounters.put(NUM_PAIR_GENOTYPES_PHASED,0); metricsCounters.put(NUM_PAIR_HET_HET,0); metricsCounters.put(NUM_PAIR_VIOLATIONS,0); + metricsCounters.put(NUM_TRIO_DOUBLE_VIOLATIONS,0); return metricsCounters; } @@ -781,6 +842,7 @@ public class PhaseByTransmission extends RodWalker, HashMa sum.put(NUM_PAIR_GENOTYPES_PHASED,value.get(NUM_PAIR_GENOTYPES_PHASED)+sum.get(NUM_PAIR_GENOTYPES_PHASED)); sum.put(NUM_PAIR_HET_HET,value.get(NUM_PAIR_HET_HET)+sum.get(NUM_PAIR_HET_HET)); sum.put(NUM_PAIR_VIOLATIONS,value.get(NUM_PAIR_VIOLATIONS)+sum.get(NUM_PAIR_VIOLATIONS)); + sum.put(NUM_TRIO_DOUBLE_VIOLATIONS,value.get(NUM_TRIO_DOUBLE_VIOLATIONS)+sum.get(NUM_TRIO_DOUBLE_VIOLATIONS)); return sum; } @@ -795,7 +857,8 @@ public class PhaseByTransmission extends RodWalker, HashMa logger.info("Number of trio-genotypes containing no call(s): " + result.get(NUM_TRIO_GENOTYPES_NOCALL)); logger.info("Number of trio-genotypes phased: " + result.get(NUM_TRIO_GENOTYPES_PHASED)); logger.info("Number of resulting Het/Het/Het trios: " + result.get(NUM_TRIO_HET_HET_HET)); - logger.info("Number of remaining mendelian violations in trios: " + result.get(NUM_TRIO_VIOLATIONS)); + logger.info("Number of remaining single mendelian violations in trios: " + result.get(NUM_TRIO_VIOLATIONS)); + logger.info("Number of remaining double mendelian violations in trios: " + result.get(NUM_TRIO_DOUBLE_VIOLATIONS)); logger.info("Number of complete pair-genotypes: " + result.get(NUM_PAIR_GENOTYPES_CALLED)); logger.info("Number of pair-genotypes containing no call(s): " + result.get(NUM_PAIR_GENOTYPES_NOCALL)); logger.info("Number of pair-genotypes phased: " + result.get(NUM_PAIR_GENOTYPES_PHASED)); From 893787de535fde77b56d02e5c006400e021f76c1 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Thu, 3 Nov 2011 13:04:11 +0100 Subject: [PATCH 15/44] Functions getAsMap and getNegLog10GQ now handle missing genotype case. --- .../sting/utils/variantcontext/GenotypeLikelihoods.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java index 47c93bb1b..8c8e4f257 100755 --- a/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java +++ b/public/java/src/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoods.java @@ -101,9 +101,12 @@ public class GenotypeLikelihoods { } //Return genotype likelihoods as an EnumMap with Genotypes as keys and likelihoods as values + //Returns null in case of missing likelihoods public EnumMap getAsMap(boolean normalizeFromLog10){ //Make sure that the log10likelihoods are set double[] likelihoods = normalizeFromLog10 ? MathUtils.normalizeFromLog10(getAsVector()) : getAsVector(); + if(likelihoods == null) + return null; EnumMap likelihoodsMap = new EnumMap(Genotype.Type.class); likelihoodsMap.put(Genotype.Type.HOM_REF,likelihoods[Genotype.Type.HOM_REF.ordinal()-1]); likelihoodsMap.put(Genotype.Type.HET,likelihoods[Genotype.Type.HET.ordinal()-1]); @@ -112,10 +115,13 @@ public class GenotypeLikelihoods { } //Return the neg log10 Genotype Quality (GQ) for the given genotype + //Returns Double.NEGATIVE_INFINITY in case of missing genotype public double getNegLog10GQ(Genotype.Type genotype){ double qual = Double.NEGATIVE_INFINITY; EnumMap likelihoods = getAsMap(false); + if(likelihoods == null) + return qual; for(Map.Entry likelihood : likelihoods.entrySet()){ if(likelihood.getKey() == genotype) continue; From 385a6abec144fc72c726f88216cdcd9c3a4fa435 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Thu, 3 Nov 2011 13:04:53 +0100 Subject: [PATCH 16/44] Fixed a bug that wrongly swapped the mother and father genotypes in case the child genotype missing. --- .../gatk/walkers/phasing/PhaseByTransmission.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index 3c39b58d3..244527212 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -547,10 +547,15 @@ public class PhaseByTransmission extends RodWalker, HashMa int parentsCalled = 0; Map firstParentLikelihoods; Map secondParentLikelihoods; + ArrayList bestFirstParentGenotype = new ArrayList(); + ArrayList bestSecondParentGenotype = new ArrayList(); + ArrayList bestChildGenotype = new ArrayList(); Genotype.Type pairSecondParentGenotype = null; if(mother == null || !mother.isCalled()){ firstParentLikelihoods = getLikelihoodsAsMapSafeNull(father); secondParentLikelihoods = getLikelihoodsAsMapSafeNull(mother); + bestFirstParentGenotype.add(getTypeSafeNull(father)); + bestSecondParentGenotype.add(getTypeSafeNull(mother)); pairSecondParentGenotype = mother == null ? Genotype.Type.UNAVAILABLE : mother.getType(); if(father != null && father.isCalled()) parentsCalled = 1; @@ -558,6 +563,8 @@ public class PhaseByTransmission extends RodWalker, HashMa else{ firstParentLikelihoods = getLikelihoodsAsMapSafeNull(mother); secondParentLikelihoods = getLikelihoodsAsMapSafeNull(father); + bestFirstParentGenotype.add(getTypeSafeNull(mother)); + bestSecondParentGenotype.add(getTypeSafeNull(father)); if(father == null || !father.isCalled()){ parentsCalled = 1; pairSecondParentGenotype = father == null ? Genotype.Type.UNAVAILABLE : father.getType(); @@ -566,6 +573,7 @@ public class PhaseByTransmission extends RodWalker, HashMa } } Map childLikelihoods = getLikelihoodsAsMapSafeNull(child); + bestChildGenotype.add(getTypeSafeNull(child)); //Prior vars double bestConfigurationLikelihood = 0.0; @@ -574,13 +582,6 @@ public class PhaseByTransmission extends RodWalker, HashMa ArrayList bestMVCount = new ArrayList(); bestMVCount.add(0); - ArrayList bestFirstParentGenotype = new ArrayList(); - ArrayList bestSecondParentGenotype = new ArrayList(); - ArrayList bestChildGenotype = new ArrayList(); - bestFirstParentGenotype.add(getTypeSafeNull(mother)); - bestSecondParentGenotype.add(getTypeSafeNull(father)); - bestChildGenotype.add(getTypeSafeNull(child)); - //Get the most likely combination //Only check for most likely combination if at least a parent and the child have genotypes if(child.isCalled() && parentsCalled > 0){ From 571c724cfdc378dac05573146f64e3da7e6424ec Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Tue, 8 Nov 2011 15:15:51 +0100 Subject: [PATCH 17/44] Added reporting of the number of genotypes updated. --- .../walkers/phasing/PhaseByTransmission.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index 244527212..6394e0e24 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -113,6 +113,7 @@ public class PhaseByTransmission extends RodWalker, HashMa private final Byte NUM_PAIR_GENOTYPES_PHASED = 7; private final Byte NUM_PAIR_HET_HET = 8; private final Byte NUM_PAIR_VIOLATIONS = 9; + private final Byte NUM_GENOTYPES_MODIFIED = 11; //Random number generator private Random rand = new Random(); @@ -742,6 +743,8 @@ public class PhaseByTransmission extends RodWalker, HashMa metricsCounters.put(NUM_PAIR_HET_HET,0); metricsCounters.put(NUM_PAIR_VIOLATIONS,0); metricsCounters.put(NUM_TRIO_DOUBLE_VIOLATIONS,0); + metricsCounters.put(NUM_GENOTYPES_MODIFIED,0); + String mvfLine; if (tracker != null) { @@ -775,15 +778,21 @@ public class PhaseByTransmission extends RodWalker, HashMa genotypeMap.put(phasedFather.getSampleName(), phasedFather); updateTrioMetricsCounters(phasedMother,phasedFather,phasedChild,mvCount,metricsCounters); mvfLine = String.format("%s\t%d\t%s\t%s\t%s\t%s\t%s:%s:%s:%s\t%s:%s:%s:%s\t%s:%s:%s:%s",vc.getChr(),vc.getStart(),vc.getFilters(),vc.getAttribute(VCFConstants.ALLELE_COUNT_KEY),sample.toString(),phasedMother.getAttribute(TRANSMISSION_PROBABILITY_TAG_NAME),phasedMother.getGenotypeString(),phasedMother.getAttribute(VCFConstants.DEPTH_KEY),phasedMother.getAttribute("AD"),phasedMother.getLikelihoods().toString(),phasedFather.getGenotypeString(),phasedFather.getAttribute(VCFConstants.DEPTH_KEY),phasedFather.getAttribute("AD"),phasedFather.getLikelihoods().toString(),phasedChild.getGenotypeString(),phasedChild.getAttribute(VCFConstants.DEPTH_KEY),phasedChild.getAttribute("AD"),phasedChild.getLikelihoods().toString()); + if(!(phasedMother.getType()==mother.getType() && phasedFather.getType()==father.getType() && phasedChild.getType()==child.getType())) + metricsCounters.put(NUM_GENOTYPES_MODIFIED,metricsCounters.get(NUM_GENOTYPES_MODIFIED)+1); } else{ updatePairMetricsCounters(phasedMother,phasedChild,mvCount,metricsCounters); + if(!(phasedMother.getType()==mother.getType() && phasedChild.getType()==child.getType())) + metricsCounters.put(NUM_GENOTYPES_MODIFIED,metricsCounters.get(NUM_GENOTYPES_MODIFIED)+1); mvfLine = String.format("%s\t%d\t%s\t%s\t%s\t%s\t%s:%s:%s:%s\t.:.:.:.\t%s:%s:%s:%s",vc.getChr(),vc.getStart(),vc.getFilters(),vc.getAttribute(VCFConstants.ALLELE_COUNT_KEY),sample.toString(),phasedMother.getAttribute(TRANSMISSION_PROBABILITY_TAG_NAME),phasedMother.getGenotypeString(),phasedMother.getAttribute(VCFConstants.DEPTH_KEY),phasedMother.getAttribute("AD"),phasedMother.getLikelihoods().toString(),phasedChild.getGenotypeString(),phasedChild.getAttribute(VCFConstants.DEPTH_KEY),phasedChild.getAttribute("AD"),phasedChild.getLikelihoods().toString()); } } else{ genotypeMap.put(phasedFather.getSampleName(),phasedFather); updatePairMetricsCounters(phasedFather,phasedChild,mvCount,metricsCounters); + if(!(phasedFather.getType()==father.getType() && phasedChild.getType()==child.getType())) + metricsCounters.put(NUM_GENOTYPES_MODIFIED,metricsCounters.get(NUM_GENOTYPES_MODIFIED)+1); mvfLine = String.format("%s\t%d\t%s\t%s\t%s\t%s\t.:.:.:.\t%s:%s:%s:%s\t%s:%s:%s:%s",vc.getChr(),vc.getStart(),vc.getFilters(),vc.getAttribute(VCFConstants.ALLELE_COUNT_KEY),sample.toString(),phasedFather.getAttribute(TRANSMISSION_PROBABILITY_TAG_NAME),phasedFather.getGenotypeString(),phasedFather.getAttribute(VCFConstants.DEPTH_KEY),phasedFather.getAttribute("AD"),phasedFather.getLikelihoods().toString(),phasedChild.getGenotypeString(),phasedChild.getAttribute(VCFConstants.DEPTH_KEY),phasedChild.getAttribute("AD"),phasedChild.getLikelihoods().toString()); } @@ -820,7 +829,9 @@ public class PhaseByTransmission extends RodWalker, HashMa metricsCounters.put(NUM_PAIR_GENOTYPES_PHASED,0); metricsCounters.put(NUM_PAIR_HET_HET,0); metricsCounters.put(NUM_PAIR_VIOLATIONS,0); - metricsCounters.put(NUM_TRIO_DOUBLE_VIOLATIONS,0); + metricsCounters.put(NUM_TRIO_DOUBLE_VIOLATIONS,0); + metricsCounters.put(NUM_GENOTYPES_MODIFIED,0); + return metricsCounters; } @@ -844,6 +855,8 @@ public class PhaseByTransmission extends RodWalker, HashMa sum.put(NUM_PAIR_HET_HET,value.get(NUM_PAIR_HET_HET)+sum.get(NUM_PAIR_HET_HET)); sum.put(NUM_PAIR_VIOLATIONS,value.get(NUM_PAIR_VIOLATIONS)+sum.get(NUM_PAIR_VIOLATIONS)); sum.put(NUM_TRIO_DOUBLE_VIOLATIONS,value.get(NUM_TRIO_DOUBLE_VIOLATIONS)+sum.get(NUM_TRIO_DOUBLE_VIOLATIONS)); + sum.put(NUM_GENOTYPES_MODIFIED,value.get(NUM_GENOTYPES_MODIFIED)+sum.get(NUM_GENOTYPES_MODIFIED)); + return sum; } @@ -865,5 +878,7 @@ public class PhaseByTransmission extends RodWalker, HashMa logger.info("Number of pair-genotypes phased: " + result.get(NUM_PAIR_GENOTYPES_PHASED)); logger.info("Number of resulting Het/Het pairs: " + result.get(NUM_PAIR_HET_HET)); logger.info("Number of remaining mendelian violations in pairs: " + result.get(NUM_PAIR_VIOLATIONS)); + logger.info("Number of genotypes updated: " + result.get(NUM_GENOTYPES_MODIFIED)); + } } From 1202a809cb8ac10193a4fb2cd49f3b843e18f82e Mon Sep 17 00:00:00 2001 From: Roger Zurawicki Date: Sun, 13 Nov 2011 22:27:49 -0500 Subject: [PATCH 18/44] Added Basic Unit Tests for ReadClipper Tests some but not all functions Some tests have been disabled because they are not working --- .../utils/clipreads/ReadClipperUnitTest.java | 152 +++++++++++------- 1 file changed, 94 insertions(+), 58 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java index f625af23c..0c71a845e 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -30,9 +30,12 @@ import org.broadinstitute.sting.BaseTest; 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.BeforeTest; import org.testng.annotations.Test; +import java.util.LinkedList; +import java.util.List; + /** * Created by IntelliJ IDEA. * User: roger @@ -44,44 +47,57 @@ public class ReadClipperUnitTest extends BaseTest { // TODO: Add error messages on failed tests + + //int debug = 0; + GATKSAMRecord read, expected; ReadClipper readClipper; final static String BASES = "ACTG"; final static String QUALS = "!+5?"; //ASCII values = 33,43,53,63 - @BeforeClass + // What the test read looks like + // Ref: 1 2 3 4 5 6 7 8 + // Read: 0 1 2 3 - - - - + // ----------------------------- + // Bases: A C T G - - - - + // Quals: ! + 5 ? - - - - + + @BeforeTest public void init() { SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 1000); read = ArtificialSAMUtils.createArtificialRead(header, "read1", 0, 1, BASES.length()); - read.setReadUnmappedFlag(true); read.setReadBases(new String(BASES).getBytes()); read.setBaseQualityString(new String(QUALS)); readClipper = new ReadClipper(read); + //logger.warn(read.getCigarString()); } - @Test ( enabled = false ) + @Test ( enabled = true ) public void testHardClipBothEndsByReferenceCoordinates() { + init(); logger.warn("Executing testHardClipBothEndsByReferenceCoordinates"); - + //int debug = 1; //Clip whole read - Assert.assertEquals(readClipper.hardClipBothEndsByReferenceCoordinates(0,0), new GATKSAMRecord(read.getHeader())); + Assert.assertEquals(readClipper.hardClipBothEndsByReferenceCoordinates(1,1), new GATKSAMRecord(read.getHeader())); //clip 1 base - expected = readClipper.hardClipBothEndsByReferenceCoordinates(0,3); + expected = readClipper.hardClipBothEndsByReferenceCoordinates(1,4); Assert.assertEquals(expected.getReadBases(), BASES.substring(1,3).getBytes()); Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,3)); Assert.assertEquals(expected.getCigarString(), "1H2M1H"); } - @Test ( enabled = false ) + @Test ( enabled = false ) // TODO This fails at hardClipCigar and returns a NullPointerException public void testHardClipByReadCoordinates() { + init(); logger.warn("Executing testHardClipByReadCoordinates"); //Clip whole read Assert.assertEquals(readClipper.hardClipByReadCoordinates(0,3), new GATKSAMRecord(read.getHeader())); //clip 1 base at start + System.out.println(readClipper.read.getCigarString()); expected = readClipper.hardClipByReadCoordinates(0,0); Assert.assertEquals(expected.getReadBases(), BASES.substring(1,4).getBytes()); Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,4)); @@ -107,83 +123,101 @@ public class ReadClipperUnitTest extends BaseTest { } - @Test ( enabled = false ) + public void testIfEqual( GATKSAMRecord read, byte[] readBases, String baseQuals, String cigar) { + Assert.assertEquals(read.getReadBases(), readBases); + Assert.assertEquals(read.getBaseQualityString(), baseQuals); + Assert.assertEquals(read.getCigarString(), cigar); + } + + public class testParameter { + int inputStart; + int inputStop; + int substringStart; + int substringStop; + String cigar; + + public testParameter(int InputStart, int InputStop, int SubstringStart, int SubstringStop, String Cigar) { + inputStart = InputStart; + inputStop = InputStop; + substringStart = SubstringStart; + substringStop = SubstringStop; + cigar = Cigar; + } + } + + @Test ( enabled = true ) public void testHardClipByReferenceCoordinates() { logger.warn("Executing testHardClipByReferenceCoordinates"); - + //logger.warn(debug); //Clip whole read Assert.assertEquals(readClipper.hardClipByReferenceCoordinates(1,4), new GATKSAMRecord(read.getHeader())); - //clip 1 base at start - expected = readClipper.hardClipByReferenceCoordinates(-1,1); - Assert.assertEquals(expected.getReadBases(), BASES.substring(1,4).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,4)); - Assert.assertEquals(expected.getCigarString(), "1H3M"); + List testList = new LinkedList(); + testList.add(new testParameter(-1,1,1,4,"1H3M"));//clip 1 base at start + testList.add(new testParameter(4,-1,0,3,"3M1H"));//clip 1 base at end + testList.add(new testParameter(-1,2,2,4,"2H2M"));//clip 2 bases at start + testList.add(new testParameter(3,-1,0,2,"2M2H"));//clip 2 bases at end - //clip 1 base at end - expected = readClipper.hardClipByReferenceCoordinates(3,-1); - Assert.assertEquals(expected.getReadBases(), BASES.substring(0,3).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(0,3)); - Assert.assertEquals(expected.getCigarString(), "3M1H"); - - //clip 2 bases at start - expected = readClipper.hardClipByReferenceCoordinates(-1,2); - Assert.assertEquals(expected.getReadBases(), BASES.substring(2,4).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(2,4)); - Assert.assertEquals(expected.getCigarString(), "2H2M"); - - //clip 2 bases at end - expected = readClipper.hardClipByReferenceCoordinates(2,-1); - Assert.assertEquals(expected.getReadBases(), BASES.substring(0,2).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(0,2)); - Assert.assertEquals(expected.getCigarString(), "2M2H"); + for ( testParameter p : testList ) { + init(); + //logger.warn("Testing Parameters: " + p.inputStart+","+p.inputStop+","+p.substringStart+","+p.substringStop+","+p.cigar); + testIfEqual( readClipper.hardClipByReferenceCoordinates(p.inputStart,p.inputStop), + BASES.substring(p.substringStart,p.substringStop).getBytes(), + QUALS.substring(p.substringStart,p.substringStop), + p.cigar ); + } } - @Test ( enabled = false ) + @Test ( enabled = true ) public void testHardClipByReferenceCoordinatesLeftTail() { + init(); logger.warn("Executing testHardClipByReferenceCoordinatesLeftTail"); //Clip whole read Assert.assertEquals(readClipper.hardClipByReferenceCoordinatesLeftTail(4), new GATKSAMRecord(read.getHeader())); - //clip 1 base at start - expected = readClipper.hardClipByReferenceCoordinatesLeftTail(1); - Assert.assertEquals(expected.getReadBases(), BASES.substring(1,4).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,4)); - Assert.assertEquals(expected.getCigarString(), "1H3M"); + List testList = new LinkedList(); + testList.add(new testParameter(1,-1,1,4,"1H3M"));//clip 1 base at start + testList.add(new testParameter(2,-1,2,4,"2H2M"));//clip 2 bases at start - //clip 2 bases at start - expected = readClipper.hardClipByReferenceCoordinatesLeftTail(2); - Assert.assertEquals(expected.getReadBases(), BASES.substring(2,4).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(2,4)); - Assert.assertEquals(expected.getCigarString(), "2H2M"); + for ( testParameter p : testList ) { + init(); + //logger.warn("Testing Parameters: " + p.inputStart+","+p.substringStart+","+p.substringStop+","+p.cigar); + testIfEqual( readClipper.hardClipByReferenceCoordinatesLeftTail(p.inputStart), + BASES.substring(p.substringStart,p.substringStop).getBytes(), + QUALS.substring(p.substringStart,p.substringStop), + p.cigar ); + } } - @Test ( enabled = false ) + @Test ( enabled = true ) public void testHardClipByReferenceCoordinatesRightTail() { + init(); logger.warn("Executing testHardClipByReferenceCoordinatesRightTail"); //Clip whole read Assert.assertEquals(readClipper.hardClipByReferenceCoordinatesRightTail(1), new GATKSAMRecord(read.getHeader())); - //clip 1 base at end - expected = readClipper.hardClipByReferenceCoordinatesRightTail(3); - Assert.assertEquals(expected.getReadBases(), BASES.substring(0,3).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(0,3)); - Assert.assertEquals(expected.getCigarString(), "3M1H"); + List testList = new LinkedList(); + testList.add(new testParameter(-1,4,0,3,"3M1H"));//clip 1 base at end + testList.add(new testParameter(-1,3,0,2,"2M2H"));//clip 2 bases at end - //clip 2 bases at end - expected = readClipper.hardClipByReferenceCoordinatesRightTail(2); - Assert.assertEquals(expected.getReadBases(), BASES.substring(0,2).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(0,2)); - Assert.assertEquals(expected.getCigarString(), "2M2H"); + for ( testParameter p : testList ) { + init(); + //logger.warn("Testing Parameters: " + p.inputStop+","+p.substringStart+","+p.substringStop+","+p.cigar); + testIfEqual( readClipper.hardClipByReferenceCoordinatesRightTail(p.inputStop), + BASES.substring(p.substringStart,p.substringStop).getBytes(), + QUALS.substring(p.substringStart,p.substringStop), + p.cigar ); + } } - @Test ( enabled = false ) + @Test ( enabled = false ) // TODO This function is returning null reads public void testHardClipLowQualEnds() { + init(); logger.warn("Executing testHardClipByReferenceCoordinates"); @@ -192,6 +226,7 @@ public class ReadClipperUnitTest extends BaseTest { //clip 1 base at start expected = readClipper.hardClipLowQualEnds((byte)34); + logger.warn(expected.getBaseQualities().toString()+","+expected.getBaseQualityString()); Assert.assertEquals(expected.getReadBases(), BASES.substring(1,4).getBytes()); Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,4)); Assert.assertEquals(expected.getCigarString(), "1H3M"); @@ -203,10 +238,11 @@ public class ReadClipperUnitTest extends BaseTest { Assert.assertEquals(expected.getCigarString(), "2H2M"); // Reverse Quals sequence - readClipper.getRead().setBaseQualityString("?5+!"); // 63,53,43,33 + //readClipper.getRead().setBaseQualityString("?5+!"); // 63,53,43,33 //clip 1 base at end - expected = readClipper.hardClipLowQualEnds((byte)34); + expected = readClipper.hardClipLowQualEnds((byte)'!'); + logger.warn(expected.getBaseQualities().toString()+","+expected.getBaseQualityString()); Assert.assertEquals(expected.getReadBases(), BASES.substring(0,3).getBytes()); Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(0,3)); Assert.assertEquals(expected.getCigarString(), "3M1H"); @@ -220,4 +256,4 @@ public class ReadClipperUnitTest extends BaseTest { // revert Qual sequence readClipper.getRead().setBaseQualityString(QUALS); } -} +} \ No newline at end of file From 34acf8b9789a263b991b65477e28b8fe3f25881d Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Mon, 14 Nov 2011 10:47:02 +0100 Subject: [PATCH 19/44] Added Unit tests for new methods in GenotypeLikelihoods --- .../GenotypeLikelihoodsUnitTest.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoodsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoodsUnitTest.java index 9243588ab..f3d0dedcd 100755 --- a/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoodsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/variantcontext/GenotypeLikelihoodsUnitTest.java @@ -29,10 +29,13 @@ package org.broadinstitute.sting.utils.variantcontext; // the imports for unit testing. +import org.broadinstitute.sting.utils.MathUtils; import org.testng.Assert; import org.testng.annotations.Test; import org.broadinstitute.sting.utils.codecs.vcf.VCFConstants; +import java.util.EnumMap; + /** * Basic unit test for Genotype likelihoods objects @@ -69,6 +72,50 @@ public class GenotypeLikelihoodsUnitTest { gl.getAsVector(); } + @Test + public void testGetAsMap(){ + GenotypeLikelihoods gl = new GenotypeLikelihoods(v); + //Log scale + EnumMap glMap = gl.getAsMap(false); + Assert.assertEquals(v[Genotype.Type.HOM_REF.ordinal()-1],glMap.get(Genotype.Type.HOM_REF)); + Assert.assertEquals(v[Genotype.Type.HET.ordinal()-1],glMap.get(Genotype.Type.HET)); + Assert.assertEquals(v[Genotype.Type.HOM_VAR.ordinal()-1],glMap.get(Genotype.Type.HOM_VAR)); + + //Linear scale + glMap = gl.getAsMap(true); + double [] vl = MathUtils.normalizeFromLog10(v); + Assert.assertEquals(vl[Genotype.Type.HOM_REF.ordinal()-1],glMap.get(Genotype.Type.HOM_REF)); + Assert.assertEquals(vl[Genotype.Type.HET.ordinal()-1],glMap.get(Genotype.Type.HET)); + Assert.assertEquals(vl[Genotype.Type.HOM_VAR.ordinal()-1],glMap.get(Genotype.Type.HOM_VAR)); + + //Test missing likelihoods + gl = new GenotypeLikelihoods("."); + glMap = gl.getAsMap(false); + Assert.assertNull(glMap); + + } + + @Test + public void testGetNegLog10GQ(){ + GenotypeLikelihoods gl = new GenotypeLikelihoods(vPLString); + + //GQ for the best guess genotype + Assert.assertEquals(gl.getNegLog10GQ(Genotype.Type.HET),3.9); + + double[] test = MathUtils.normalizeFromLog10(gl.getAsVector()); + + //GQ for the other genotypes + Assert.assertEquals(gl.getNegLog10GQ(Genotype.Type.HOM_REF), -1.0 * Math.log10(1.0 - test[Genotype.Type.HOM_REF.ordinal()-1])); + Assert.assertEquals(gl.getNegLog10GQ(Genotype.Type.HOM_VAR), -1.0 * Math.log10(1.0 - test[Genotype.Type.HOM_VAR.ordinal()-1])); + + //Test missing likelihoods + gl = new GenotypeLikelihoods("."); + Assert.assertEquals(gl.getNegLog10GQ(Genotype.Type.HOM_REF),Double.NEGATIVE_INFINITY); + Assert.assertEquals(gl.getNegLog10GQ(Genotype.Type.HET),Double.NEGATIVE_INFINITY); + Assert.assertEquals(gl.getNegLog10GQ(Genotype.Type.HOM_VAR),Double.NEGATIVE_INFINITY); + + } + private void assertDoubleArraysAreEqual(double[] v1, double[] v2) { Assert.assertEquals(v1.length, v2.length); for ( int i = 0; i < v1.length; i++ ) { From 6881d4800c3f782255152f63d453f12451a96509 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Mon, 14 Nov 2011 10:47:51 +0100 Subject: [PATCH 20/44] Added Integration tests for Phasing by Transmission --- .../PhaseByTransmissionIntegrationTest.java | 122 +++++++++++++++++- 1 file changed, 115 insertions(+), 7 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmissionIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmissionIntegrationTest.java index c663c1dd7..2cd76e7a5 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmissionIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmissionIntegrationTest.java @@ -6,23 +6,131 @@ import org.testng.annotations.Test; import java.util.Arrays; public class PhaseByTransmissionIntegrationTest extends WalkerTest { - private static String phaseByTransmissionTestDataRoot = validationDataLocation + "/PhaseByTransmission"; - private static String fundamentalTestVCF = phaseByTransmissionTestDataRoot + "/" + "FundamentalsTest.unfiltered.vcf"; + private static String phaseByTransmissionTestDataRoot = validationDataLocation + "PhaseByTransmission/"; + private static String goodFamilyFile = phaseByTransmissionTestDataRoot + "PhaseByTransmission.IntegrationTest.goodFamilies.ped"; + private static String TNTest = phaseByTransmissionTestDataRoot + "PhaseByTransmission.IntegrationTest.TN.vcf"; + private static String TPTest = phaseByTransmissionTestDataRoot + "PhaseByTransmission.IntegrationTest.TP.vcf"; + private static String FPTest = phaseByTransmissionTestDataRoot + "PhaseByTransmission.IntegrationTest.FP.vcf"; + private static String SpecialTest = phaseByTransmissionTestDataRoot + "PhaseByTransmission.IntegrationTest.Special.vcf"; + //Tests using PbT on all genotypes with default parameters + //And all reporting options @Test - public void testBasicFunctionality() { + public void testTrueNegativeMV() { WalkerTestSpec spec = new WalkerTestSpec( buildCommandLine( "-T PhaseByTransmission", "-NO_HEADER", "-R " + b37KGReference, - "--variant " + fundamentalTestVCF, - "-f NA12892+NA12891=NA12878", + "--variant " + TNTest, + "-ped "+ goodFamilyFile, + "-L 1:10109-10315", + "-mvf %s", + "-o %s" + ), + 2, + Arrays.asList("16fefda693156eadf1481fd9de23facb","9418a7a6405b78179ca13a67b8bfcc14") + ); + executeTest("testTrueNegativeMV", spec); + } + + @Test + public void testTruePositiveMV() { + WalkerTestSpec spec = new WalkerTestSpec( + buildCommandLine( + "-T PhaseByTransmission", + "-NO_HEADER", + "-R " + b37KGReference, + "--variant " + TPTest, + "-ped "+ goodFamilyFile, + "-L 1:10109-10315", + "-mvf %s", + "-o %s" + ), + 2, + Arrays.asList("14cf1d21a54d8b9fb506df178b634c56","efc66ae3d036715b721f9bd35b65d556") + ); + executeTest("testTruePositiveMV", spec); + } + + @Test + public void testFalsePositiveMV() { + WalkerTestSpec spec = new WalkerTestSpec( + buildCommandLine( + "-T PhaseByTransmission", + "-NO_HEADER", + "-R " + b37KGReference, + "--variant " + FPTest, + "-ped "+ goodFamilyFile, + "-L 1:10109-10315", + "-mvf %s", + "-o %s" + ), + 2, + Arrays.asList("f9b0fae9fe1e0f09b883a292b0e70a12","398724bc1e65314cc5ee92706e05a3ee") + ); + executeTest("testFalsePositiveMV", spec); + } + + @Test + public void testSpecialCases() { + WalkerTestSpec spec = new WalkerTestSpec( + buildCommandLine( + "-T PhaseByTransmission", + "-NO_HEADER", + "-R " + b37KGReference, + "--variant " + SpecialTest, + "-ped "+ goodFamilyFile, + "-L 1:10109-10315", + "-mvf %s", + "-o %s" + ), + 2, + Arrays.asList("b8d1aa3789ce77b45430c62d13ee3006","a1a333e08fafb288cda0e7711909e1c3") + ); + executeTest("testSpecialCases", spec); + } + + //Test using a different prior + //Here the FP file is used but as the prior is lowered, 3 turn to TP + @Test + public void testPriorOption() { + WalkerTestSpec spec = new WalkerTestSpec( + buildCommandLine( + "-T PhaseByTransmission", + "-NO_HEADER", + "-R " + b37KGReference, + "--variant " + FPTest, + "-ped "+ goodFamilyFile, + "-L 1:10109-10315", + "-prior 1e-4", + "-mvf %s", + "-o %s" + ), + 2, + Arrays.asList("7201ce7cc47db5840ac6b647709f7c33","c11b5e7cd7459d90d0160f917eff3b1e") + ); + executeTest("testPriorOption", spec); + } + + //Test when running without MV reporting option + //This is the exact same test file as FP but should not generate a .mvf file + @Test + public void testMVFileOption() { + WalkerTestSpec spec = new WalkerTestSpec( + buildCommandLine( + "-T PhaseByTransmission", + "-NO_HEADER", + "-R " + b37KGReference, + "--variant " + FPTest, + "-ped "+ goodFamilyFile, + "-L 1:10109-10315", "-o %s" ), 1, - Arrays.asList("") + Arrays.asList("398724bc1e65314cc5ee92706e05a3ee") ); - executeTest("testBasicFunctionality", spec); + executeTest("testMVFileOption", spec); } + } From 284430d61dafcdf47177040bbf416b7501e47168 Mon Sep 17 00:00:00 2001 From: Roger Zurawicki Date: Tue, 15 Nov 2011 00:13:52 -0500 Subject: [PATCH 21/44] Added more basic UnitTests for ReadClipper hardClipByReadCoordinatesWorks hardClipLowQualTailsWorks --- .../utils/clipreads/ReadClipperUnitTest.java | 206 +++++++++--------- 1 file changed, 103 insertions(+), 103 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java index 0c71a845e..ecb5a6d33 100644 --- a/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/clipreads/ReadClipperUnitTest.java @@ -30,8 +30,7 @@ import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; import org.testng.Assert; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; +import org.testng.annotations.*; import java.util.LinkedList; import java.util.List; @@ -47,7 +46,6 @@ public class ReadClipperUnitTest extends BaseTest { // TODO: Add error messages on failed tests - //int debug = 0; GATKSAMRecord read, expected; @@ -55,73 +53,6 @@ public class ReadClipperUnitTest extends BaseTest { final static String BASES = "ACTG"; final static String QUALS = "!+5?"; //ASCII values = 33,43,53,63 - // What the test read looks like - // Ref: 1 2 3 4 5 6 7 8 - // Read: 0 1 2 3 - - - - - // ----------------------------- - // Bases: A C T G - - - - - // Quals: ! + 5 ? - - - - - - @BeforeTest - public void init() { - SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 1000); - read = ArtificialSAMUtils.createArtificialRead(header, "read1", 0, 1, BASES.length()); - read.setReadBases(new String(BASES).getBytes()); - read.setBaseQualityString(new String(QUALS)); - - readClipper = new ReadClipper(read); - //logger.warn(read.getCigarString()); - } - - @Test ( enabled = true ) - public void testHardClipBothEndsByReferenceCoordinates() { - init(); - logger.warn("Executing testHardClipBothEndsByReferenceCoordinates"); - //int debug = 1; - //Clip whole read - Assert.assertEquals(readClipper.hardClipBothEndsByReferenceCoordinates(1,1), new GATKSAMRecord(read.getHeader())); - //clip 1 base - expected = readClipper.hardClipBothEndsByReferenceCoordinates(1,4); - Assert.assertEquals(expected.getReadBases(), BASES.substring(1,3).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,3)); - Assert.assertEquals(expected.getCigarString(), "1H2M1H"); - - } - - @Test ( enabled = false ) // TODO This fails at hardClipCigar and returns a NullPointerException - public void testHardClipByReadCoordinates() { - init(); - logger.warn("Executing testHardClipByReadCoordinates"); - - //Clip whole read - Assert.assertEquals(readClipper.hardClipByReadCoordinates(0,3), new GATKSAMRecord(read.getHeader())); - - //clip 1 base at start - System.out.println(readClipper.read.getCigarString()); - expected = readClipper.hardClipByReadCoordinates(0,0); - Assert.assertEquals(expected.getReadBases(), BASES.substring(1,4).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,4)); - Assert.assertEquals(expected.getCigarString(), "1H3M"); - - //clip 1 base at end - expected = readClipper.hardClipByReadCoordinates(3,3); - Assert.assertEquals(expected.getReadBases(), BASES.substring(0,3).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(0,3)); - Assert.assertEquals(expected.getCigarString(), "3M1H"); - - //clip 2 bases at start - expected = readClipper.hardClipByReadCoordinates(0,1); - Assert.assertEquals(expected.getReadBases(), BASES.substring(2,4).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(2,4)); - Assert.assertEquals(expected.getCigarString(), "2H2M"); - - //clip 2 bases at end - expected = readClipper.hardClipByReadCoordinates(2,3); - Assert.assertEquals(expected.getReadBases(), BASES.substring(0,2).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(0,2)); - Assert.assertEquals(expected.getCigarString(), "2M2H"); - - } public void testIfEqual( GATKSAMRecord read, byte[] readBases, String baseQuals, String cigar) { Assert.assertEquals(read.getReadBases(), readBases); @@ -145,6 +76,65 @@ public class ReadClipperUnitTest extends BaseTest { } } + // What the test read looks like + // Ref: 1 2 3 4 5 6 7 8 + // Read: 0 1 2 3 - - - - + // ----------------------------- + // Bases: A C T G - - - - + // Quals: ! + 5 ? - - - - + + @BeforeMethod + public void init() { + SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 1000); + read = ArtificialSAMUtils.createArtificialRead(header, "read1", 0, 1, BASES.length()); + read.setReadBases(new String(BASES).getBytes()); + read.setBaseQualityString(new String(QUALS)); + + readClipper = new ReadClipper(read); + //logger.warn(read.getCigarString()); + } + + @Test ( enabled = true ) + public void testHardClipBothEndsByReferenceCoordinates() { + + logger.warn("Executing testHardClipBothEndsByReferenceCoordinates"); + //int debug = 1; + //Clip whole read + Assert.assertEquals(readClipper.hardClipBothEndsByReferenceCoordinates(1,1), new GATKSAMRecord(read.getHeader())); + + //clip 1 base + expected = readClipper.hardClipBothEndsByReferenceCoordinates(1,4); + Assert.assertEquals(expected.getReadBases(), BASES.substring(1,3).getBytes()); + Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,3)); + Assert.assertEquals(expected.getCigarString(), "1H2M1H"); + + } + + @Test ( enabled = true ) + public void testHardClipByReadCoordinates() { + + logger.warn("Executing testHardClipByReadCoordinates"); + + //Clip whole read + Assert.assertEquals(readClipper.hardClipByReadCoordinates(0,3), new GATKSAMRecord(read.getHeader())); + + List testList = new LinkedList(); + testList.add(new testParameter(0,0,1,4,"1H3M"));//clip 1 base at start + testList.add(new testParameter(3,3,0,3,"3M1H"));//clip 1 base at end + testList.add(new testParameter(0,1,2,4,"2H2M"));//clip 2 bases at start + testList.add(new testParameter(2,3,0,2,"2M2H"));//clip 2 bases at end + + for ( testParameter p : testList ) { + init(); + //logger.warn("Testing Parameters: " + p.inputStart+","+p.inputStop+","+p.substringStart+","+p.substringStop+","+p.cigar); + testIfEqual( readClipper.hardClipByReadCoordinates(p.inputStart, p.inputStop), + BASES.substring(p.substringStart,p.substringStop).getBytes(), + QUALS.substring(p.substringStart,p.substringStop), + p.cigar ); + } + + } + @Test ( enabled = true ) public void testHardClipByReferenceCoordinates() { logger.warn("Executing testHardClipByReferenceCoordinates"); @@ -178,8 +168,8 @@ public class ReadClipperUnitTest extends BaseTest { Assert.assertEquals(readClipper.hardClipByReferenceCoordinatesLeftTail(4), new GATKSAMRecord(read.getHeader())); List testList = new LinkedList(); - testList.add(new testParameter(1,-1,1,4,"1H3M"));//clip 1 base at start - testList.add(new testParameter(2,-1,2,4,"2H2M"));//clip 2 bases at start + testList.add(new testParameter(1, -1, 1, 4, "1H3M"));//clip 1 base at start + testList.add(new testParameter(2, -1, 2, 4, "2H2M"));//clip 2 bases at start for ( testParameter p : testList ) { init(); @@ -201,8 +191,8 @@ public class ReadClipperUnitTest extends BaseTest { Assert.assertEquals(readClipper.hardClipByReferenceCoordinatesRightTail(1), new GATKSAMRecord(read.getHeader())); List testList = new LinkedList(); - testList.add(new testParameter(-1,4,0,3,"3M1H"));//clip 1 base at end - testList.add(new testParameter(-1,3,0,2,"2M2H"));//clip 2 bases at end + testList.add(new testParameter(-1, 4, 0, 3, "3M1H"));//clip 1 base at end + testList.add(new testParameter(-1, 3, 0, 2, "2M2H"));//clip 2 bases at end for ( testParameter p : testList ) { init(); @@ -215,45 +205,55 @@ public class ReadClipperUnitTest extends BaseTest { } - @Test ( enabled = false ) // TODO This function is returning null reads + @Test ( enabled = true ) // TODO This function is returning null reads public void testHardClipLowQualEnds() { - init(); - logger.warn("Executing testHardClipByReferenceCoordinates"); + logger.warn("Executing testHardClipByReferenceCoordinates"); //Clip whole read Assert.assertEquals(readClipper.hardClipLowQualEnds((byte)64), new GATKSAMRecord(read.getHeader())); - //clip 1 base at start - expected = readClipper.hardClipLowQualEnds((byte)34); - logger.warn(expected.getBaseQualities().toString()+","+expected.getBaseQualityString()); - Assert.assertEquals(expected.getReadBases(), BASES.substring(1,4).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(1,4)); - Assert.assertEquals(expected.getCigarString(), "1H3M"); - - //clip 2 bases at start - expected = readClipper.hardClipLowQualEnds((byte)44); - Assert.assertEquals(expected.getReadBases(), BASES.substring(2,4).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(2,4)); - Assert.assertEquals(expected.getCigarString(), "2H2M"); + List testList = new LinkedList(); + testList.add(new testParameter(1,-1,1,4,"1H3M"));//clip 1 base at start + testList.add(new testParameter(11,-1,2,4,"2H2M"));//clip 2 bases at start + for ( testParameter p : testList ) { + init(); + //logger.warn("Testing Parameters: " + p.inputStart+","+p.substringStart+","+p.substringStop+","+p.cigar); + testIfEqual( readClipper.hardClipLowQualEnds( (byte)p.inputStart ), + BASES.substring(p.substringStart,p.substringStop).getBytes(), + QUALS.substring(p.substringStart,p.substringStop), + p.cigar ); + } + /* todo find a better way to test lowqual tail clipping on both sides // Reverse Quals sequence - //readClipper.getRead().setBaseQualityString("?5+!"); // 63,53,43,33 + readClipper.getRead().setBaseQualityString("?5+!"); // 63,53,43,33 - //clip 1 base at end - expected = readClipper.hardClipLowQualEnds((byte)'!'); - logger.warn(expected.getBaseQualities().toString()+","+expected.getBaseQualityString()); - Assert.assertEquals(expected.getReadBases(), BASES.substring(0,3).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(0,3)); - Assert.assertEquals(expected.getCigarString(), "3M1H"); + testList = new LinkedList(); + testList.add(new testParameter(1,-1,0,3,"3M1H"));//clip 1 base at end + testList.add(new testParameter(11,-1,0,2,"2M2H"));//clip 2 bases at end - //clip 2 bases at end - expected = readClipper.hardClipLowQualEnds((byte)44); - Assert.assertEquals(expected.getReadBases(), BASES.substring(0,2).getBytes()); - Assert.assertEquals(expected.getBaseQualityString(), QUALS.substring(0,2)); - Assert.assertEquals(expected.getCigarString(), "2M2H"); + for ( testParameter p : testList ) { + init(); + readClipper.getRead().setBaseQualityString("?5+!"); // 63,53,43,33 + //logger.warn("Testing Parameters: " + p.inputStart+","+p.substringStart+","+p.substringStop+","+p.cigar); + testIfEqual( readClipper.hardClipLowQualEnds( (byte)p.inputStart ), + BASES.substring(p.substringStart,p.substringStop).getBytes(), + QUALS.substring(p.substringStart,p.substringStop), + p.cigar ); + } + */ + } - // revert Qual sequence - readClipper.getRead().setBaseQualityString(QUALS); + public class CigarReadMaker { + + } + + @Test ( enabled = false ) + public void testHardClipSoftClippedBases() { + + // Generate a list of cigars to test + // We will use testParameter in the following way + // Right tail, left tail, } } \ No newline at end of file From b66556f4a0faf3036275741b07e0bace4df34943 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Tue, 15 Nov 2011 09:22:57 -0500 Subject: [PATCH 22/44] Update error message so that it's clear ReadPair Walkers are exceptions --- .../src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index f8e87aa58..2ceb4ab46 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -480,7 +480,7 @@ public class GenomeAnalysisEngine { } } else if (walker instanceof ReadPairWalker) { if(readsDataSource != null && readsDataSource.getSortOrder() != SAMFileHeader.SortOrder.queryname) - throw new UserException.MissortedBAM(SAMFileHeader.SortOrder.queryname, "Read pair walkers can only walk over query name-sorted data. Please resort your input BAM file."); + throw new UserException.MissortedBAM(SAMFileHeader.SortOrder.queryname, "Read pair walkers are exceptions in that they cannot be run on coordinate-sorted BAMs but instead require query name-sorted files. You will need to resort your input BAM file in query name order to use this walker."); if(intervals != null && !intervals.isEmpty()) throw new UserException.CommandLineException("Pairs traversal cannot be used in conjunction with intervals."); From b45d10e6f1fc3443b9cfada54f4554f5f2e6d34f Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Tue, 15 Nov 2011 10:23:59 -0500 Subject: [PATCH 23/44] The DP in the FORMAT field (per sample) must also use the representative count or else it's always 1 for reduced reads. --- .../genotyper/GenotypeLikelihoodsCalculationModel.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GenotypeLikelihoodsCalculationModel.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GenotypeLikelihoodsCalculationModel.java index 489e963e8..74c55dbfe 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GenotypeLikelihoodsCalculationModel.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/GenotypeLikelihoodsCalculationModel.java @@ -26,7 +26,6 @@ package org.broadinstitute.sting.gatk.walkers.genotyper; import org.apache.log4j.Logger; -import org.broadinstitute.sting.commandline.RodBinding; import org.broadinstitute.sting.gatk.contexts.AlignmentContext; import org.broadinstitute.sting.gatk.contexts.AlignmentContextUtils; import org.broadinstitute.sting.gatk.contexts.ReferenceContext; @@ -36,7 +35,6 @@ import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.pileup.PileupElement; import org.broadinstitute.sting.utils.pileup.ReadBackedPileup; import org.broadinstitute.sting.utils.variantcontext.Allele; -import org.broadinstitute.sting.utils.variantcontext.VariantContext; import java.util.Map; @@ -83,8 +81,7 @@ public abstract class GenotypeLikelihoodsCalculationModel implements Cloneable { * @param priors priors to use for GLs * @param GLs hash of sample->GL to fill in * @param alternateAlleleToUse the alternate allele to use, null if not set - * - * @param useBAQedPileup + * @param useBAQedPileup should we use the BAQed pileup or the raw one? * @return genotype likelihoods per sample for AA, AB, BB */ public abstract Allele getLikelihoods(RefMetaDataTracker tracker, @@ -93,13 +90,14 @@ public abstract class GenotypeLikelihoodsCalculationModel implements Cloneable { AlignmentContextUtils.ReadOrientation contextType, GenotypePriors priors, Map GLs, - Allele alternateAlleleToUse, boolean useBAQedPileup); + Allele alternateAlleleToUse, + boolean useBAQedPileup); protected int getFilteredDepth(ReadBackedPileup pileup) { int count = 0; for ( PileupElement p : pileup ) { if ( BaseUtils.isRegularBase( p.getBase() ) ) - count++; + count += p.getRepresentativeCount(); } return count; From 7fada320a9dc36b8ff335dcfbc334b20dd81475b Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Tue, 15 Nov 2011 14:53:27 -0500 Subject: [PATCH 25/44] The right fix for this test is just to delete it. --- .../org/broadinstitute/sting/utils/SimpleTimerUnitTest.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/public/java/test/org/broadinstitute/sting/utils/SimpleTimerUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/SimpleTimerUnitTest.java index 3f5d05e66..7a2696b7b 100755 --- a/public/java/test/org/broadinstitute/sting/utils/SimpleTimerUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/SimpleTimerUnitTest.java @@ -41,11 +41,6 @@ public class SimpleTimerUnitTest extends BaseTest { double t6 = t.getElapsedTime(); Assert.assertTrue(t5 >= t4, "Restarted timer elapsed time should be after elapsed time preceding the restart"); Assert.assertTrue(t6 >= t5, "Second elapsed time not after the first in restarted timer"); - - t.stop().start(); - Assert.assertTrue(t.isRunning(), "second started timer isn't running"); - Assert.assertTrue(t.getElapsedTime() >= 0.0, "elapsed time should have been reset"); - Assert.assertTrue(t.getElapsedTime() < t6, "elapsed time isn't less than time before start call"); // we should have effective no elapsed time } private final static void idleLoop() { From 0d163e3f52b4de1b54c2213aea5aca14f336acd0 Mon Sep 17 00:00:00 2001 From: David Roazen Date: Tue, 15 Nov 2011 16:05:20 -0500 Subject: [PATCH 26/44] SnpEff 2.0.4 support -Modified the SnpEff parser to work with the SnpEff 2.0.4 VCF output format -Assigning functional classes and effect impacts now handled directly by SnpEff rather than the GATK -Removed support for SnpEff 2.0.2, as we no longer trust the output of that version since it doesn't exclude effects associated with certain nonsensical transcripts. These effects are excluded as of 2.0.4. -Updated unit and integration tests This support is based on a *release-candidate* of SnpEff 2.0.4, and so is subject to change between now and the next GATK release. --- .../sting/gatk/walkers/annotator/SnpEff.java | 180 +++++++++--------- .../walkers/annotator/SnpEffUnitTest.java | 20 +- .../VariantAnnotatorIntegrationTest.java | 4 +- .../VariantEvalIntegrationTest.java | 6 +- 4 files changed, 106 insertions(+), 104 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SnpEff.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SnpEff.java index 85977bf8e..1956dac6c 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SnpEff.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/SnpEff.java @@ -56,7 +56,7 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio // We refuse to parse SnpEff output files generated by unsupported versions, or // lacking a SnpEff version number in the VCF header: - public static final String[] SUPPORTED_SNPEFF_VERSIONS = { "2.0.2" }; + public static final String[] SUPPORTED_SNPEFF_VERSIONS = { "2.0.4" }; public static final String SNPEFF_VCF_HEADER_VERSION_LINE_KEY = "SnpEffVersion"; public static final String SNPEFF_VCF_HEADER_COMMAND_LINE_KEY = "SnpEffCmd"; @@ -77,13 +77,13 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio public enum InfoFieldKey { EFFECT_KEY ("SNPEFF_EFFECT", -1), IMPACT_KEY ("SNPEFF_IMPACT", 0), - CODON_CHANGE_KEY ("SNPEFF_CODON_CHANGE", 1), - AMINO_ACID_CHANGE_KEY ("SNPEFF_AMINO_ACID_CHANGE", 2), - GENE_NAME_KEY ("SNPEFF_GENE_NAME", 3), - GENE_BIOTYPE_KEY ("SNPEFF_GENE_BIOTYPE", 4), - TRANSCRIPT_ID_KEY ("SNPEFF_TRANSCRIPT_ID", 6), - EXON_ID_KEY ("SNPEFF_EXON_ID", 7), - FUNCTIONAL_CLASS_KEY ("SNPEFF_FUNCTIONAL_CLASS", -1); + FUNCTIONAL_CLASS_KEY ("SNPEFF_FUNCTIONAL_CLASS", 1), + CODON_CHANGE_KEY ("SNPEFF_CODON_CHANGE", 2), + AMINO_ACID_CHANGE_KEY ("SNPEFF_AMINO_ACID_CHANGE", 3), + GENE_NAME_KEY ("SNPEFF_GENE_NAME", 4), + GENE_BIOTYPE_KEY ("SNPEFF_GENE_BIOTYPE", 5), + TRANSCRIPT_ID_KEY ("SNPEFF_TRANSCRIPT_ID", 7), + EXON_ID_KEY ("SNPEFF_EXON_ID", 8); // Actual text of the key private final String keyName; @@ -110,70 +110,53 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio // are validated against this list. public enum EffectType { // High-impact effects: - FRAME_SHIFT (EffectFunctionalClass.NONE, false), - STOP_GAINED (EffectFunctionalClass.NONSENSE, false), - START_LOST (EffectFunctionalClass.NONE, false), - SPLICE_SITE_ACCEPTOR (EffectFunctionalClass.NONE, false), - SPLICE_SITE_DONOR (EffectFunctionalClass.NONE, false), - EXON_DELETED (EffectFunctionalClass.NONE, false), - STOP_LOST (EffectFunctionalClass.NONE, false), + SPLICE_SITE_ACCEPTOR, + SPLICE_SITE_DONOR, + START_LOST, + EXON_DELETED, + FRAME_SHIFT, + STOP_GAINED, + STOP_LOST, // Moderate-impact effects: - NON_SYNONYMOUS_CODING (EffectFunctionalClass.MISSENSE, false), - CODON_CHANGE (EffectFunctionalClass.NONE, false), - CODON_INSERTION (EffectFunctionalClass.NONE, false), - CODON_CHANGE_PLUS_CODON_INSERTION (EffectFunctionalClass.NONE, false), - CODON_DELETION (EffectFunctionalClass.NONE, false), - CODON_CHANGE_PLUS_CODON_DELETION (EffectFunctionalClass.NONE, false), - UTR_5_DELETED (EffectFunctionalClass.NONE, false), - UTR_3_DELETED (EffectFunctionalClass.NONE, false), + NON_SYNONYMOUS_CODING, + CODON_CHANGE, + CODON_INSERTION, + CODON_CHANGE_PLUS_CODON_INSERTION, + CODON_DELETION, + CODON_CHANGE_PLUS_CODON_DELETION, + UTR_5_DELETED, + UTR_3_DELETED, // Low-impact effects: - SYNONYMOUS_CODING (EffectFunctionalClass.SILENT, false), - SYNONYMOUS_START (EffectFunctionalClass.SILENT, false), - NON_SYNONYMOUS_START (EffectFunctionalClass.SILENT, false), - SYNONYMOUS_STOP (EffectFunctionalClass.SILENT, false), - NON_SYNONYMOUS_STOP (EffectFunctionalClass.SILENT, false), - START_GAINED (EffectFunctionalClass.NONE, false), + SYNONYMOUS_START, + NON_SYNONYMOUS_START, + START_GAINED, + SYNONYMOUS_CODING, + SYNONYMOUS_STOP, + NON_SYNONYMOUS_STOP, // Modifiers: - NONE (EffectFunctionalClass.NONE, true), - CHROMOSOME (EffectFunctionalClass.NONE, true), - INTERGENIC (EffectFunctionalClass.NONE, true), - UPSTREAM (EffectFunctionalClass.NONE, true), - UTR_5_PRIME (EffectFunctionalClass.NONE, true), - CDS (EffectFunctionalClass.NONE, true), - GENE (EffectFunctionalClass.NONE, true), - TRANSCRIPT (EffectFunctionalClass.NONE, true), - EXON (EffectFunctionalClass.NONE, true), - INTRON (EffectFunctionalClass.NONE, true), - UTR_3_PRIME (EffectFunctionalClass.NONE, true), - DOWNSTREAM (EffectFunctionalClass.NONE, true), - INTRON_CONSERVED (EffectFunctionalClass.NONE, true), - INTERGENIC_CONSERVED (EffectFunctionalClass.NONE, true), - REGULATION (EffectFunctionalClass.NONE, true), - CUSTOM (EffectFunctionalClass.NONE, true), - WITHIN_NON_CODING_GENE (EffectFunctionalClass.NONE, true); - - private final EffectFunctionalClass functionalClass; - private final boolean isModifier; - - EffectType ( EffectFunctionalClass functionalClass, boolean isModifier ) { - this.functionalClass = functionalClass; - this.isModifier = isModifier; - } - - public EffectFunctionalClass getFunctionalClass() { - return functionalClass; - } - - public boolean isModifier() { - return isModifier; - } + NONE, + CHROMOSOME, + CUSTOM, + CDS, + GENE, + TRANSCRIPT, + EXON, + INTRON_CONSERVED, + UTR_5_PRIME, + UTR_3_PRIME, + DOWNSTREAM, + INTRAGENIC, + INTERGENIC, + INTERGENIC_CONSERVED, + UPSTREAM, + REGULATION, + INTRON } - // SnpEff labels each effect as either LOW, MODERATE, or HIGH impact. We take the additional step of - // classifying some of the LOW impact effects as MODIFIERs. + // SnpEff labels each effect as either LOW, MODERATE, or HIGH impact, or as a MODIFIER. public enum EffectImpact { MODIFIER (0), LOW (1), @@ -202,7 +185,7 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio UNKNOWN } - // We assign a functional class to each SnpEff effect. + // SnpEff assigns a functional class to each effect. public enum EffectFunctionalClass { NONE (0), SILENT (1), @@ -379,13 +362,13 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio public List getKeyNames() { return Arrays.asList( InfoFieldKey.EFFECT_KEY.getKeyName(), InfoFieldKey.IMPACT_KEY.getKeyName(), + InfoFieldKey.FUNCTIONAL_CLASS_KEY.getKeyName(), InfoFieldKey.CODON_CHANGE_KEY.getKeyName(), InfoFieldKey.AMINO_ACID_CHANGE_KEY.getKeyName(), InfoFieldKey.GENE_NAME_KEY.getKeyName(), InfoFieldKey.GENE_BIOTYPE_KEY.getKeyName(), InfoFieldKey.TRANSCRIPT_ID_KEY.getKeyName(), - InfoFieldKey.EXON_ID_KEY.getKeyName(), - InfoFieldKey.FUNCTIONAL_CLASS_KEY.getKeyName() + InfoFieldKey.EXON_ID_KEY.getKeyName() ); } @@ -393,13 +376,13 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio return Arrays.asList( new VCFInfoHeaderLine(InfoFieldKey.EFFECT_KEY.getKeyName(), 1, VCFHeaderLineType.String, "The highest-impact effect resulting from the current variant (or one of the highest-impact effects, if there is a tie)"), new VCFInfoHeaderLine(InfoFieldKey.IMPACT_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Impact of the highest-impact effect resulting from the current variant " + Arrays.toString(EffectImpact.values())), + new VCFInfoHeaderLine(InfoFieldKey.FUNCTIONAL_CLASS_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Functional class of the highest-impact effect resulting from the current variant: " + Arrays.toString(EffectFunctionalClass.values())), new VCFInfoHeaderLine(InfoFieldKey.CODON_CHANGE_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Old/New codon for the highest-impact effect resulting from the current variant"), - new VCFInfoHeaderLine(InfoFieldKey.AMINO_ACID_CHANGE_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Old/New amino acid for the highest-impact effect resulting from the current variant"), + new VCFInfoHeaderLine(InfoFieldKey.AMINO_ACID_CHANGE_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Old/New amino acid for the highest-impact effect resulting from the current variant (in HGVS style)"), new VCFInfoHeaderLine(InfoFieldKey.GENE_NAME_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Gene name for the highest-impact effect resulting from the current variant"), new VCFInfoHeaderLine(InfoFieldKey.GENE_BIOTYPE_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Gene biotype for the highest-impact effect resulting from the current variant"), new VCFInfoHeaderLine(InfoFieldKey.TRANSCRIPT_ID_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Transcript ID for the highest-impact effect resulting from the current variant"), - new VCFInfoHeaderLine(InfoFieldKey.EXON_ID_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Exon ID for the highest-impact effect resulting from the current variant"), - new VCFInfoHeaderLine(InfoFieldKey.FUNCTIONAL_CLASS_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Functional class of the highest-impact effect resulting from the current variant: " + Arrays.toString(EffectFunctionalClass.values())) + new VCFInfoHeaderLine(InfoFieldKey.EXON_ID_KEY.getKeyName(), 1, VCFHeaderLineType.String, "Exon ID for the highest-impact effect resulting from the current variant") ); } @@ -409,6 +392,7 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio protected static class SnpEffEffect { private EffectType effect; private EffectImpact impact; + private EffectFunctionalClass functionalClass; private String codonChange; private String aminoAcidChange; private String geneName; @@ -420,16 +404,21 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio private String parseError = null; private boolean isWellFormed = true; - private static final int EXPECTED_NUMBER_OF_METADATA_FIELDS = 8; - private static final int NUMBER_OF_METADATA_FIELDS_UPON_WARNING = 9; - private static final int NUMBER_OF_METADATA_FIELDS_UPON_ERROR = 10; + private static final int EXPECTED_NUMBER_OF_METADATA_FIELDS = 9; + private static final int NUMBER_OF_METADATA_FIELDS_UPON_EITHER_WARNING_OR_ERROR = 10; + private static final int NUMBER_OF_METADATA_FIELDS_UPON_BOTH_WARNING_AND_ERROR = 11; - // Note that contrary to the description for the EFF field layout that SnpEff adds to the VCF header, - // errors come after warnings, not vice versa: - private static final int SNPEFF_WARNING_FIELD_INDEX = NUMBER_OF_METADATA_FIELDS_UPON_WARNING - 1; - private static final int SNPEFF_ERROR_FIELD_INDEX = NUMBER_OF_METADATA_FIELDS_UPON_ERROR - 1; + // If there is either a warning OR an error, it will be in the last field. If there is both + // a warning AND an error, the warning will be in the second-to-last field, and the error will + // be in the last field. + private static final int SNPEFF_WARNING_OR_ERROR_FIELD_UPON_SINGLE_ERROR = NUMBER_OF_METADATA_FIELDS_UPON_EITHER_WARNING_OR_ERROR - 1; + private static final int SNPEFF_WARNING_FIELD_UPON_BOTH_WARNING_AND_ERROR = NUMBER_OF_METADATA_FIELDS_UPON_BOTH_WARNING_AND_ERROR - 2; + private static final int SNPEFF_ERROR_FIELD_UPON_BOTH_WARNING_AND_ERROR = NUMBER_OF_METADATA_FIELDS_UPON_BOTH_WARNING_AND_ERROR - 1; - private static final int SNPEFF_CODING_FIELD_INDEX = 5; + // Position of the field indicating whether the effect is coding or non-coding. This field is used + // in selecting the most significant effect, but is not included in the annotations we return + // since it can be deduced from the SNPEFF_GENE_BIOTYPE field. + private static final int SNPEFF_CODING_FIELD_INDEX = 6; public SnpEffEffect ( String effectName, String[] effectMetadata ) { parseEffectName(effectName); @@ -447,11 +436,14 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio private void parseEffectMetadata ( String[] effectMetadata ) { if ( effectMetadata.length != EXPECTED_NUMBER_OF_METADATA_FIELDS ) { - if ( effectMetadata.length == NUMBER_OF_METADATA_FIELDS_UPON_WARNING ) { - parseError(String.format("SnpEff issued the following warning: %s", effectMetadata[SNPEFF_WARNING_FIELD_INDEX])); + if ( effectMetadata.length == NUMBER_OF_METADATA_FIELDS_UPON_EITHER_WARNING_OR_ERROR ) { + parseError(String.format("SnpEff issued the following warning or error: \"%s\"", + effectMetadata[SNPEFF_WARNING_OR_ERROR_FIELD_UPON_SINGLE_ERROR])); } - else if ( effectMetadata.length == NUMBER_OF_METADATA_FIELDS_UPON_ERROR ) { - parseError(String.format("SnpEff issued the following error: %s", effectMetadata[SNPEFF_ERROR_FIELD_INDEX])); + else if ( effectMetadata.length == NUMBER_OF_METADATA_FIELDS_UPON_BOTH_WARNING_AND_ERROR ) { + parseError(String.format("SnpEff issued the following warning: \"%s\", and the following error: \"%s\"", + effectMetadata[SNPEFF_WARNING_FIELD_UPON_BOTH_WARNING_AND_ERROR], + effectMetadata[SNPEFF_ERROR_FIELD_UPON_BOTH_WARNING_AND_ERROR])); } else { parseError(String.format("Wrong number of effect metadata fields. Expected %d but found %d", @@ -461,23 +453,33 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio return; } - if ( effect != null && effect.isModifier() ) { - impact = EffectImpact.MODIFIER; + // The impact field will never be empty, and should always contain one of the enumerated values: + try { + impact = EffectImpact.valueOf(effectMetadata[InfoFieldKey.IMPACT_KEY.getFieldIndex()]); } - else { + catch ( IllegalArgumentException e ) { + parseError(String.format("Unrecognized value for effect impact: %s", effectMetadata[InfoFieldKey.IMPACT_KEY.getFieldIndex()])); + } + + // The functional class field will be empty when the effect has no functional class associated with it: + if ( effectMetadata[InfoFieldKey.FUNCTIONAL_CLASS_KEY.getFieldIndex()].trim().length() > 0 ) { try { - impact = EffectImpact.valueOf(effectMetadata[InfoFieldKey.IMPACT_KEY.getFieldIndex()]); + functionalClass = EffectFunctionalClass.valueOf(effectMetadata[InfoFieldKey.FUNCTIONAL_CLASS_KEY.getFieldIndex()]); } catch ( IllegalArgumentException e ) { - parseError(String.format("Unrecognized value for effect impact: %s", effectMetadata[InfoFieldKey.IMPACT_KEY.getFieldIndex()])); + parseError(String.format("Unrecognized value for effect functional class: %s", effectMetadata[InfoFieldKey.FUNCTIONAL_CLASS_KEY.getFieldIndex()])); } } + else { + functionalClass = EffectFunctionalClass.NONE; + } codonChange = effectMetadata[InfoFieldKey.CODON_CHANGE_KEY.getFieldIndex()]; aminoAcidChange = effectMetadata[InfoFieldKey.AMINO_ACID_CHANGE_KEY.getFieldIndex()]; geneName = effectMetadata[InfoFieldKey.GENE_NAME_KEY.getFieldIndex()]; geneBiotype = effectMetadata[InfoFieldKey.GENE_BIOTYPE_KEY.getFieldIndex()]; + // The coding field will be empty when SnpEff has no coding info for the effect: if ( effectMetadata[SNPEFF_CODING_FIELD_INDEX].trim().length() > 0 ) { try { coding = EffectCoding.valueOf(effectMetadata[SNPEFF_CODING_FIELD_INDEX]); @@ -534,7 +536,7 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio return true; } else if ( impact.isSameImpactAs(other.impact) ) { - return effect.getFunctionalClass().isHigherPriorityThan(other.effect.getFunctionalClass()); + return functionalClass.isHigherPriorityThan(other.functionalClass); } return false; @@ -545,13 +547,13 @@ public class SnpEff extends InfoFieldAnnotation implements RodRequiringAnnotatio addAnnotation(annotations, InfoFieldKey.EFFECT_KEY.getKeyName(), effect.toString()); addAnnotation(annotations, InfoFieldKey.IMPACT_KEY.getKeyName(), impact.toString()); + addAnnotation(annotations, InfoFieldKey.FUNCTIONAL_CLASS_KEY.getKeyName(), functionalClass.toString()); addAnnotation(annotations, InfoFieldKey.CODON_CHANGE_KEY.getKeyName(), codonChange); addAnnotation(annotations, InfoFieldKey.AMINO_ACID_CHANGE_KEY.getKeyName(), aminoAcidChange); addAnnotation(annotations, InfoFieldKey.GENE_NAME_KEY.getKeyName(), geneName); addAnnotation(annotations, InfoFieldKey.GENE_BIOTYPE_KEY.getKeyName(), geneBiotype); addAnnotation(annotations, InfoFieldKey.TRANSCRIPT_ID_KEY.getKeyName(), transcriptID); addAnnotation(annotations, InfoFieldKey.EXON_ID_KEY.getKeyName(), exonID); - addAnnotation(annotations, InfoFieldKey.FUNCTIONAL_CLASS_KEY.getKeyName(), effect.getFunctionalClass().toString()); return annotations; } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/SnpEffUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/SnpEffUnitTest.java index 462abeba1..5c8fa32a8 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/SnpEffUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/SnpEffUnitTest.java @@ -33,7 +33,7 @@ public class SnpEffUnitTest { @Test public void testParseWellFormedEffect() { String effectName = "NON_SYNONYMOUS_CODING"; - String[] effectMetadata = { "MODERATE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829" }; + String[] effectMetadata = { "MODERATE", "MISSENSE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829" }; SnpEffEffect effect = new SnpEffEffect(effectName, effectMetadata); Assert.assertTrue( effect.isWellFormed() && effect.isCoding() ); @@ -42,7 +42,7 @@ public class SnpEffUnitTest { @Test public void testParseInvalidEffectNameEffect() { String effectName = "MADE_UP_EFFECT"; - String[] effectMetadata = { "MODERATE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829" }; + String[] effectMetadata = { "MODERATE", "MISSENSE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829" }; SnpEffEffect effect = new SnpEffEffect(effectName, effectMetadata); Assert.assertFalse(effect.isWellFormed()); @@ -51,7 +51,7 @@ public class SnpEffUnitTest { @Test public void testParseInvalidEffectImpactEffect() { String effectName = "NON_SYNONYMOUS_CODING"; - String[] effectMetadata = { "MEDIUM", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829" }; + String[] effectMetadata = { "MEDIUM", "MISSENSE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829" }; SnpEffEffect effect = new SnpEffEffect(effectName, effectMetadata); Assert.assertFalse(effect.isWellFormed()); @@ -60,27 +60,27 @@ public class SnpEffUnitTest { @Test public void testParseWrongNumberOfMetadataFieldsEffect() { String effectName = "NON_SYNONYMOUS_CODING"; - String[] effectMetadata = { "MODERATE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990" }; + String[] effectMetadata = { "MODERATE", "MISSENSE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990" }; SnpEffEffect effect = new SnpEffEffect(effectName, effectMetadata); Assert.assertFalse(effect.isWellFormed()); } @Test - public void testParseSnpEffWarningEffect() { + public void testParseSnpEffOneWarningOrErrorEffect() { String effectName = "NON_SYNONYMOUS_CODING"; - String[] effectMetadata = { "MODERATE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829", "SNPEFF_WARNING" }; + String[] effectMetadata = { "MODERATE", "MISSENSE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829", "SNPEFF_WARNING_OR_ERROR_TEXT" }; SnpEffEffect effect = new SnpEffEffect(effectName, effectMetadata); - Assert.assertTrue( ! effect.isWellFormed() && effect.getParseError().equals("SnpEff issued the following warning: SNPEFF_WARNING") ); + Assert.assertTrue( ! effect.isWellFormed() && effect.getParseError().equals("SnpEff issued the following warning or error: \"SNPEFF_WARNING_OR_ERROR_TEXT\"") ); } @Test - public void testParseSnpEffErrorEffect() { + public void testParseSnpEffBothWarningAndErrorEffect() { String effectName = "NON_SYNONYMOUS_CODING"; - String[] effectMetadata = { "MODERATE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829", "", "SNPEFF_ERROR" }; + String[] effectMetadata = { "MODERATE", "MISSENSE", "Aca/Gca", "T/A", "OR4F5", "protein_coding", "CODING", "ENST00000534990", "exon_1_69037_69829", "SNPEFF_WARNING_TEXT", "SNPEFF_ERROR_TEXT" }; SnpEffEffect effect = new SnpEffEffect(effectName, effectMetadata); - Assert.assertTrue( ! effect.isWellFormed() && effect.getParseError().equals("SnpEff issued the following error: SNPEFF_ERROR") ); + Assert.assertTrue( ! effect.isWellFormed() && effect.getParseError().equals("SnpEff issued the following warning: \"SNPEFF_WARNING_TEXT\", and the following error: \"SNPEFF_ERROR_TEXT\"") ); } } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java index bde4c4a8f..919e3d9bd 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java @@ -148,9 +148,9 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { WalkerTestSpec spec = new WalkerTestSpec( "-T VariantAnnotator -R " + hg19Reference + " -NO_HEADER -o %s -A SnpEff --variant " + validationDataLocation + "1kg_exomes_unfiltered.AFR.unfiltered.vcf --snpEffFile " + validationDataLocation + - "snpEff.AFR.unfiltered.vcf -L 1:1-1,500,000 -L 2:232,325,429", + "snpEff2.0.4.AFR.unfiltered.vcf -L 1:1-1,500,000 -L 2:232,325,429", 1, - Arrays.asList("122321a85e448f21679f6ca15c5e22ad") + Arrays.asList("51258f5c880bd1ca3eb45a1711335c66") ); executeTest("Testing SnpEff annotations", spec); } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalIntegrationTest.java index 3dceb9bd2..102d4715e 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/varianteval/VariantEvalIntegrationTest.java @@ -21,16 +21,16 @@ public class VariantEvalIntegrationTest extends WalkerTest { "-T VariantEval", "-R " + b37KGReference, "--dbsnp " + b37dbSNP132, - "--eval " + validationDataLocation + "snpEff.AFR.unfiltered.VariantAnnotator.output.vcf", + "--eval " + validationDataLocation + "snpEff2.0.4.AFR.unfiltered.VariantAnnotator.output.vcf", "-noEV", "-EV TiTvVariantEvaluator", "-noST", "-ST FunctionalClass", - "-L " + validationDataLocation + "snpEff.AFR.unfiltered.VariantAnnotator.output.vcf", + "-L " + validationDataLocation + "snpEff2.0.4.AFR.unfiltered.VariantAnnotator.output.vcf", "-o %s" ), 1, - Arrays.asList("d9dcb352c53106f54fcc981f15d35a90") + Arrays.asList("a36414421621b377d6146d58d2fcecd0") ); executeTest("testFunctionClassWithSnpeff", spec); } From 7d77fc51f55948510e9d230864dbbe78e1f791b2 Mon Sep 17 00:00:00 2001 From: Laurent Francioli Date: Wed, 16 Nov 2011 03:32:43 -0500 Subject: [PATCH 27/44] Corrected bug causing PhaseByTransmission to crash in case of new Genotype.Type --- .../walkers/phasing/PhaseByTransmission.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java index 6394e0e24..847165e3e 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/phasing/PhaseByTransmission.java @@ -151,16 +151,16 @@ public class PhaseByTransmission extends RodWalker, HashMa alleles.add(VAR); alleles.add(VAR); } - else if(genotype == Genotype.Type.NO_CALL){ - alleles.add(NO_CALL); - alleles.add(NO_CALL); - } else{ return null; } return alleles; } + private boolean isPhasable(Genotype.Type genotype){ + return genotype == Genotype.Type.HOM_REF || genotype == Genotype.Type.HET || genotype == Genotype.Type.HOM_VAR; + } + //Create a new Genotype based on information from a single individual //Homozygous genotypes will be set as phased, heterozygous won't be private void phaseSingleIndividualAlleles(Genotype.Type genotype, FamilyMember familyMember){ @@ -271,21 +271,21 @@ public class PhaseByTransmission extends RodWalker, HashMa public TrioPhase(Genotype.Type mother, Genotype.Type father, Genotype.Type child){ //Take care of cases where one or more family members are no call - if(child == Genotype.Type.NO_CALL || child == Genotype.Type.UNAVAILABLE){ + if(!isPhasable(child)){ phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); phaseSingleIndividualAlleles(father, FamilyMember.FATHER); phaseSingleIndividualAlleles(child, FamilyMember.CHILD); } - else if(mother == Genotype.Type.NO_CALL || mother == Genotype.Type.UNAVAILABLE){ + else if(!isPhasable(mother)){ phaseSingleIndividualAlleles(mother, FamilyMember.MOTHER); - if(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE){ + if(!isPhasable(father)){ phaseSingleIndividualAlleles(father, FamilyMember.FATHER); phaseSingleIndividualAlleles(child, FamilyMember.CHILD); } else phasePairAlleles(father, child, FamilyMember.FATHER); } - else if(father == Genotype.Type.NO_CALL || father == Genotype.Type.UNAVAILABLE){ + else if(!isPhasable(father)){ phasePairAlleles(mother, child, FamilyMember.MOTHER); phaseSingleIndividualAlleles(father, FamilyMember.FATHER); } @@ -327,7 +327,7 @@ public class PhaseByTransmission extends RodWalker, HashMa //Note that only cases where a null/missing/unavailable genotype was passed in the first place can lead to a null/missing/unavailable //genotype so it is safe to return the original genotype in this case. //In addition, if the phasing confidence is 0, then return the unphased, original genotypes. - if(phredScoreTransmission ==0 || genotype == null || !phasedGenotype.isAvailable() || phasedGenotype.isNoCall()) + if(phredScoreTransmission ==0 || genotype == null || !isPhasable(genotype.getType())) return genotype; //Add the transmission probability From 7ac5cf8430e82822d008c35b99d37f35b151230e Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Wed, 16 Nov 2011 09:53:59 -0500 Subject: [PATCH 28/44] Getting rid of unsupported CountReadPairs walker in stable. Removal of remainder of pairs processing framework to follow in unstable. --- .../gatk/walkers/qc/CountPairsWalker.java | 140 ------------------ 1 file changed, 140 deletions(-) delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountPairsWalker.java diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountPairsWalker.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountPairsWalker.java deleted file mode 100644 index e770418c1..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/qc/CountPairsWalker.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2010. - * - * 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.walkers.qc; - -import net.sf.samtools.SAMRecord; -import org.broadinstitute.sting.commandline.Output; -import org.broadinstitute.sting.gatk.walkers.ReadPairWalker; -import org.broadinstitute.sting.utils.collections.ExpandingArrayList; - -import java.io.PrintStream; -import java.util.Collection; -import java.util.List; - -/** - * Counts the number of read pairs encountered in a file sorted in - * query name order. Breaks counts down by total pairs and number - * of paired reads. - * - * - *

Input

- *

- * One or more bam files. - *

- * - *

Output

- *

- * Number of pairs seen. - *

- * - *

Examples

- *
- * java -Xmx2g -jar GenomeAnalysisTK.jar \
- *   -R ref.fasta \
- *   -T CountPairs \
- *   -o output.txt \
- *   -I input.bam
- * 
- * - * @author mhanna - */ -public class CountPairsWalker extends ReadPairWalker { - @Output - private PrintStream out; - - /** - * How many reads are the first in a pair, based on flag 0x0040 from the SAM spec. - */ - private long firstOfPair = 0; - - /** - * How many reads are the second in a pair, based on flag 0x0080 from the SAM spec. - */ - private long secondOfPair = 0; - - /** - * A breakdown of the total number of reads seen with exactly the same read name. - */ - private List pairCountsByType = new ExpandingArrayList(); - - /** - * Maps a read pair to a given reduce of type MapType. Semantics determined by subclasser. - * @param reads Collection of reads having the same name. - * @return Semantics defined by implementer. - */ - @Override - public Integer map(Collection reads) { - if(pairCountsByType.get(reads.size()) != null) - pairCountsByType.set(reads.size(),pairCountsByType.get(reads.size())+1); - else - pairCountsByType.set(reads.size(),1L); - - for(SAMRecord read: reads) { - if(read.getFirstOfPairFlag()) firstOfPair++; - if(read.getSecondOfPairFlag()) secondOfPair++; - } - - return 1; - } - - /** - * No pairs at the beginning of a traversal. - * @return 0 always. - */ - @Override - public Long reduceInit() { - return 0L; - } - - /** - * Combine number of pairs seen in this iteration (always 1) with total number of pairs - * seen in previous iterations. - * @param value Pairs in this iteration (1), from the map function. - * @param sum Count of all pairs in prior iterations. - * @return All pairs encountered in previous iterations + all pairs encountered in this iteration (sum + 1). - */ - @Override - public Long reduce(Integer value, Long sum) { - return value + sum; - } - - /** - * Print summary statistics over the entire traversal. - * @param sum A count of all read pairs viewed. - */ - @Override - public void onTraversalDone(Long sum) { - out.printf("Total number of pairs : %d%n",sum); - out.printf("Total number of first reads in pair : %d%n",firstOfPair); - out.printf("Total number of second reads in pair: %d%n",secondOfPair); - for(int i = 1; i < pairCountsByType.size(); i++) { - if(pairCountsByType.get(i) == null) - continue; - out.printf("Pairs of size %d: %d%n",i,pairCountsByType.get(i)); - } - } - -} From e7d41d8d334221c12dc89a080a3ec55bb9cc4bfb Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Thu, 17 Nov 2011 12:00:28 -0500 Subject: [PATCH 30/44] Minor cleanup --- .../gatk/walkers/variantutils/SelectVariants.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java index 609593acc..b92b1cee0 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/variantutils/SelectVariants.java @@ -24,10 +24,8 @@ package org.broadinstitute.sting.gatk.walkers.variantutils; -import org.apache.poi.hpsf.Variant; import org.broadinstitute.sting.commandline.*; import org.broadinstitute.sting.gatk.arguments.StandardVariantContextInputArgumentCollection; -import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.codecs.vcf.*; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.text.XReadLines; @@ -275,8 +273,8 @@ public class SelectVariants extends RodWalker { private double MENDELIAN_VIOLATION_QUAL_THRESHOLD = 0; /** - * Variants are kept in memory to guarantee that exactly n variants will be chosen randomly, so use it only for a reasonable - * number of variants. Use --select_random_fraction for larger numbers of variants. + * Variants are kept in memory to guarantee that exactly n variants will be chosen randomly, so make sure you supply the program with enough memory + * given your input set. This option will NOT work well for large callsets; use --select_random_fraction for sets with a large numbers of variants. */ @Argument(fullName="select_random_number", shortName="number", doc="Selects a number of variants at random from the variant track", required=false) private int numRandom = 0; @@ -532,7 +530,7 @@ public class SelectVariants extends RodWalker { } } if (SELECT_RANDOM_NUMBER) { - randomlyAddVariant(++variantNumber, sub, ref.getBase()); + randomlyAddVariant(++variantNumber, sub); } else if (!SELECT_RANDOM_FRACTION || ( GenomeAnalysisEngine.getRandomGenerator().nextDouble() < fractionRandom)) { vcfWriter.add(sub); @@ -705,7 +703,7 @@ public class SelectVariants extends RodWalker { return sub; } - private void randomlyAddVariant(int rank, VariantContext vc, byte refBase) { + private void randomlyAddVariant(int rank, VariantContext vc) { if (nVariantsAdded < numRandom) variantArray[nVariantsAdded++] = new RandomVariantStructure(vc); From 16a021992bf51bb5bbac5a9ebb8f4206fd4c314c Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Thu, 17 Nov 2011 13:17:53 -0500 Subject: [PATCH 31/44] Updated header description for the INFO and FORMAT DP fields to be more accurate. --- .../walkers/annotator/DepthOfCoverage.java | 2 +- .../walkers/genotyper/UnifiedGenotyper.java | 2 +- .../VariantAnnotatorIntegrationTest.java | 22 ++++---- .../UnifiedGenotyperIntegrationTest.java | 50 +++++++++---------- 4 files changed, 37 insertions(+), 39 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/DepthOfCoverage.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/DepthOfCoverage.java index 8098de5b1..ab38b69cd 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/DepthOfCoverage.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/annotator/DepthOfCoverage.java @@ -49,5 +49,5 @@ public class DepthOfCoverage extends InfoFieldAnnotation implements StandardAnno public List getKeyNames() { return Arrays.asList(VCFConstants.DEPTH_KEY); } - public List getDescriptions() { return Arrays.asList(new VCFInfoHeaderLine(getKeyNames().get(0), 1, VCFHeaderLineType.Integer, "Filtered Depth")); } + public List getDescriptions() { return Arrays.asList(new VCFInfoHeaderLine(getKeyNames().get(0), 1, VCFHeaderLineType.Integer, "Approximate read depth; some reads may have been filtered")); } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyper.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyper.java index bdd4e2c65..369c2d0c6 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyper.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyper.java @@ -258,7 +258,7 @@ public class UnifiedGenotyper extends LocusWalker result = new HashSet(); result.add(new VCFFormatHeaderLine(VCFConstants.GENOTYPE_KEY, 1, VCFHeaderLineType.String, "Genotype")); result.add(new VCFFormatHeaderLine(VCFConstants.GENOTYPE_QUALITY_KEY, 1, VCFHeaderLineType.Float, "Genotype Quality")); - result.add(new VCFFormatHeaderLine(VCFConstants.DEPTH_KEY, 1, VCFHeaderLineType.Integer, "Read Depth (only filtered reads used for calling)")); + result.add(new VCFFormatHeaderLine(VCFConstants.DEPTH_KEY, 1, VCFHeaderLineType.Integer, "Approximate read depth (reads with MQ=255 or with bad mates are filtered)")); result.add(new VCFFormatHeaderLine(VCFConstants.PHRED_GENOTYPE_LIKELIHOODS_KEY, VCFHeaderLineCount.G, VCFHeaderLineType.Integer, "Normalized, Phred-scaled likelihoods for genotypes as defined in the VCF specification")); return result; diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java index 919e3d9bd..3bfb81dd0 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/annotator/VariantAnnotatorIntegrationTest.java @@ -32,7 +32,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testHasAnnotsAsking1() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample2.vcf -I " + validationDataLocation + "low_coverage_CEU.chr1.10k-11k.bam -L 1:10,020,000-10,021,000", 1, - Arrays.asList("8e7de435105499cd71ffc099e268a83e")); + Arrays.asList("9beb795536e95954f810835c6058f2ad")); executeTest("test file has annotations, asking for annotations, #1", spec); } @@ -40,7 +40,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testHasAnnotsAsking2() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample3.vcf -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -L 1:10,000,000-10,050,000", 1, - Arrays.asList("64b6804cb1e27826e3a47089349be581")); + Arrays.asList("2977bb30c8b84a5f4094fe6090658561")); executeTest("test file has annotations, asking for annotations, #2", spec); } @@ -64,7 +64,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testNoAnnotsAsking1() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample2empty.vcf -I " + validationDataLocation + "low_coverage_CEU.chr1.10k-11k.bam -L 1:10,020,000-10,021,000", 1, - Arrays.asList("fd1ffb669800c2e07df1e2719aa38e49")); + Arrays.asList("49d989f467b8d6d8f98f7c1b67cd4a05")); executeTest("test file doesn't have annotations, asking for annotations, #1", spec); } @@ -72,7 +72,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testNoAnnotsAsking2() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample3empty.vcf -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -L 1:10,000,000-10,050,000", 1, - Arrays.asList("09f8e840770a9411ff77508e0ed0837f")); + Arrays.asList("0948cd1dba7d61f283cc4cf2a7757d92")); executeTest("test file doesn't have annotations, asking for annotations, #2", spec); } @@ -80,7 +80,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testExcludeAnnotations() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard -XA FisherStrand -XA ReadPosRankSumTest --variant:VCF3 " + validationDataLocation + "vcfexample2empty.vcf -I " + validationDataLocation + "low_coverage_CEU.chr1.10k-11k.bam -L 1:10,020,000-10,021,000", 1, - Arrays.asList("b49fe03aa4b675db80a9db38a3552c95")); + Arrays.asList("33062eccd6eb73bc49440365430454c4")); executeTest("test exclude annotations", spec); } @@ -88,7 +88,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testOverwritingHeader() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard --variant " + validationDataLocation + "vcfexample4.vcf -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -L 1:10,001,292", 1, - Arrays.asList("78d2c19f8107d865970dbaf3e12edd92")); + Arrays.asList("062155edec46a8c52243475fbf3a2943")); executeTest("test overwriting header", spec); } @@ -96,7 +96,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testNoReads() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " -G Standard --variant " + validationDataLocation + "vcfexample3empty.vcf -L " + validationDataLocation + "vcfexample3empty.vcf", 1, - Arrays.asList("16e3a1403fc376320d7c69492cad9345")); + Arrays.asList("06635f2dd91b539bfbce9bf7914d8e43")); executeTest("not passing it any reads", spec); } @@ -104,7 +104,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testDBTagWithDbsnp() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " --dbsnp " + b36dbSNP129 + " -G Standard --variant " + validationDataLocation + "vcfexample3empty.vcf -L " + validationDataLocation + "vcfexample3empty.vcf", 1, - Arrays.asList("3da8ca2b6bdaf6e92d94a8c77a71313d")); + Arrays.asList("820eeba1f6e3a0758a69d937c524a38e")); executeTest("getting DB tag with dbSNP", spec); } @@ -112,7 +112,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testDBTagWithHapMap() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " --comp:H3 " + validationDataLocation + "fakeHM3.vcf -G Standard --variant " + validationDataLocation + "vcfexample3empty.vcf -L " + validationDataLocation + "vcfexample3empty.vcf", 1, - Arrays.asList("1bc01c5b3bd0b7aef75230310c3ce688")); + Arrays.asList("31cc2ce157dd20771418c08d6b3be1fa")); executeTest("getting DB tag with HM3", spec); } @@ -120,7 +120,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testUsingExpression() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " --resource:foo " + validationDataLocation + "targetAnnotations.vcf -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample3empty.vcf -E foo.AF -L " + validationDataLocation + "vcfexample3empty.vcf", 1, - Arrays.asList("ae30a1ac7bfbc3d22a327f8b689cad31")); + Arrays.asList("074865f8f8c0ca7bfd58681f396c49e9")); executeTest("using expression", spec); } @@ -128,7 +128,7 @@ public class VariantAnnotatorIntegrationTest extends WalkerTest { public void testUsingExpressionWithID() { WalkerTestSpec spec = new WalkerTestSpec( baseTestString() + " --resource:foo " + validationDataLocation + "targetAnnotations.vcf -G Standard --variant:VCF3 " + validationDataLocation + "vcfexample3empty.vcf -E foo.ID -L " + validationDataLocation + "vcfexample3empty.vcf", 1, - Arrays.asList("1b4921085b26cbfe07d53b7c947de1e5")); + Arrays.asList("97b26db8135d083566fb585a677fbe8a")); executeTest("using expression with ID", spec); } diff --git a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java index b80f214b1..0197f94e5 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/walkers/genotyper/UnifiedGenotyperIntegrationTest.java @@ -5,10 +5,8 @@ import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; import org.broadinstitute.sting.utils.exceptions.UserException; import org.testng.annotations.Test; -import java.io.File; import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; // ********************************************************************************** // @@ -30,7 +28,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testMultiSamplePilot1() { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( baseCommand + " -I " + validationDataLocation + "low_coverage_CEU.chr1.10k-11k.bam -o %s -L 1:10,022,000-10,025,000", 1, - Arrays.asList("b27939251539439a382538e507e03507")); + Arrays.asList("364a6112e6034571a896d4c68b6ff02a")); executeTest("test MultiSample Pilot1", spec); } @@ -38,12 +36,12 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testWithAllelesPassedIn() { WalkerTest.WalkerTestSpec spec1 = new WalkerTest.WalkerTestSpec( baseCommand + " --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "allelesForUG.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,025,000", 1, - Arrays.asList("8de2602679ffc92388da0b6cb4325ef6")); + Arrays.asList("6cce55734ecd1a56c33f0ecba67d1258")); executeTest("test MultiSample Pilot2 with alleles passed in", spec1); WalkerTest.WalkerTestSpec spec2 = new WalkerTest.WalkerTestSpec( baseCommand + " --output_mode EMIT_ALL_SITES --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "allelesForUG.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,025,000", 1, - Arrays.asList("6458f3b8fe4954e2ffc2af972aaab19e")); + Arrays.asList("b2a00130a4b6f9a3c31288a236f5d159")); executeTest("test MultiSample Pilot2 with alleles passed in and emitting all sites", spec2); } @@ -51,7 +49,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testSingleSamplePilot2() { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( baseCommand + " -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -o %s -L 1:10,000,000-10,100,000", 1, - Arrays.asList("6762b72ae60155ad71738d7c76b80e4b")); + Arrays.asList("3ccce5d909f8f128e496f6841836e5f7")); executeTest("test SingleSample Pilot2", spec); } @@ -61,7 +59,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { // // -------------------------------------------------------------------------------------------------------------- - private final static String COMPRESSED_OUTPUT_MD5 = "bc71dba7bbdb23e7d5cc60461fdd897b"; + private final static String COMPRESSED_OUTPUT_MD5 = "890143b366050e78d6c6ba6b2c6b6864"; @Test public void testCompressedOutput() { @@ -82,7 +80,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { // Note that we need to turn off any randomization for this to work, so no downsampling and no annotations - String md5 = "b9504e446b9313559c3ed97add7e8dc1"; + String md5 = "95614280c565ad90f8c000376fef822c"; WalkerTest.WalkerTestSpec spec1 = new WalkerTest.WalkerTestSpec( baseCommand + " -dt NONE -G none -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -o %s -L 1:10,000,000-10,075,000", 1, @@ -113,8 +111,8 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { @Test public void testCallingParameters() { HashMap e = new HashMap(); - e.put( "--min_base_quality_score 26", "bb3f294eab3e2cf52c70e63b23aac5ee" ); - e.put( "--computeSLOD", "eb34979efaadba1e34bd82bcacf5c722" ); + e.put( "--min_base_quality_score 26", "7acb1a5aee5fdadb0cc0ea07a212efc6" ); + e.put( "--computeSLOD", "e9d23a08472e4e27b4f25e844f5bad57" ); for ( Map.Entry entry : e.entrySet() ) { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( @@ -127,9 +125,9 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { @Test public void testOutputParameter() { HashMap e = new HashMap(); - e.put( "-sites_only", "d40114aa201aa33ff5f174f15b6b73af" ); - e.put( "--output_mode EMIT_ALL_CONFIDENT_SITES", "3c681b053fd2280f3c42041d24243752" ); - e.put( "--output_mode EMIT_ALL_SITES", "eafa6d71c5ecd64dfee5d7a3f60e392e" ); + e.put( "-sites_only", "44f3b5b40e6ad44486cddfdb7e0bfcd8" ); + e.put( "--output_mode EMIT_ALL_CONFIDENT_SITES", "94e53320f14c5ff29d62f68d36b46fcd" ); + e.put( "--output_mode EMIT_ALL_SITES", "73ad1cc41786b12c5f0e6f3e9ec2b728" ); for ( Map.Entry entry : e.entrySet() ) { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( @@ -143,12 +141,12 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { public void testConfidence() { WalkerTest.WalkerTestSpec spec1 = new WalkerTest.WalkerTestSpec( baseCommand + " -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -o %s -L 1:10,000,000-10,010,000 -stand_call_conf 10 ", 1, - Arrays.asList("c71ca370947739cb7d87b59452be7a07")); + Arrays.asList("902327e8a45fe585c8dfd1a7c4fcf60f")); executeTest("test confidence 1", spec1); WalkerTest.WalkerTestSpec spec2 = new WalkerTest.WalkerTestSpec( baseCommand + " -I " + validationDataLocation + "NA12878.1kg.p2.chr1_10mb_11_mb.SLX.bam -o %s -L 1:10,000,000-10,010,000 -stand_emit_conf 10 ", 1, - Arrays.asList("1c0a599d475cc7d5e745df6e9b6c0d29")); + Arrays.asList("9ef66764a0ecfe89c3e4e1a93a69e349")); executeTest("test confidence 2", spec2); } @@ -160,8 +158,8 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { @Test public void testHeterozyosity() { HashMap e = new HashMap(); - e.put( 0.01, "f84da90c310367bd51f2ab6e346fa3d8" ); - e.put( 1.0 / 1850, "5791e7fef40d4412b6d8f84e0a809c6c" ); + e.put( 0.01, "46243ecc2b9dc716f48ea280c9bb7e72" ); + e.put( 1.0 / 1850, "6b2a59dbc76984db6d4d6d6b5ee5d62c" ); for ( Map.Entry entry : e.entrySet() ) { WalkerTest.WalkerTestSpec spec = new WalkerTest.WalkerTestSpec( @@ -185,7 +183,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -o %s" + " -L 1:10,000,000-10,100,000", 1, - Arrays.asList("9cc9538ac83770e12bd0830d285bfbd0")); + Arrays.asList("f0fbe472f155baf594b1eeb58166edef")); executeTest(String.format("test multiple technologies"), spec); } @@ -204,7 +202,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -L 1:10,000,000-10,100,000" + " -baq CALCULATE_AS_NECESSARY", 1, - Arrays.asList("eaf8043edb46dfbe9f97ae03baa797ed")); + Arrays.asList("8c87c749a7bb5a76ed8504d4ec254272")); executeTest(String.format("test calling with BAQ"), spec); } @@ -223,7 +221,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -o %s" + " -L 1:10,000,000-10,500,000", 1, - Arrays.asList("eeba568272f9b42d5450da75c7cc6d2d")); + Arrays.asList("a64d2e65b5927260e4ce0d948760cc5c")); executeTest(String.format("test indel caller in SLX"), spec); } @@ -238,7 +236,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -minIndelCnt 1" + " -L 1:10,000,000-10,100,000", 1, - Arrays.asList("5fe98ee853586dc9db58f0bc97daea63")); + Arrays.asList("2ad52c2e75b3ffbfd8f03237c444e8e6")); executeTest(String.format("test indel caller in SLX with low min allele count"), spec); } @@ -251,7 +249,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { " -o %s" + " -L 1:10,000,000-10,500,000", 1, - Arrays.asList("19ff9bd3139480bdf79dcbf117cf2b24")); + Arrays.asList("69107157632714150fc068d412e31939")); executeTest(String.format("test indel calling, multiple technologies"), spec); } @@ -261,7 +259,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { WalkerTest.WalkerTestSpec spec1 = new WalkerTest.WalkerTestSpec( baseCommandIndels + " --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "indelAllelesForUG.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,100,000", 1, - Arrays.asList("118918f2e9e56a3cfc5ccb2856d529c8")); + Arrays.asList("84f065e115004e4c9d9d62f1bf6b4f44")); executeTest("test MultiSample Pilot2 indels with alleles passed in", spec1); } @@ -271,7 +269,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { baseCommandIndels + " --output_mode EMIT_ALL_SITES --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "indelAllelesForUG.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,100,000", 1, - Arrays.asList("a20799237accd52c1b8c2ac096309c8f")); + Arrays.asList("7ea8372e4074f90ec942f964e1863351")); executeTest("test MultiSample Pilot2 indels with alleles passed in and emitting all sites", spec2); } @@ -281,7 +279,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { WalkerTest.WalkerTestSpec spec3 = new WalkerTest.WalkerTestSpec( baseCommandIndels + " --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "ALL.wgs.union_v2.20101123.indels.sites.vcf -I " + validationDataLocation + "pilot2_daughters.chr20.10k-11k.bam -o %s -L 20:10,000,000-10,080,000", 1, - Arrays.asList("18ef8181157b4ac3eb8492f538467f92")); + Arrays.asList("94afa8c011b47af7323c821bf19106b4")); executeTest("test MultiSample Pilot2 indels with complicated records", spec3); } @@ -290,7 +288,7 @@ public class UnifiedGenotyperIntegrationTest extends WalkerTest { WalkerTest.WalkerTestSpec spec4 = new WalkerTest.WalkerTestSpec( baseCommandIndelsb37 + " --genotyping_mode GENOTYPE_GIVEN_ALLELES -alleles " + validationDataLocation + "ALL.wgs.union_v2_chr20_100_110K.20101123.indels.sites.vcf -I " + validationDataLocation + "phase1_GBR_realigned.chr20.100K-110K.bam -o %s -L 20:100,000-110,000", 1, - Arrays.asList("ad884e511a751b05e64db5314314365a")); + Arrays.asList("1e02f57fafaa41db71c531eb25e148e1")); executeTest("test MultiSample 1000G Phase1 indels with complicated records emitting all sites", spec4); } From c50274e02e8bd381a2f7995a77b22f0ac8e0b00b Mon Sep 17 00:00:00 2001 From: Khalid Shakir Date: Thu, 17 Nov 2011 13:53:46 -0500 Subject: [PATCH 32/44] During flanking interval creation merging overlapping flanks so that on scatter the list doesn't accidentally genotype the same site twice. Moved flanking interval utilies to IntervalUtils with UnitTests. --- .../sting/utils/GenomeLocParser.java | 50 ++++ .../sting/utils/interval/IntervalUtils.java | 119 ++++++++-- .../sting/utils/GenomeLocParserUnitTest.java | 104 +++++++- .../utils/interval/IntervalUtilsUnitTest.java | 223 +++++++++++++++++- .../gatk/WriteFlankingIntervalsFunction.scala | 48 ++++ .../ipf/intervals/ExpandIntervals.scala | 135 ----------- .../ipf/intervals/IntersectIntervals.scala | 70 ------ 7 files changed, 519 insertions(+), 230 deletions(-) create mode 100644 public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/WriteFlankingIntervalsFunction.scala delete mode 100755 public/scala/src/org/broadinstitute/sting/queue/library/ipf/intervals/ExpandIntervals.scala delete mode 100755 public/scala/src/org/broadinstitute/sting/queue/library/ipf/intervals/IntersectIntervals.scala diff --git a/public/java/src/org/broadinstitute/sting/utils/GenomeLocParser.java b/public/java/src/org/broadinstitute/sting/utils/GenomeLocParser.java index e10bcbaa0..8cba183da 100644 --- a/public/java/src/org/broadinstitute/sting/utils/GenomeLocParser.java +++ b/public/java/src/org/broadinstitute/sting/utils/GenomeLocParser.java @@ -554,4 +554,54 @@ public class GenomeLocParser { return createGenomeLoc(contigName,contig.getSequenceIndex(),1,contig.getSequenceLength(), true); } + /** + * Creates a loc to the left (starting at the loc start + 1) of maxBasePairs size. + * @param loc The original loc + * @param maxBasePairs The maximum number of basePairs + * @return The contiguous loc of up to maxBasePairs length or null if the loc is already at the start of the contig. + */ + @Requires({"loc != null", "maxBasePairs > 0"}) + public GenomeLoc createGenomeLocAtStart(GenomeLoc loc, int maxBasePairs) { + if (GenomeLoc.isUnmapped(loc)) + return null; + String contigName = loc.getContig(); + SAMSequenceRecord contig = contigInfo.getSequence(contigName); + int contigIndex = contig.getSequenceIndex(); + + int start = loc.getStart() - maxBasePairs; + int stop = loc.getStart() - 1; + + if (start < 1) + start = 1; + if (stop < 1) + return null; + + return createGenomeLoc(contigName, contigIndex, start, stop, true); + } + + /** + * Creates a loc to the right (starting at the loc stop + 1) of maxBasePairs size. + * @param loc The original loc + * @param maxBasePairs The maximum number of basePairs + * @return The contiguous loc of up to maxBasePairs length or null if the loc is already at the end of the contig. + */ + @Requires({"loc != null", "maxBasePairs > 0"}) + public GenomeLoc createGenomeLocAtStop(GenomeLoc loc, int maxBasePairs) { + if (GenomeLoc.isUnmapped(loc)) + return null; + String contigName = loc.getContig(); + SAMSequenceRecord contig = contigInfo.getSequence(contigName); + int contigIndex = contig.getSequenceIndex(); + int contigLength = contig.getSequenceLength(); + + int start = loc.getStop() + 1; + int stop = loc.getStop() + maxBasePairs; + + if (start > contigLength) + return null; + if (stop > contigLength) + stop = contigLength; + + return createGenomeLoc(contigName, contigIndex, start, stop, true); + } } diff --git a/public/java/src/org/broadinstitute/sting/utils/interval/IntervalUtils.java b/public/java/src/org/broadinstitute/sting/utils/interval/IntervalUtils.java index f0e164c87..159b145a0 100644 --- a/public/java/src/org/broadinstitute/sting/utils/interval/IntervalUtils.java +++ b/public/java/src/org/broadinstitute/sting/utils/interval/IntervalUtils.java @@ -233,8 +233,12 @@ public class IntervalUtils { * * Returns a null string if there are no differences, otherwise returns a string describing the difference * (useful for UnitTests). Assumes both lists are sorted + * + * @param masterArg sorted master genome locs + * @param testArg sorted test genome locs + * @return null string if there are no difference, otherwise a string describing the difference */ - public static final String equateIntervals(List masterArg, List testArg) { + public static String equateIntervals(List masterArg, List testArg) { LinkedList master = new LinkedList(masterArg); LinkedList test = new LinkedList(testArg); @@ -317,23 +321,6 @@ public class IntervalUtils { return lengths; } - /** - * Counts the number of interval files an interval list can be split into using scatterIntervalArguments. - * @param locs The genome locs. - * @return The maximum number of parts the intervals can be split into. - */ - public static int countContigIntervals(List locs) { - int maxFiles = 0; - String contig = null; - for (GenomeLoc loc: locs) { - if (contig == null || !contig.equals(loc.getContig())) { - maxFiles++; - contig = loc.getContig(); - } - } - return maxFiles; - } - /** * Splits an interval list into multiple files. * @param fileHeader The sam file header. @@ -373,7 +360,6 @@ public class IntervalUtils { * @return A list of lists of genome locs, split according to splits */ public static List> splitIntervalsToSubLists(List locs, List splits) { - int locIndex = 1; int start = 0; List> sublists = new ArrayList>(splits.size()); for (Integer stop: splits) { @@ -465,7 +451,7 @@ public class IntervalUtils { @Requires({"remaining != null", "!remaining.isEmpty()", "idealSplitSize > 0"}) @Ensures({"result != null"}) - final static SplitLocusRecursive splitLocusIntervals1(LinkedList remaining, long idealSplitSize) { + static SplitLocusRecursive splitLocusIntervals1(LinkedList remaining, long idealSplitSize) { final List split = new ArrayList(); long size = 0; @@ -579,10 +565,101 @@ public class IntervalUtils { } } - public static final long intervalSize(final List locs) { + public static long intervalSize(final List locs) { long size = 0; for ( final GenomeLoc loc : locs ) size += loc.size(); return size; } + + public static void writeFlankingIntervals(File reference, File inputIntervals, File flankingIntervals, int basePairs) { + ReferenceDataSource referenceDataSource = new ReferenceDataSource(reference); + GenomeLocParser parser = new GenomeLocParser(referenceDataSource.getReference()); + List originalList = intervalFileToList(parser, inputIntervals.getAbsolutePath()); + + if (originalList.isEmpty()) + throw new UserException.MalformedFile(inputIntervals, "File contains no intervals"); + + List flankingList = getFlankingIntervals(parser, originalList, basePairs); + + if (flankingList.isEmpty()) + throw new UserException.MalformedFile(inputIntervals, "Unable to produce any flanks for the intervals"); + + SAMFileHeader samFileHeader = new SAMFileHeader(); + samFileHeader.setSequenceDictionary(referenceDataSource.getReference().getSequenceDictionary()); + IntervalList intervalList = new IntervalList(samFileHeader); + int i = 0; + for (GenomeLoc loc: flankingList) + intervalList.add(toInterval(loc, ++i)); + intervalList.write(flankingIntervals); + } + + /** + * Returns a list of intervals between the passed int locs. Does not extend UNMAPPED locs. + * @param parser A genome loc parser for creating the new intervals + * @param locs Original genome locs + * @param basePairs Number of base pairs on each side of loc + * @return The list of intervals between the locs + */ + public static List getFlankingIntervals(final GenomeLocParser parser, final List locs, final int basePairs) { + List sorted = sortAndMergeIntervals(parser, locs, IntervalMergingRule.ALL).toList(); + + if (sorted.size() == 0) + return Collections.emptyList(); + + LinkedHashMap> locsByContig = splitByContig(sorted); + List expanded = new ArrayList(); + for (String contig: locsByContig.keySet()) { + List contigLocs = locsByContig.get(contig); + int contigLocsSize = contigLocs.size(); + + GenomeLoc startLoc, stopLoc; + + // Create loc at start of the list + startLoc = parser.createGenomeLocAtStart(contigLocs.get(0), basePairs); + if (startLoc != null) + expanded.add(startLoc); + + // Create locs between each loc[i] and loc[i+1] + for (int i = 0; i < contigLocsSize - 1; i++) { + stopLoc = parser.createGenomeLocAtStop(contigLocs.get(i), basePairs); + startLoc = parser.createGenomeLocAtStart(contigLocs.get(i + 1), basePairs); + if (stopLoc.getStop() + 1 >= startLoc.getStart()) { + // NOTE: This is different than GenomeLoc.merge() + // merge() returns a loc which covers the entire range of stop and start, + // possibly returning positions inside loc(i) or loc(i+1) + // We want to make sure that the start of the stopLoc is used, and the stop of the startLoc + GenomeLoc merged = parser.createGenomeLoc( + stopLoc.getContig(), stopLoc.getStart(), startLoc.getStop()); + expanded.add(merged); + } else { + expanded.add(stopLoc); + expanded.add(startLoc); + } + } + + // Create loc at the end of the list + stopLoc = parser.createGenomeLocAtStop(contigLocs.get(contigLocsSize - 1), basePairs); + if (stopLoc != null) + expanded.add(stopLoc); + } + return expanded; + } + + private static LinkedHashMap> splitByContig(List sorted) { + LinkedHashMap> splits = new LinkedHashMap>(); + GenomeLoc last = null; + List contigLocs = null; + for (GenomeLoc loc: sorted) { + if (GenomeLoc.isUnmapped(loc)) + continue; + if (last == null || !last.onSameContig(loc)) { + contigLocs = new ArrayList(); + splits.put(loc.getContig(), contigLocs); + } + contigLocs.add(loc); + last = loc; + } + return splits; + } } diff --git a/public/java/test/org/broadinstitute/sting/utils/GenomeLocParserUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/GenomeLocParserUnitTest.java index f1f849bf5..e9f138a0e 100644 --- a/public/java/test/org/broadinstitute/sting/utils/GenomeLocParserUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/GenomeLocParserUnitTest.java @@ -2,7 +2,6 @@ package org.broadinstitute.sting.utils; import net.sf.samtools.SAMFileHeader; -import net.sf.samtools.SAMSequenceDictionary; import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; @@ -11,6 +10,7 @@ import org.broadinstitute.sting.utils.sam.ArtificialSAMUtils; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; /** @@ -36,7 +36,6 @@ public class GenomeLocParserUnitTest extends BaseTest { @Test public void testGetContigIndexValid() { - SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 10); assertEquals(genomeLocParser.getContigIndex("chr1"), 0); // should be in the reference } @@ -67,7 +66,6 @@ public class GenomeLocParserUnitTest extends BaseTest { @Test public void testGetContigInfoKnownContig() { - SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, 10); assertEquals(0, "chr1".compareTo(genomeLocParser.getContigInfo("chr1").getSequenceName())); // should be in the reference } @@ -191,4 +189,104 @@ public class GenomeLocParserUnitTest extends BaseTest { assertTrue(!genomeLocParser.isValidGenomeLoc("chr1",1,-2)); // bad stop assertTrue(!genomeLocParser.isValidGenomeLoc("chr1",10,11)); // bad start, past end } + + private static class FlankingGenomeLocTestData extends TestDataProvider { + final GenomeLocParser parser; + final int basePairs; + final GenomeLoc original, flankStart, flankStop; + + private FlankingGenomeLocTestData(String name, GenomeLocParser parser, int basePairs, String original, String flankStart, String flankStop) { + super(FlankingGenomeLocTestData.class, name); + this.parser = parser; + this.basePairs = basePairs; + this.original = parse(parser, original); + this.flankStart = flankStart == null ? null : parse(parser, flankStart); + this.flankStop = flankStop == null ? null : parse(parser, flankStop); + } + + private static GenomeLoc parse(GenomeLocParser parser, String str) { + return "unmapped".equals(str) ? GenomeLoc.UNMAPPED : parser.parseGenomeLoc(str); + } + } + + @DataProvider(name = "flankingGenomeLocs") + public Object[][] getFlankingGenomeLocs() { + int contigLength = 10000; + SAMFileHeader header = ArtificialSAMUtils.createArtificialSamHeader(1, 1, contigLength); + GenomeLocParser parser = new GenomeLocParser(header.getSequenceDictionary()); + + new FlankingGenomeLocTestData("atStartBase1", parser, 1, + "chr1:1", null, "chr1:2"); + + new FlankingGenomeLocTestData("atStartBase50", parser, 50, + "chr1:1", null, "chr1:2-51"); + + new FlankingGenomeLocTestData("atStartRange50", parser, 50, + "chr1:1-10", null, "chr1:11-60"); + + new FlankingGenomeLocTestData("atEndBase1", parser, 1, + "chr1:" + contigLength, "chr1:" + (contigLength - 1), null); + + new FlankingGenomeLocTestData("atEndBase50", parser, 50, + "chr1:" + contigLength, String.format("chr1:%d-%d", contigLength - 50, contigLength - 1), null); + + new FlankingGenomeLocTestData("atEndRange50", parser, 50, + String.format("chr1:%d-%d", contigLength - 10, contigLength), + String.format("chr1:%d-%d", contigLength - 60, contigLength - 11), + null); + + new FlankingGenomeLocTestData("nearStartBase1", parser, 1, + "chr1:2", "chr1:1", "chr1:3"); + + new FlankingGenomeLocTestData("nearStartRange50", parser, 50, + "chr1:21-30", "chr1:1-20", "chr1:31-80"); + + new FlankingGenomeLocTestData("nearEndBase1", parser, 1, + "chr1:" + (contigLength - 1), "chr1:" + (contigLength - 2), "chr1:" + contigLength); + + new FlankingGenomeLocTestData("nearEndRange50", parser, 50, + String.format("chr1:%d-%d", contigLength - 30, contigLength - 21), + String.format("chr1:%d-%d", contigLength - 80, contigLength - 31), + String.format("chr1:%d-%d", contigLength - 20, contigLength)); + + new FlankingGenomeLocTestData("beyondStartBase1", parser, 1, + "chr1:3", "chr1:2", "chr1:4"); + + new FlankingGenomeLocTestData("beyondStartRange50", parser, 50, + "chr1:101-200", "chr1:51-100", "chr1:201-250"); + + new FlankingGenomeLocTestData("beyondEndBase1", parser, 1, + "chr1:" + (contigLength - 3), + "chr1:" + (contigLength - 4), + "chr1:" + (contigLength - 2)); + + new FlankingGenomeLocTestData("beyondEndRange50", parser, 50, + String.format("chr1:%d-%d", contigLength - 200, contigLength - 101), + String.format("chr1:%d-%d", contigLength - 250, contigLength - 201), + String.format("chr1:%d-%d", contigLength - 100, contigLength - 51)); + + new FlankingGenomeLocTestData("unmapped", parser, 50, + "unmapped", null, null); + + new FlankingGenomeLocTestData("fullContig", parser, 50, + "chr1", null, null); + + return FlankingGenomeLocTestData.getTests(FlankingGenomeLocTestData.class); + } + + @Test(dataProvider = "flankingGenomeLocs") + public void testCreateGenomeLocAtStart(FlankingGenomeLocTestData data) { + GenomeLoc actual = data.parser.createGenomeLocAtStart(data.original, data.basePairs); + String description = String.format("%n name: %s%n original: %s%n actual: %s%n expected: %s%n", + data.toString(), data.original, actual, data.flankStart); + assertEquals(actual, data.flankStart, description); + } + + @Test(dataProvider = "flankingGenomeLocs") + public void testCreateGenomeLocAtStop(FlankingGenomeLocTestData data) { + GenomeLoc actual = data.parser.createGenomeLocAtStop(data.original, data.basePairs); + String description = String.format("%n name: %s%n original: %s%n actual: %s%n expected: %s%n", + data.toString(), data.original, actual, data.flankStop); + assertEquals(actual, data.flankStop, description); + } } diff --git a/public/java/test/org/broadinstitute/sting/utils/interval/IntervalUtilsUnitTest.java b/public/java/test/org/broadinstitute/sting/utils/interval/IntervalUtilsUnitTest.java index 9c3b905c2..03d33d2c5 100644 --- a/public/java/test/org/broadinstitute/sting/utils/interval/IntervalUtilsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/utils/interval/IntervalUtilsUnitTest.java @@ -1,8 +1,8 @@ package org.broadinstitute.sting.utils.interval; import net.sf.picard.reference.ReferenceSequenceFile; -import net.sf.picard.util.IntervalUtil; import net.sf.samtools.SAMFileHeader; +import org.apache.commons.io.FileUtils; import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.gatk.datasources.reference.ReferenceDataSource; import org.broadinstitute.sting.utils.GenomeLocSortedSet; @@ -762,4 +762,225 @@ public class IntervalUtilsUnitTest extends BaseTest { List merged = IntervalUtils.mergeIntervalLocations(locs, IntervalMergingRule.ALL); Assert.assertEquals(merged.size(), 1); } + + /* + Split into tests that can be written to files and tested by writeFlankingIntervals, + and lists that cannot but are still handled by getFlankingIntervals. + */ + private static abstract class FlankingIntervalsTestData extends TestDataProvider { + final public File referenceFile; + final public GenomeLocParser parser; + final int basePairs; + final List original; + final List expected; + + protected FlankingIntervalsTestData(Class clazz, String name, File referenceFile, GenomeLocParser parser, + int basePairs, List original, List expected) { + super(clazz, name); + this.referenceFile = referenceFile; + this.parser = parser; + this.basePairs = basePairs; + this.original = parse(parser, original); + this.expected = parse(parser, expected); + } + + private static List parse(GenomeLocParser parser, List locs) { + List parsed = new ArrayList(); + for (String loc: locs) + parsed.add("unmapped".equals(loc) ? GenomeLoc.UNMAPPED : parser.parseGenomeLoc(loc)); + return parsed; + } + } + + private static class FlankingIntervalsFile extends FlankingIntervalsTestData { + public FlankingIntervalsFile(String name, File referenceFile, GenomeLocParser parser, + int basePairs, List original, List expected) { + super(FlankingIntervalsFile.class, name, referenceFile, parser, basePairs, original, expected); + } + } + + private static class FlankingIntervalsList extends FlankingIntervalsTestData { + public FlankingIntervalsList(String name, File referenceFile, GenomeLocParser parser, + int basePairs, List original, List expected) { + super(FlankingIntervalsList.class, name, referenceFile, parser, basePairs, original, expected); + } + } + + /* Intervals where the original and the flanks can be written to files. */ + @DataProvider(name = "flankingIntervalsFiles") + public Object[][] getFlankingIntervalsFiles() { + File hg19ReferenceFile = new File(BaseTest.hg19Reference); + int hg19Length1 = hg19GenomeLocParser.getContigInfo("1").getSequenceLength(); + + new FlankingIntervalsFile("atStartBase1", hg19ReferenceFile, hg19GenomeLocParser, 1, + Arrays.asList("1:1"), + Arrays.asList("1:2")); + + new FlankingIntervalsFile("atStartBase50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:1"), + Arrays.asList("1:2-51")); + + new FlankingIntervalsFile("atStartRange50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:1-10"), + Arrays.asList("1:11-60")); + + new FlankingIntervalsFile("atEndBase1", hg19ReferenceFile, hg19GenomeLocParser, 1, + Arrays.asList("1:" + hg19Length1), + Arrays.asList("1:" + (hg19Length1 - 1))); + + new FlankingIntervalsFile("atEndBase50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:" + hg19Length1), + Arrays.asList(String.format("1:%d-%d", hg19Length1 - 50, hg19Length1 - 1))); + + new FlankingIntervalsFile("atEndRange50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList(String.format("1:%d-%d", hg19Length1 - 10, hg19Length1)), + Arrays.asList(String.format("1:%d-%d", hg19Length1 - 60, hg19Length1 - 11))); + + new FlankingIntervalsFile("nearStartBase1", hg19ReferenceFile, hg19GenomeLocParser, 1, + Arrays.asList("1:2"), + Arrays.asList("1:1", "1:3")); + + new FlankingIntervalsFile("nearStartRange50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:21-30"), + Arrays.asList("1:1-20", "1:31-80")); + + new FlankingIntervalsFile("nearEndBase1", hg19ReferenceFile, hg19GenomeLocParser, 1, + Arrays.asList("1:" + (hg19Length1 - 1)), + Arrays.asList("1:" + (hg19Length1 - 2), "1:" + hg19Length1)); + + new FlankingIntervalsFile("nearEndRange50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList(String.format("1:%d-%d", hg19Length1 - 30, hg19Length1 - 21)), + Arrays.asList( + String.format("1:%d-%d", hg19Length1 - 80, hg19Length1 - 31), + String.format("1:%d-%d", hg19Length1 - 20, hg19Length1))); + + new FlankingIntervalsFile("beyondStartBase1", hg19ReferenceFile, hg19GenomeLocParser, 1, + Arrays.asList("1:3"), + Arrays.asList("1:2", "1:4")); + + new FlankingIntervalsFile("beyondStartRange50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:101-200"), + Arrays.asList("1:51-100", "1:201-250")); + + new FlankingIntervalsFile("beyondEndBase1", hg19ReferenceFile, hg19GenomeLocParser, 1, + Arrays.asList("1:" + (hg19Length1 - 3)), + Arrays.asList("1:" + (hg19Length1 - 4), "1:" + (hg19Length1 - 2))); + + new FlankingIntervalsFile("beyondEndRange50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList(String.format("1:%d-%d", hg19Length1 - 200, hg19Length1 - 101)), + Arrays.asList( + String.format("1:%d-%d", hg19Length1 - 250, hg19Length1 - 201), + String.format("1:%d-%d", hg19Length1 - 100, hg19Length1 - 51))); + + new FlankingIntervalsFile("betweenFar50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:101-200", "1:401-500"), + Arrays.asList("1:51-100", "1:201-250", "1:351-400", "1:501-550")); + + new FlankingIntervalsFile("betweenSpan50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:101-200", "1:301-400"), + Arrays.asList("1:51-100", "1:201-300", "1:401-450")); + + new FlankingIntervalsFile("betweenOverlap50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:101-200", "1:271-400"), + Arrays.asList("1:51-100", "1:201-270", "1:401-450")); + + new FlankingIntervalsFile("betweenShort50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:101-200", "1:221-400"), + Arrays.asList("1:51-100", "1:201-220", "1:401-450")); + + new FlankingIntervalsFile("betweenNone50", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:101-200", "1:121-400"), + Arrays.asList("1:51-100", "1:401-450")); + + new FlankingIntervalsFile("twoContigs", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:101-200", "2:301-400"), + Arrays.asList("1:51-100", "1:201-250", "2:251-300", "2:401-450")); + + // Explicit testing a problematic agilent target pair + new FlankingIntervalsFile("badAgilent", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("2:74756257-74756411", "2:74756487-74756628"), + // wrong! ("2:74756206-74756256", "2:74756412-74756462", "2:74756436-74756486", "2:74756629-74756679") + Arrays.asList("2:74756207-74756256", "2:74756412-74756486", "2:74756629-74756678")); + + return TestDataProvider.getTests(FlankingIntervalsFile.class); + } + + /* Intervals where either the original and/or the flanks cannot be written to a file. */ + @DataProvider(name = "flankingIntervalsLists") + public Object[][] getFlankingIntervalsLists() { + File hg19ReferenceFile = new File(BaseTest.hg19Reference); + List empty = Collections.emptyList(); + + new FlankingIntervalsList("empty", hg19ReferenceFile, hg19GenomeLocParser, 50, + empty, + empty); + + new FlankingIntervalsList("unmapped", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("unmapped"), + empty); + + new FlankingIntervalsList("fullContig", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1"), + empty); + + new FlankingIntervalsList("fullContigs", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1", "2", "3"), + empty); + + new FlankingIntervalsList("betweenWithUnmapped", hg19ReferenceFile, hg19GenomeLocParser, 50, + Arrays.asList("1:101-200", "1:301-400", "unmapped"), + Arrays.asList("1:51-100", "1:201-300", "1:401-450")); + + return TestDataProvider.getTests(FlankingIntervalsList.class); + } + + @Test(dataProvider = "flankingIntervalsFiles") + public void testWriteFlankingIntervals(FlankingIntervalsTestData data) throws Exception { + File originalFile = createTempFile("original.", ".intervals"); + File flankingFile = createTempFile("flanking.", ".intervals"); + try { + List lines = new ArrayList(); + for (GenomeLoc loc: data.original) + lines.add(loc.toString()); + FileUtils.writeLines(originalFile, lines); + + IntervalUtils.writeFlankingIntervals(data.referenceFile, originalFile, flankingFile, data.basePairs); + + List actual = IntervalUtils.intervalFileToList(data.parser, flankingFile.getAbsolutePath()); + + String description = String.format("%n name: %s%n original: %s%n actual: %s%n expected: %s%n", + data.toString(), data.original, actual, data.expected); + Assert.assertEquals(actual, data.expected, description); + } finally { + FileUtils.deleteQuietly(originalFile); + FileUtils.deleteQuietly(flankingFile); + } + } + + @Test(dataProvider = "flankingIntervalsLists", expectedExceptions = UserException.class) + public void testWritingBadFlankingIntervals(FlankingIntervalsTestData data) throws Exception { + File originalFile = createTempFile("original.", ".intervals"); + File flankingFile = createTempFile("flanking.", ".intervals"); + try { + List lines = new ArrayList(); + for (GenomeLoc loc: data.original) + lines.add(loc.toString()); + FileUtils.writeLines(originalFile, lines); + + // Should throw a user exception on bad input if either the original + // intervals are empty or if the flanking intervals are empty + IntervalUtils.writeFlankingIntervals(data.referenceFile, originalFile, flankingFile, data.basePairs); + } finally { + FileUtils.deleteQuietly(originalFile); + FileUtils.deleteQuietly(flankingFile); + } + } + + @Test(dataProvider = "flankingIntervalsLists") + public void testGetFlankingIntervals(FlankingIntervalsTestData data) { + List actual = IntervalUtils.getFlankingIntervals(data.parser, data.original, data.basePairs); + String description = String.format("%n name: %s%n original: %s%n actual: %s%n expected: %s%n", + data.toString(), data.original, actual, data.expected); + Assert.assertEquals(actual, data.expected, description); + } } diff --git a/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/WriteFlankingIntervalsFunction.scala b/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/WriteFlankingIntervalsFunction.scala new file mode 100644 index 000000000..d90db0de4 --- /dev/null +++ b/public/scala/src/org/broadinstitute/sting/queue/extensions/gatk/WriteFlankingIntervalsFunction.scala @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2011, 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.queue.extensions.gatk + +import org.broadinstitute.sting.queue.function.InProcessFunction +import org.broadinstitute.sting.commandline.{Output, Argument, Input} +import java.io.File +import org.broadinstitute.sting.utils.interval.IntervalUtils + +class WriteFlankingIntervalsFunction extends InProcessFunction { + @Input(doc="The reference sequence") + var reference : File = _ + + @Input(doc="The interval list to flank") + var inputIntervals : File = _ + + @Output(doc="The output intervals file to write to") + var outputIntervals: File = _ + + @Argument(doc="Number of base pair to flank the input intervals") + var flankSize : Int = _ + + def run() { + IntervalUtils.writeFlankingIntervals(reference, inputIntervals, outputIntervals, flankSize) + } +} diff --git a/public/scala/src/org/broadinstitute/sting/queue/library/ipf/intervals/ExpandIntervals.scala b/public/scala/src/org/broadinstitute/sting/queue/library/ipf/intervals/ExpandIntervals.scala deleted file mode 100755 index 77eb3ccbc..000000000 --- a/public/scala/src/org/broadinstitute/sting/queue/library/ipf/intervals/ExpandIntervals.scala +++ /dev/null @@ -1,135 +0,0 @@ -package org.broadinstitute.sting.queue.library.ipf.intervals - -import org.broadinstitute.sting.queue.function.InProcessFunction -import org.broadinstitute.sting.commandline._ -import java.io.{PrintStream, File} -import collection.JavaConversions._ -import org.broadinstitute.sting.utils.text.XReadLines -import net.sf.picard.reference.FastaSequenceFile -import org.broadinstitute.sting.utils.{GenomeLoc, GenomeLocParser} -import collection.immutable.TreeSet - -// todo -- this is unsafe. Need to use a reference dictionary to ensure no off-contig targets are created -class ExpandIntervals(in : File, start: Int, size: Int, out: File, ref: File, ipType: String, opType: String) extends InProcessFunction { - @Input(doc="The interval list to expand") val inList : File = in - @Input(doc="The reference sequence") val refDict : File = ref - @Argument(doc="Number of basepair to start the expanded interval") val startInt : Int = start - @Argument(doc="Number of baispair to stop the expanded interval") val sizeInt : Int = size - @Output(doc="The output intervals file to write to") val outList : File = out - @Argument(doc="The output format for the intervals") val outTypeStr = opType - @Argument(doc="The input format for the intervals") val inTypeStr = ipType - - var output : PrintStream = _ - var parser : GenomeLocParser = _ - var xrl : XReadLines = _ - val outType = IntervalFormatType.convert(outTypeStr) - val inType = IntervalFormatType.convert(inTypeStr) - - var offsetIn : Int = 0 - var offsetOut : Int = 0 - - var first : Boolean = true - var lastTwo : (GenomeLoc,GenomeLoc) = _ - - var intervalCache : TreeSet[GenomeLoc] = _ - val LINES_TO_CACHE : Int = 1000 - - def run = { - output = new PrintStream(outList) - intervalCache = new TreeSet[GenomeLoc]()(new Ordering[GenomeLoc]{ - def compare(o1: GenomeLoc, o2: GenomeLoc) : Int = { o1.compareTo(o2) } - }) - parser = new GenomeLocParser(new FastaSequenceFile(ref,true)) - xrl = new XReadLines(inList) - offsetIn = if (isBed(inType)) 1 else 0 - offsetOut = if( isBed(outType)) 1 else 0 - var line : String = xrl.next - while ( line.startsWith("@") ) { - line = xrl.next - } - var prevLoc: GenomeLoc = null - var curLoc: GenomeLoc = null - var nextLoc : GenomeLoc = parseGenomeInterval(line) - var linesProcessed : Int = 1 - while ( prevLoc != null || curLoc != null || nextLoc != null ) { - prevLoc = curLoc - curLoc = nextLoc - nextLoc = if ( xrl.hasNext ) parseGenomeInterval(xrl.next) else null - if ( curLoc != null ) { - val left: GenomeLoc = refine(expandLeft(curLoc),prevLoc) - val right: GenomeLoc = refine(expandRight(curLoc),nextLoc) - if ( left != null ) { - intervalCache += left - } - if ( right != null ) { - intervalCache += right - } - } - linesProcessed += 1 - if ( linesProcessed % LINES_TO_CACHE == 0 ) { - val toPrint = intervalCache.filter( u => (u.isBefore(prevLoc) && u.distance(prevLoc) > startInt+sizeInt)) - intervalCache = intervalCache -- toPrint - toPrint.foreach(u => output.print("%s%n".format(repr(u)))) - } - //System.out.printf("%s".format(if ( curLoc == null ) "null" else repr(curLoc))) - } - - intervalCache.foreach(u => output.print("%s%n".format(repr(u)))) - - output.close() - } - - def expandLeft(g: GenomeLoc) : GenomeLoc = { - parser.createGenomeLoc(g.getContig,g.getStart-startInt-sizeInt,g.getStart-startInt) - } - - def expandRight(g: GenomeLoc) : GenomeLoc = { - parser.createGenomeLoc(g.getContig,g.getStop+startInt,g.getStop+startInt+sizeInt) - } - - def refine(newG: GenomeLoc, borderG: GenomeLoc) : GenomeLoc = { - if ( borderG == null || ! newG.overlapsP(borderG) ) { - return newG - } else { - if ( newG.getStart < borderG.getStart ) { - if ( borderG.getStart - startInt > newG.getStart ) { - return parser.createGenomeLoc(newG.getContig,newG.getStart,borderG.getStart-startInt) - } - } else { - if ( borderG.getStop + startInt < newG.getStop ){ - return parser.createGenomeLoc(newG.getContig,borderG.getStop+startInt,newG.getStop) - } - } - } - - null - } - - def repr(loc : GenomeLoc) : String = { - if ( loc == null ) return "null" - if ( outType == IntervalFormatType.INTERVALS ) { - return "%s:%d-%d".format(loc.getContig,loc.getStart,loc.getStop) - } else { - return "%s\t%d\t%d".format(loc.getContig,loc.getStart-offsetOut,loc.getStop+offsetOut) - } - } - - def isBed(t: IntervalFormatType.IntervalFormatType) : Boolean = { - t == IntervalFormatType.BED - } - - def parseGenomeInterval( s : String ) : GenomeLoc = { - val sp = s.split("\\s+") - // todo -- maybe specify whether the bed format [0,6) --> (1,2,3,4,5) is what's wanted - if ( s.contains(":") ) parser.parseGenomeLoc(s) else parser.createGenomeLoc(sp(0),sp(1).toInt+offsetIn,sp(2).toInt-offsetIn) - } - - object IntervalFormatType extends Enumeration("INTERVALS","BED","TDF") { - type IntervalFormatType = Value - val INTERVALS,BED,TDF = Value - - def convert(s : String) : IntervalFormatType = { - if ( s.equals("INTERVALS") ) INTERVALS else { if (s.equals("BED") ) BED else TDF} - } - } -} \ No newline at end of file diff --git a/public/scala/src/org/broadinstitute/sting/queue/library/ipf/intervals/IntersectIntervals.scala b/public/scala/src/org/broadinstitute/sting/queue/library/ipf/intervals/IntersectIntervals.scala deleted file mode 100755 index e929477a1..000000000 --- a/public/scala/src/org/broadinstitute/sting/queue/library/ipf/intervals/IntersectIntervals.scala +++ /dev/null @@ -1,70 +0,0 @@ -package org.broadinstitute.sting.queue.library.ipf.intervals - -import org.broadinstitute.sting.queue.function.InProcessFunction -import collection.JavaConversions._ -import org.broadinstitute.sting.commandline._ -import java.io.{PrintStream, File} -import net.sf.samtools.{SAMSequenceRecord, SAMFileHeader, SAMSequenceDictionary} -import org.broadinstitute.sting.utils.text.XReadLines -import org.broadinstitute.sting.utils.{GenomeLoc, GenomeLocParser} - -class IntersectIntervals(iVals: List[File], outFile: File, bed: Boolean) extends InProcessFunction { - @Input(doc="List of interval files to find the intersection of") val intervals : List[File] = iVals - @Output(doc="Output interval file to which to write") val output : File = outFile - @Argument(doc="Assume the input interval lists are sorted in the proper order") var assumeSorted = false - @Argument(doc="Is the tdf in bed file (0-based clopen: 0 5 for {1,2,3,4}?") var isBed = bed - - - var outStream : PrintStream = _ - var contigs : List[String] = Nil - var dict : SAMSequenceDictionary = _ - var parser : GenomeLocParser = _ - - def run = { - outStream = new PrintStream(output) - dict = new SAMSequenceDictionary - // note: memory hog - val sources : List[(List[(String,Int,Int)],Int)] = intervals.map(g => asScalaIterator(new XReadLines(g)).map(u => parse(u)).toList).zipWithIndex - sources.map(u => u._1).flatten.map(u => u._1).distinct.foreach(u => dict.addSequence(new SAMSequenceRecord(u,Integer.MAX_VALUE))) - parser = new GenomeLocParser(dict) - sources.map( (u: (List[(String,Int,Int)],Int)) => u._1.map(g => (newGenomeLoc(g),u._2))).flatten.sortWith( (a,b) => (a._1 compareTo b._1) < 0 ).foldLeft[List[List[(GenomeLoc,Int)]]](Nil)( (a,b) => overlapFold(a,b)).map(u => mapIntersect(u)).filter(h => h != null && h.size > 0).foreach(h => writeOut(h)) - outStream.close() - } - - def writeOut(g : GenomeLoc) : Unit = { - outStream.print("%s%n".format(g.toString)) - } - - def parse(s : String) : (String,Int,Int) = { - if ( s.contains(":") ) { - val split1 = s.split(":") - val split2 = split1(1).split("-") - return (split1(0),split2(0).toInt,split2(1).toInt) - } else { - val split = s.split("\\s+") - return (split(0),split(1).toInt + (if(isBed) 1 else 0) ,split(2).toInt - (if(isBed) 1 else 0) ) - } - } - - def newGenomeLoc(coords : (String,Int,Int) ) : GenomeLoc = { - parser.createGenomeLoc(coords._1,coords._2,coords._3) - } - - def overlapFold( a: List[List[(GenomeLoc,Int)]], b: (GenomeLoc,Int) ) : List[List[(GenomeLoc,Int)]] = { - if ( a.last.forall(u => u._1.overlapsP(b._1)) ) { - a.init :+ (a.last :+ b) - } else { - a :+ ( a.last.dropWhile(u => ! u._1.overlapsP(b._1)) :+ b) - } - } - - def mapIntersect( u: List[(GenomeLoc,Int)]) : GenomeLoc = { - if ( u.map(h => h._2).distinct.sum != range(1,intervals.size).sum ) { // if all sources not accounted for - null - } - u.map(h => h._1).reduceLeft[GenomeLoc]( (a,b) => a.intersect(b) ) - } - - def range(a: Int, b: Int) : Range = new Range(a,b+1,1) - -} \ No newline at end of file From 68b2a0968c7f512c92390104861e9730b13cb165 Mon Sep 17 00:00:00 2001 From: David Roazen Date: Thu, 17 Nov 2011 13:16:22 -0500 Subject: [PATCH 33/44] Updating the HybridSelectionPipeline for SnpEff 2.0.4 RC3 This will have to be done again when the 2.0.4 release becomes official, but it's necessary to do now in order to re-enable the pipeline tests. --- ivy.xml | 2 +- .../{snpeff-2.0.2.jar => snpeff-2.0.4rc3.jar} | Bin 4113631 -> 4155641 bytes .../{snpeff-2.0.2.xml => snpeff-2.0.4rc3.xml} | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename settings/repository/net.sf.snpeff/{snpeff-2.0.2.jar => snpeff-2.0.4rc3.jar} (88%) mode change 100755 => 100644 rename settings/repository/net.sf.snpeff/{snpeff-2.0.2.xml => snpeff-2.0.4rc3.xml} (77%) diff --git a/ivy.xml b/ivy.xml index 96c1de844..ee24bc367 100644 --- a/ivy.xml +++ b/ivy.xml @@ -76,7 +76,7 @@ - + diff --git a/settings/repository/net.sf.snpeff/snpeff-2.0.2.jar b/settings/repository/net.sf.snpeff/snpeff-2.0.4rc3.jar old mode 100755 new mode 100644 similarity index 88% rename from settings/repository/net.sf.snpeff/snpeff-2.0.2.jar rename to settings/repository/net.sf.snpeff/snpeff-2.0.4rc3.jar index bfd06f97f8c93dd980044ccea2021e1fe97e1040..ee5d02367293107c0e4383274daec7fc91d4d53f GIT binary patch delta 319310 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

FOU+lhy| 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 m?e~3sQ;c*q>SeOzwt1w$7vac#3l}`~x zuot6`6SDk4W_~hh1&$MQ0@4j)gA90smmt(X7{rzO8FzmH>m1+2-y;#k3+#qImG8*$ zj}h`s^qIdyE;xmo_y*$|8n`nX!_-+W%&EORrU2h3MVIvAotkme%GB_@8o3X-LzuI%gjyj`MG+Ir=C@RM+hGsd3W?*}b^8x=fv$SLgNOR!wRRST<2FE2;J~ z5A>q5TKut0O`;canx0gqCVT!Q>o23n&C09FeHd0f6h0?_ff_lezk?0hxQME2I{v7)2KbD7jojJ;#{C#l@U@@HMqS@MHF2 zwQy-oUM*_r#f^mo7+=Pb#SwoYyJe1HLtmTIwb~Dscvx4an#EdqbvYrWOtpAeNz1Kp zp{8%09$dv|Z9n+y>Ds(nv(AIPf8*EoA9Xxk8@M0x#d|ymue8!0JdAtUe*ljRs01GL zY-8eXb%k2b(~d_@ZP1m#B&5_vw)|Qt5fmL~=_ZjAgRT)#&2Ufdj{{>Q}j2ls!R+dy*=9hTirptH$ZC)*$a4 z+GHW_;F>I~P1Xwkan^)}*n;L!^3Qu(B`YRk9P|(Kr6=Uq!`4qwa-1GN$XdP~M!9Rv zO43O99Ch<^s7&RKsO=2WCNae<{!Krut~pew={SGA%e?{YvDE}Jc%lN)4PMG#TbCMa4@%b&H*SA?U zzm1jnE>-zG;@vyA4L`u$_#qx*mVOF9rh(tZH}I1IKyq{dknmPB?EDL=%`$T&wRS#k z@zCrQnolb2O*O}0fAzWNS2P@m7TWqekCSEUtA)n?LgT?gzRA7XD(Plzk((-47qF;q`V zp`f1nBT!2N1PTBE2nYa$ZEru!!%x8N4*&pu8~^}B0001Em&NJ>Hh*t-ZftL1WG-WD zVRLh(SPOhq<(2=RWRiPla)FQ#U?9RptmGXG7|S4tgaE+=G=b276ecq_$;f2xFms0h z)lwg=TDslVtyQR6=+P_ll<#*M9=d%N0pwUVJtpnqQYi6+5N^KAK#w~S;9SO&~!>O2h&TST7?+Of{5OWR8 zfuUnQwQOow-mp@MTVOzg&YPTpSX!{I{r~4Vnx*%K)7IATATPhV>TTMz@~%}_tsz9)en%Dq*DEhp^jpq`uzwR)0qV4pb~@(RslytWZu2fx za=o(EN(Hb6bJfR<2G*m6`i7H9D-jjc|4y;ZiD7`4;suQdm8eZB4>wbtsU&pk*dp-f zOxSx+M>}J(SGjDffo&>P{ry(L9<;ixl#W}NxeZ5RNhOylv|V6Kj7tDr*rZ{nVA1$? zM>yVRr+)^mD9>9DMXaRbmZ-+)ciJ{HriiO_uOK+BHPndFHQV1_Cdp=WIpN6uj&L$p zje0a!CxWF_lQU=vU+mHm7R+P}6O1LdOvg5rr4bd3OJlID_cl_!O$-9)!+-|nTIDo( zvpqIwr3}PWbOPxCd#KBawx&|bi~9}4F-UD}hJQb=k@DMuOG@G58FSR;`S{^x$4O2i z2q1w222vpK%wleKTIr;nNLvc@Tm)>>k)hKRu6guBV7eY+VNHFbfg@N%9Vu(G6{gJ_ zV^%z><95QvMmrw2hm;djRy39(L&((d-n@dYzdADshb|b8ppN$mS~texmYJ{}v(L^X zqJO5HFr5L*Oj{|!dxO~#-p_|j%5rz3x$-g-1VA&EHY0XQ-PI7l9oVGfZozzd)K2jr zYMFg0doU*{3^`JvA&oUb)CN@fMa+-)#@Lx$f~xe(tMDP zY_XGvl^gq%TdJJHNlU3dgog|~tWZXIpCNIqgtMK8I!^F{I&&~5oBj)#Iv!ySRqJ>x z-?lt)%4;EWnU0e@UZ&wwbXVRW6^3mYXWXg)PGOV!c-+8eFwD9gO2r%|j#jmMeSce9 zb$2030UdwLPMdE}yk5s?qL|rx*s;<9Jc%*%=DHpO$+z&RMa zF%q}ameS#@j%SG&8|=i8x`);zV(9@Z+F)+C9a^X3Ig0RZb#vhh%CTRhc@s-``#{Pb zQb<7@)bM;>E%$5!RLz`>97M#pa(|(65F?keSWbb+gc6-UU4c?mpIa84xsu?<0IPIj zeOI9t*ki;wx5|_45PDP#+*koJKK3%L=D_^tv6goS~ zp~_|^Y!@*q;mIXZ7VKU>e)nXbu>!t9mSi}Uwr))Ir&Vj7qQ_Tvt3LB>1AqU7dP)tG zQpb0hiuJ0$f8W5%_<=%3C!BK9J7dnkG);`a{V!CfKBCzE)xf{uM|6>OAQO(KFJZ}} zHd+3i8B*_!1B&lI4g8n7uGHge>rdDztA*I2bo_~dpQ>IM@aUDlbo@+EwavBHTvi`h zW)4w9JL8xhjaOl`DKp5@41Z9_&kg)SL48ps9quRpuawU0xW6Xm))SI?ZSS%-4g5EX zy?Hjj_ge#RslB2DnV98J6U95A3y1Hb`T;AR42VyPH7S}r)E!Pc)}SGpsFBk;qy{t} zp{`pOHPY@#>`&N3rk%5aUJ%2O8LB@N$E`%a!wY5+F>-VaC-K6uQhy%lY(vVVoNC6> zt%FJDu!~~5bg2+5>4>G%3bee@A+WM5e(11pT-B+r|B?hj*BDGp-;N94(R_`$6Y8CRLOoB6wE>+Cg9cmde zx80-*r&=Eo#?quNKYy|1YGp#11*DcEk2JD!FIE{vH13KeCy;4!jbLG3#`+A$MfSx> za+b>q<%X3PLF$r%uI>&oMq(wcBmhYfM3 zQjPsWy)Fb~t*q1J20C(@@nlmnWyp=H!)t^q#iI*H(giKl#(%xog+bGUGETN^@Y0%} z2y>na$Od)|*&v$@zHu>IiU;?TA+52e#5awj5A1)|=+0s%=#QWJYZSDwIP$N_~}T2gJr6S`2R8@#s#Ghfy2 z+pXEzvcjx#-`UKj<=0$Oul`?KUB@PD-pnRzn=M{mC4XYNaLjDnp-?bn=49n3b#u1| zN9jE|@W^9_E<;TFz$PnUrDBmBXr)8C(3cJV`iNO)=Fr@n@Dnn-l2#+&8! zlfQmI@hI>#%}aRL=S=m6Zn~#@{WcYN>T(A$%3ps_@#VmI5?^<2kHZ%)IVx!J*C*_D zyWhO*Cx2vau-z$_Z?AUBG2NbEveh}Uv=fV@)w_o`snYp_R$3ji|8&`zRK)U9a_(h+ z4rovT#1b}#LV&-1^;Zn2AB~e45Kw>1;J2SoB``=ggHJ2?t$s>;&tT^4s#l*u=~H|x zKp8)0x}qxKV~Y%60m|JIkY&!efHmAvB1&pTP=8TVHt!sQmkL-)0gEW$Dp!Evkz*kR zs~;kBPE;4E75(hmEEbo&Ba3Anb#)hf(WW!w0t4>-b^NY(g_$11xUku%rFrxfjjkh2 z0spZRzllFhg#pvk1RU*xpNd`O)?qPj z#JkW!iVd*P>Y7wTC8=W0qwja#|52Wsk;*Adlt2UKtKOfBqY z^bJPpp~AJUA^T_ut?H$6&%)yBwvf-)Tz@o-yMn$iArUGL7H5%c)+)5w_}WSM3)NX4 zpQPa=Rs?-zP8J74x-08FloixHt0_zw9n@)ZyC-%S4T|q$Coy9bcl4AU9l@Q?tCh=q z7nv)xEbejLbm#LGMJL9NlJVY9(I}4f1btcj-UvP#Dt4(qrmo`F$Caax^FyiGTYvV6 z373=VQBu_@icgO3OGq2Vsh+wK{9#BdY}N~#X;R>6lzu@El?-EbPz#n6R?vt~SNJRR zea(J;1)^nt^l6x`$j>S@{ZzB;iE)_~{^mfihJ zktuK;gTIF1Z)NDWGWfj=ew+c%Fywb&AMQmMC&}|T)8h#Y;5=gZJ}F+uAl^hmd^o`A zE+um~;mw01OZd89#rOLr9G07KM7D81jHA-W_wfC=Q|{sW^ZmF-j^X|CAb;+2Q}Zq? zY4@LzSEttB!`JcWq&tt7@E7y=XU%pR{>g; z!QbH+oTP)P*hwEm)f%#)N2txz+yEIO-Jl61}hGQB| ziSY3sfa}Jv3ciapYH^{2#{MI|>D3sG_Tc1=n%Z;tR^ggqe7m-?sq8yheD63OQOkeM z;)f?NQ!Rg-#eW>fl1ksc+6$jTX-(}t1yb2`4zCR3R885dS-dukhkq5}FE8@E?(&q@ z_|EZ;!gKh|FfwX?%q5w{Wx0q;GpmN*uN6Kkg~RBqQHfYCepT|7O%v+Ey<~-pTT=2U zX4KRw4LXLgIL9(`Di)<^?S)fgS9^@5#}TM0mpMFn%55|%d$+isA7$(wp#L9a27jEn z@em>IVYJ~mbMyp3?tc@^$478C9%Vi~#>jq>@AoH}FQ*v!2k;~2?yvEg3Ap~oIIh2; zUK8nd4KK@Fw+pD~&c&?XF!<)2qJj!zZpp%e@`VL&a#v)Yl;<%2Me3wv-AWTz^(Yhg z`^t#SKZli`(F;9{@0biAH6wCGdrfUt7H7rmsHv^X%9Y14yMKn?R~?7HT$YijT&h)h zpC0$am2Rv39BueKbNnf+#M7+WbJ&P1Yx6wo_qhr7-I}v+B`d?Lq&0+juafq-O_Eu= z-Ah2`8{bzvqY5f%lwTNxqqxzWW^WJRW|eli$`Q*RyOTac?cb8nEgU-Q6Jmuv$CTnnapvFzl zSJ=UR!u3x%X1>a?=V$C*uMzQoj-6C-AAU(p_$ZYdin*SW@3IG5I2mol3YCpHM>I1q05C{kWg>7#?_pJ~4SP%dJ=$C%&14@5*T-9~{ zk4B^SX8df+l4V=AG0!nr^2j4uHnz!l0b3Rz@Ge_`4cMcZCu#63^32E!B&;R`2zy9t znzRih2ogvNL4ygwEJ;X0+NPvUTe>7|(=_Qq(uIaLNumFF?~P=YHkd4`!(bR;UwiR%H=chqTu3m7prom+vg* z1vMM5jUR}&q~qD%781Cg&aO^nQ^mD{K;xn=L1kMm>4Xu$WD6mLLzsVxX$GnV?f)<8 zSg1i2H||6HY|K{+=CVDhUcsU>Z`0G@f$9RJQ2k55&_g9xwM>geC(K!NQ42SXhjx z!1T6rXv0Ml+s!F(vlg@(SRy!gLIM_+Vj1^Jkr*%L7>YBvmm7=&#Z6yBgymRi zUz$DaUVGo`;L4(TS(+n z{lzfW;6e-Qu%0nN!?h)8wwh5#*_bL6d5jIX$UwVb%IFyj7vmBhp^&=HaWk$%m%P-% zW!NaN3IpBcUAgeAQ46=_(rF$smCJJZ&AR*+8ezP7U6M!JJ^@R#A5!*h*kNHOnEjJ@ zCOoyDxu!z!_erFh3j`M+M#fqg?&O$rHEt9CxR>XZS8PnMz=sZ zh_m{(Ip@BIUHJE5u-bh&!4c(oS1~%+eQ{Gi*z1WjOb|D@GedXbgt*CEp+$S~8M#MP z)pDL4$hf+h9QYcGGm%Y|nF~zrEXEW2HpTn>0vmtokLL@{x_oazQG54z38SU*5etvv zGlaId%UL@Pqlw3;OH={maSKo2b2MzB7|$0Am($s&$i`hJo}?wB2^IT-g)idwct{3? z3z4Hl{2HJl&(PaZuV|~>vlfPQyJ}Caqc@w&J8kiT+kiZ0;g~iYVUPc!RN67|WkKT> zH>iK@hG?Q;I2=75yN66RPdbosvaAY%uUL3d^Eo(Bi1$+dCDr+WiC5@|=m96+o%1&P zs)euN>(tJt(IC5&{tXM?RC;j#K*}kacwOc9oA`YmHtN&(;spBnfcv*;AQzPO(#^=E(hbCw#{s~tq6c#m@m(|4$1e_`P-agWw+ ze-{&ft-7`9Fg2{f3MnhJ4x*z|G~nKwe1V6cakRlQ91fd{47TKz_lkF{XpI@>@`-||@@r=WO*LD6!Fn?33P~c&0Z-x9Gx}~7o z3^Q^s78KKkX==~5PQ;Q53D9U-EXz&Y*1VHUCEVU-<*ECqk)Cg?;Ht0$#W195BG-qp z;+B{a5=^5~9ezr)<#r0i(<(Ha{Vab267|FF&IQ&!qeV6XE3%wC|IJmNb7zqoP+l zJ>YENm385G06b39DAf|Nq)8UDz-Z&#m(OK#1^1HEJb~yO;pbA#vc!;<2{i5_QkJx8 zZx+<#NUAc+EICg>La#raT7#6h!jhFPa*)`bCEo{Sl_9ZFU8;9ROIFJoPr5A~FKEFV z)hoM1s(OK}Gvq>o*hy2CtXF@1)D+%Lcr($)lnr#q9@S)tDHn6he6Gs4t|fL7`FR4j zVDimvN-MK*wO}AWkX2yX!nRhnvK<)*&Bv?Trtpq%)^;bJR0vGQi}7yOM7t-K_wkS@ zDLdE`HrS1N&9*bC-oBz;jPGOdvk&&M2rddsmt0}Uod)X38}@V=|YeY4-9ypDUUx|7s}Gx4I`z@}65E+Dy+ zn$AZbToBf|9^pp?{>KeEp1-X ze4f3%ZJFKZp2}Xjq_s7wf6Esww!078TiK%J>^9HP*_0`~BQ)*Q4kTv#Yw1r)_HM6u z7OwXFS}t>@@XinmX_+4XQlUSO(AxI;_6=o|G^&|Wyjw+=EHZzESBl*sZA$Fb1^4)C zN6FKbdVR7RyvYW;n6neZS!rwd+KoWd$H|zn4^D%JNunPs^{>DouVms3YLbVeJZ03&&eBhGAnF1!`#_8Ev9XdDIXKG zg`(Nq#$2yG=0o>*_bVDXmoM6G>uD1pim5^|l_;pO3(ONV`1$X;->l=TOP#JU(YrW)7y|Y>oiSlO$!XyZ%|_RRept zXEoRj23CIu)&$lDR#XO71OqFoBF|!82_Gn-F=o^oB{auEgSfXo^gPZJyo^}PJQ9$4 zvxL>LNrOn&Px2?$#KQIA5-xZiZGsh+jys;mCXOc`sg$~4-Q>M1f^}7O*4`CWI=MAA zbr1{cr~2aCVpHm;l+ani&$6iur1zczKxX*LxtcAQxG|)MV(25Ao!(v>DD7w&$ZY+UAY8p#X z;LLv=SdNFW0-xe|kbF;5<^`d z9MpXltP{xwVmjeZu0{3ONds&66e%;!Jsp#QZ9ZGYMAizOPMgs9Cn&)jMW)g5JNR8M z*(z?LO_}QQRHSJLpAM`UlzW@zF0J`&34=#vyAD5J!cz}piVmMH;Y&v`e{SX8rsIE) zpgPjTY?!-r2&F;kiqt$`!V814Ng0oi$h_>zR7Wa@@MccKU4a5MnvCmS?2f}z2UPS!ygaf%|X1PEPq9AT_sYvYY5+W=}##A zZ_D(+F#Ud||Inq+B%J+7;p~LzyiR}rWMuPyayOqE5gt5n4F58Str0iT{*{SF24-2! zzw_bd;tPXVN(6hmgm*rL$!dZjygi6{p2RQ4lhCs?9sl&Z`#m{@pGeKGI5Cp22Z)=% zWlV>S4D~Ij!&Wpg;4j2>bf6P^umef#WU_YQ7F>?od2PQJS29s|;YIAm_i%p|-XdlN zag9`CkIckgIfq?T4Bc`85^@oeqCXQKVt4i+Hp(Y)tvrc+ZUWn0;FJ-Zqet->hLs@? z;axX@HM!2ltY5Jgw5JCv>a03zX5I9PnKS3rO`2U%JGwHYND?ob`+nFYAFcT2el3u^l7~@ zTnDNm^;Kl#gVjLlv?CitUAcnDjF=f={UAdb$A7H+v&LbaGcGGj$BA4l9b>r+f3#24 zcSo_s{$SpSU$+yWfIMwmV9FK=tp~9Rhp-OU(~BP>xZS|F8~MBeAH{#owEQi2g5O^v ztbPl(;XC|(le|Bq{99b-=eX1L*><;zzvKIC4!$dA$vhgr1<&GLIh(t$!y_()2D#5I zViQ8ExKowPmk&_(QH~ZcLQL*b!e8!0X=_~X|tx)PE(va%_5<3b`71Nmxh~6_xqP$68yBgxl419Avg1w zoU4ggl0_wndU?2sZ<@H@-@BHhsUzto5X7rHv9KwR?iGpkI?0m*+3y zgf;`-$ZY_;p$4m$^Tm#086K;0#m(1E-i z@($HOw^UHdkxSkBxeQmxWp4fKVmaB!*&s{8CMLgY2w9--T;n*=Xn|VdZT^>@lg&c} zsAuu&QP_HtTG_^fcxZG-t#o=?ACsM4y6*B=L$z|XNAq6x3^OpFaq$M6ik!tftyN{{P6zHXE1}N z)b99!rq^IUBY97`0XKNmGzW(r%E?+eTpnCs9(=ewxUqjcxVb#IwLG}3Jh;6)xT8F{ zt30^7Jh+#G3KpRe3|Y$-4%B%*4-~;m41$;0mc7Cz=v8dSSDD{m!(MzHDZEC6_y*D9 zn=X3Xjn^4=-*yc$!#m}0kUb#xGkC-`O#m{VecJ@ z3IG5I2mlU+ZEru!qWO{WF8~0^XqN&014w^hRMpk+IrmQHy%`=`!WPyC1QW8dF9F#? zfM6CN2_c}ukW7++WG2i^*sQy@E_JC|E$+C*eE~9{RNOw%YOUJVs%^E^R=cQeU24_Z z@}G0xdy|=Lg8gZoci&ykJ@@SA+?N;r``8fxnC9B5fk2pB=PPKbYYa3u(_?c%Yh8aJ z*cfW|6-0uq#SIPqx@bXNsHMdhtS=1&{RNe(x2UC_el>6*O#c?k5e8LOmz6DEQ@gRM zxNKGF;;LeV)Y2`!?Y@F$U$C*DG8!f@^AP$Mg@Tc&FBo0xYi{%F;GuaIX#~L=3O4U53e^+l z2x$qYSrQ60`+Y$=PQSXQaHu7;syP&m5EgB%OG7(?bXdQyrPLp6j5Z;7qM?7NuemrJ zrU?VQN7_WK_6!tuGuPx=^2?-YsI&>xiV`Wr60XKkTos;Dfknd)kdQ z2{23=BeeV3gtXoU*>B64SXS4-h<{sA6CK2F0YfM#?S<(LYC=I}@&Z1%wP7gS;v+m3 z_qM(-p~)!e1VIpukm8O5a>;+U0$Gs&gJ2G?Q&?N`*Z3ofMC!!TzTw102@B(gPO}C} zg7mZ9*UZ!;{tUGwqb(X}E?686`*xKEBD}e$GSC?GMccwe^2t5TncrPO7SOtZ$-B@F zGkZfeV9zNaiSrl4@d}Z!GX`mOZINiGr3=0T;_R=C`dGm7dL=cFcQSupO((F!x>3~z z)GMAAprRg^5kP%=U4soGvu_YPgcmSx1H zB_lyvGJ22}1hJt6q3nN$T9T+4?)iaWAi4lSW=&p;;39M4_kx541{A_P4>%cG$EgO41GfiGhfNx6L|FJg>azhpm_&zOTeOV9*BOuj zlRV&uMh(bYEc#z{+khs>B=}Y9su=zj15ScW4agJZoscE-!d8D+zz{+POn`|RY(toG zqL#r6trieMD+?xTu$@$|2YvTKn>BBj0ojnF!5O`K+rBj03%e~iXBm(W1>8EvfGLop z!+ErG7kAneZE5By7Z_kN&%fK%fkt;mdHf;+#)F&857WH7t!1-6T;Dn(LYRSHJ=tt|p&k zK{x&MANT&yfNSA8(m`Kqt3OyzvXRvtcK7XUxbqmq9s{n28wf;!EHqEu46jfLH5S<>TnlnphAE4ZUgRtd&w3?L)ILGAz3}p zg2IwWYjePfA9UWIg$p6Q*OL&Mk-%NU>puP>Oq{% z>W#IDj+!;1BKdvZQd(TSN`t2fUhgKtU!o`( zN;WnARrFtVDt96~Z~^;Rh66mcfQ68tX%zJS39 z+ID|Iy;X;=iAo+O^M9Gl#}I!;XRYjms)(dY=5{+f@}k5P4JnNs-3!81 zBrIVA-OOTdq$#wcGFnf`83h8U8|XnVX?la5B;@yM0=pl3-sJVOpAZ%a>Ym%jRXvOPH4z&SXVbiK(JDOYBO zf~fU8bDn|onS5S~&TQi8NV(D^Qb;n-JDS*9w}wM?{z!y&cC_5gYNp_aqNew8k%5cZ z(0Iwv*l{bF4lE+mflJwRl=NjfmLa4}HVeHT9l2cO^%DJ78h8qp(tw|FVAp?~0HCTV z9NNJ_)4ZO2!~0lnUwA4g>~3KQ42OId-$!RKUUv5 z3>dftw{jWLA1$%tOY&!=Nj%K9iY(m3UO;ZIYhdh(gVFFVwnnW6%!FB93?tby;@za< zs_wA;X+y~B)&3-IqZDs9?l5pCJKmISZT|2s_K~{{JVV)?K%}^(HA;VdEqJDZXW`lG z`5gh{vTas3$Drf6w6LaX+4zNprjF;6%`yv2FJ6e>(~vUgk-Z?cyp>$SMR+mcQSaZ} z*4QPCbb`dSI$lcdyB9OJwrH8@=c74>j<~Xjf66&vvUuNzdiNzNcCk$nj<|7L^d;SG z^@St;#VXSsmeoV5%PfD`i$65*TD*>l%ib_j6X1lo$1r-mj(aEp&a2~PHyC&$-b9gw ze_NX>clJm!T0(iMmS<&?yl$_7xAAs^taT-g!BE&=&UHPV0j`x)o$IzC8L%(Ej` z-t1umAK_@0WG~>4>bRfhZPT%X4wYy39pt{LJo`}tALH!JLt9(wrQ?rzbc&8g_}Qf6 zlLRcU$=}@S#i#NAG<+txh_EXX^|u)KEbFPuzcWA(p5v7vrpr$Ze1R(o{i31j)>eO* zNuooO4n7a~@n?Trn#&fxkvdxC|vBCBbN=ufg97(@Q+~7XD7dw|mc@;})qdR8QH0Qc%k9<81?f z&y-8ATv1W8v8ZBcMS11M#cL|6%a?8}TCuo%d9jY~B20gc^El9r$IcELlbEgJANhO{ z9p7h`DQm>X`?G<6K}xc;`arnc$C>&`$5le&+7Etc;79m3R$0F4s%Yr4xYvY{j1OgG zSfC^2T`8)5$GCiE;6L$mlDy5tPYanjJsJDsK~KjoNHy}~^*tC_c}0IQP!q)M!QC$2h_GT1o1&y*p>r#zi&RpsJcrX>XcI#? zB)v%&!}yd90bPus04y*0o<$dw-OP=PPD6LuvF3ko>_vpcXcnP>4o~W09Kt9^pX>@l zk~)d;j7XqC7ZWX1ZEA~2toJQ};A&lDGNrgl#w|eJOH39yn#fL~2jkwzS;$8sm+6rd z?b?GRi(5s$AqvD4z6te5%i=J3Ne>IiJ&9=w5#LTiWQIb7nl>T?Zk8ct$HB!%bTQXP zzes-<9F?hNhzzBV%K}@)$%a_K%LZ6jI77NvWba1nmZ){qOjx(n5XJFzokO}<&SY$> z*F_20$Gk4LuevyeWw$BR!Z6AVQ7$S}3K?DDCk}>o?`jOSMY_qIS~Tv>m8Dg_Sw5)uUF6wzC5RC9hgCQE#NJ)96E&@DK?{DUjt%hh;BTI`* zbrG~wKd+t`5>X8CLaS&s#5NJO@Ln1p;ox#TqY!1*)1tQ4&L!Iov4fXzG+J%<>tcVG z4PG6KtXQCq=bmAR?>OK~;v>2^i%ps}I~)q~%ySHJF3GpSb>=d)+y-J^V=BQ7+= zcX^7NH;$6|yNG2i7}CWhWSn!!z>Du2;xchLiLNThQ#{v`Z|EUf(#4g8Q7371aTVFk zw)p9;;j|zD|7!`0fQ{vk2#YpG=X!r4TX`r*N^m2>aL3qKCdv%iR!kQ+lf+wt@lJ|C z#I1(d%i)oyQHB0%{RHuLiiGW{WQobaFr-ZOWV>~77sB|mfEAxwYr-2z3nL6MK3f-e zBb;KTr)2k3AZy#aK*VILZ${c$TSMWfzn;Qgf6$CJ`E3J96DWuaB`+gNlZ=0*1cJ*r z0hQ#131#by>idTQ@wU2Tiw~;1q`^eZ`Xc8BoFhPr>BJ+3*vBqi=TYv|!~ycm-=sGd z9Ix0fVw7nKisqJlv$8p^WnM{1NsmD@fqT>tkBP%{Nag(y7WN=SB_~k7_u^>rIJ>)( zIP(eG--Ngt)FY+Yx;08spOk;lgl4N|LY}LR2>s0`vdAz`6Gur`6`p}mfg-JV7T%&C z@e@P5z*(1@!qNzP{GSq=%0tBXx+cpDW-=RoW{8(n3AVv!eH}w-EUkXk5IJK^~C~kO!n83s9rm*bst=G< zwvm|qt?)&f%6zT!`2MSh{vJNr=;+>BQAu@fIx5#XEHDxE-cAbC~#pA^s@d zqw{dR++vN)n!J*Y%%9k~|C!P3p$a~aAd0^l;sfy^tx(@K&FLY=>^thTyNG2LVdCF9 zbs@;yZ%q!uE!?II?}qE&llVkZKBaicCY`W^nI-Y+9+FP)8YusqvBtz zc3<>tWP8#rT~J8ts#g}0V%jkd{Cbi%j-c7X*p`g#B!#)7GR-W$)HYiJjZINA>f1`u zot2$WCJZDP61lWDtU2VX?_N36B@K^A6xD>tFO$MqnPNycAKri1Yy}xg0vWPyNDup( z5#(zs;<0g6*cXh{g#)dWi^zVmzb1|2FjST$FysKLq^6A;EJY3^_?vV>k0$ zU1oA!hET~TD=8-%GMf|Vp(?cK`r)1~DWV<`-=iyVTAy#o0tVhs0k=NQ(B)K8A6q68 zO4boZe!3xNs0E}9i-W%AT@n6*LWJswQ4L9k3Wv!#B=3K6?s45EB$V$8=z--21nP>ZV1WYRJ>rZVV#um5HCY^58~8ZesB^*r+)9 zJP&L(WF4dIQAlyD#7FWQFjtb?oGusfYmY zrLtUakvqv#%3bU!d${zOr^_?Qznbb+Cz9`#XUelRd6siKlMK#~=P-e^R_hdjGBFsNKZ?Xic4x%kA6{;R}gD@I?PgkgRiYQ zYJ~$eVg6`}bn9|5CWOLOhP+x{V~JYQ47Go8PvDYf@i#&8TKOYQUguOYtztXZlE_=_ zk(AGf>dN?6;|Zhlc>fy>d6T@^5?ZUfv&xt_m!;`eV)T^!iRvG(z0HufGe0@?w?1sM zYzK?&orb(i{D$?{5?dvOehzPV<=uw7M{+5Z;fqAg1}_dNax z`nb;bZDMkps|&tef)ih@%U@H_5-%RGU)1GqIpF4+uq0(0Giho@Rc#*swy1yKdXs-w z6!LGGCV!uFqhSHofwaVp@LfzF5q1lt?GjmP*ddN)pS7=r?g?5H6 zt+04tW~oY1Ajq@XV1)gRya}ZV30UZ^{_M{!fcd&>Ai^q7Ucj7hiN^vndH<5c@BB8H z+v^%~&FIc(uGvb*YilLy`RmQo;yV;_5sIJ=Mp)s=3-t!IqAdxk0;oWDg;uq$il9+m zwZ8LW!Zf=;tB!EOY5jlAMwjF#YgeevZ1L?f+h~qTQtfIU1)RG~{s1yES$7RZX!Y=A zjX6J}Bz-{=d`e6BJ7_c08dOQ2_4i8=&VqwJ`pNR=H|Cp})IBwmaEL@HB{M?}X6DpP zqCY36{M95K$Bwb-RKpQ!J$aE_)3h$mx}1v3GYr8Rvrc2XfMkD;X7=#mdZzA5M=0~; zwbhegbt&*$g5{5zPU+O_tf_X=&-!xP%p!T>yr1bb3c=(GOq)Mg=XazosdUN`JqBT_ zhs)b$5B~~HmBH=yn>4|%mZ$=!?ixob$4rj&y^JRK!-?z0lSTIMZAIVfXo7un5zj4c z022{T_V9hhHvoS~xcuk}Vv-VfzH~Cz5xqNapUia(Vwp`MlKJ>Tz9TcY*|VBMJ7_#g z1U1*`F3Pq`JnH7if)>9hI!!SW_tm^K#1BY$;%}m`_Kz%#+s;V9T9+Bigx>&z2oaRU&-m zsHI^MajDh{GJ}DjWw1`bj?E1}yQc~$Y6<;CSCMV-s%QcCXWyf5lX`Yy}YRFrovrKoDQ zC-Dla_odaVx}trul5gtQmbuB5??hTHtPrd9N{Zf9(V8||z-=JUh`;%Mi8OkZWMHhicG*B0R;i!gOD;a6swOYAE`-7BT0oi{g zVg)Gi*u-X2rE(M7m`&Hncb}V1Px_#)?%ft5mr&F?nI(eP3fH{Z#%=Y zzaUG3x7O}sh?EW3AUuGsbq36aIbQI))*G%3u2U(?*lu586=zM}@W21LwB|ZJ=`U-_ zLzQiRbxq3x{^oj>lxQ%A@$eZim+?5=RmXVLBP{*4+b914xVJLY7Ot~X^}&gMDVWbl zQ8r%^B9EPp62ce|{g4ArK zX1D_))NG^XjtbV_oes=1G9q_$J)R`})-_67YfO2rrSv_>p z0d&SRI_U^H;}|++2Ay&;oJ{{)07bBW5K8EG6)c4ssDRU{RS&CS3#@@QsDyK%3ND3e zxE9vJUYgMk>)-*Xg-d7$!=e(6e_zq|GPvBP;7lc50D;4B1)=(bvfR|GVsK4OZVZ03 zAo~E^SX-zandO$VQrcmDx;8XrTA?d9-NjSJ@Z;7^3r6jMp<@PR-2nr0Q*Y;g0lAwm(dpNp<*HB*IJr9qLGc>q$@>NF*9b44UZp0XpUu zI0d%SFKI&gZt^&f}mB?fd|lDIV5lok+8ae z_g)IFV?5DMxF9)*GYARwsi zfwQQ6=1~~R5ANz0!+|$}yB+pZ&*D8`WFNpZ9vI3}grf@GIs6=-?tT<03oF#yAuCmHnh>#!DFgwIM`j~l>E z7^UN>gp)@57vkxExRLrcSysfJrz;6R4Lu z$U%L`T2z(Xt-;4?Aa%sRW8{H6-N!V{vesrCBbV0W;sHxETy>1hWDh$MQ8vdl^cZPq zzeEi^jCG0nbP)aJd8|}1Y$9eiv%=izC=9kFDNJ07Lo9ZG+q#JR_89KkL*kANwpi28 zcPz=)+EJuyaV2zHs^|Xyp?Yqmdj3QWb1MyVvxez8{2b2;!+3aE!@O|~^C%7TC=K%@ zYnZoF!`w>4QY{U0aIR#u=)cNqZiGxrx6VmK_B=wChv`M(d;uNbQmL;To$XmeFR?WA z`^T%HH8lBuGS<*J(4;hTYm$bZMX?LOV@%dXUy#OrsWkRf%SH5|v7_*RX}lb-Af23p*W#7<1H%6M1c|lN zO?X(^sS_rac6z#Nr#;I@CnfDf9R?gDla!zxWMpi8oRr+HVT&3Vaf}?N-ouE!Y-LY_ zab;Gz+^@F@$r{}U9KyPwFC7+!xp3{>r#V|eQxm@wM4Dd)&-Fep3c0)eBab>Qvo+T`ps zyeo!xw`=3n)cX>aJ)o8i;`sA(3OwFgk>9?8o1>r@TPqeMc>PQceCt!ov5|LZ7uT;c}HQSdLF_L2%$9myM;|Vq@-3`eLO!6KR$$?(oh=y%bGwzdV9)BwbPdgyZ&44 zI?CQPJ3EdqpFL%yn)4r7$e&Tx(5t4e3R!e!*FlizQZmoR=oX#0QRa4!n^l zZ&n^=4Lp%3Z}1WiNHuVmC2wME5Ar52R_K`tC!3JSKp9A2AOovab21XTVd5(BQukH!+r>dOb78`&#YwBK z>%OX}U7V|CE=yRoB9RWlmq>?_cClK`S*hqfm?26Mt(Yin7qivKs?HIYGcrbvtg+1# zjqDRuF|oc~xYWSui36K>06$QF19gc5el_rOHPDneutg0#ss>sV?gMBb;);n-JKm7v)l1B)qP%4_XX{^Om%-xb-$ikU2f}y*CH56xVShb zE-iGWyJ`#dBeOI)i&C9C()FS4X@x22DQV)0F4)2a-1~zr$WjbPy;paC!4=kV?+?46 z3QM^6x-OVPF8A(Hy@bM8TQ6=Py+}_M$wh+EoWItR`PeL!Hw*U-LqYAOSNdBiCj;8BXz$e}|D3h+`!H59#&hy7p)8c>S;p&kc700+W0 z90V8RV7MGpDdS3mhbVG-9*0uwHVodw;qW1jrub|uCH3Rbi{o(!PN4j5BK!XTKN^OcctP+S2k{P ztw5h^6K-~$g>|lfYq8#S0R66?VuS0i*q9=)DP<%EQfA;8DHFB`nH;}0oSxfTZCbubuz1jCVn47?r|;0=_v z+z4gVS3}7Y3&siyvv_@Py*Q%E-)^W9PtdO*;V2bP62m17h7$1%xS-5`Vr9_-?jZI(RN4Wsol>{EAsg?3Q}AAOTGQG$e%e*ySL(DL zST3FruhVH6h6hNzvNiFnNoc>}u#p7CVq;Bz&upA~{A@gvdH7}`507@|;XdNwe&XQ) z;$a8zFh)E)NIX0Q%kfd-V+T~>;RGJm#7|Wd=i%x&4@=@aTo&iyqBsvTj)R9YNsxan zenZgIRf71fBFe=IK0w}K@mB3Y@w-wkiX4S;Icehe%q)J5iFXf)_lakURe!0;;ccvc ziUF660iUF7`zgr5r`2(WSpeqM!=j7g2&)=!_uo}m&Ax9H$5qkf?xppDy z89Ek;fY`!*J0?E99(2B@i15#uRt0FBB}<>F+yrk=5GOAB9EL}0*^TnhLGgJ99I#e- z(!`fmQHWkl3xriTV*hF0M+043d~CEZe3+0uq& z=T1GvN0q;3Op0UD6_Z*!%(8_mCcX2mokm+w1`?D}F*!K#P&AdgG)_@sa!6S_ROQ-b zvc08Ai-&emFkYM!lfxHSv&LAn(rMNhE*;5H@oBksDeE3iW~@ybk~TT>ISQtK5^io1 zw;j{-w&RX}+v}_{VTMC=Io>+=nK3yr-glD29AITEiOH<^P)_GiOy+f6VZBV@mzbQE zG@78kS3w5J^vf_1Uxgv~OPGzXk-GkhOzxX-2L2k(Ba3ncz70RYci4m;=k}K;lh85e)zQ* zAwZlYq?j&T;$)E`RtjCz2#?qxyrM}MO0O31J)+oR>(z&lC1>(g4o)WHrwj&Gz<6aG zBt;Fia+c~l>hvjX+e*$YbKJzVxx>jq;#Z&@BU{BD&?y;Bw*BE=`~9SUgweEPFe+(y zT=F=3V&peS8#&Av1jv6IWht~+Nea!TBAR^|ht)deWI4M7)2OGQv!}2FbEzj>dr;2n zz>u7eWrrlIMj$2^=HJWqaUK_{6uUrOz4CSHl*4ifEm=zPT6kC%Q*${r3l7T_)Lco; z4CewUO`c*YXrRjtP;Q!kEQ`sC+-w<>Yhto0SJ@s}!_S2=xq+TYQgu4N*TkgnkgQkt zNV35~OI>eB`@gj8+?Z@^C$mM*fK_llm1`(Rq1qC>krpyr?b=GG#-!@}PDr#QP26Tp z{9eMue*8{$q-E62FlL{O@WfGv81@1aKa(OB@1!sbVPkjA2k9M!-}t z2BwR#FpK_MCrsER#zUQ$2wO!41jR|PU1Y*8F&VBGIj~pc!o4C7_KAFWOccP=VhX$< zX2MJK-=D>7_(;rwe~3c(r&xeeEX46*5$1};xKb>^YEg_4v8>aNW7HPqsaT{g*%7AW zVpZ<7{P-|1{sZfOxJK#qdd+`BndZvL@{Xaygk-B7o4rP&$Jeq)56g3j)8`kuh$cmQ zU}(CF?Q@#EfX%b~Zaef(r>LJRTk;Yblb6%%D`WBp>8^e9hcS7>m_5)h-IXS9iOIcW z9qyp4)J3+dz0iGF-dB5AK2Xc>atYc&`Orc6NTHVQPESdH*JAPj|2)P&Pqf1#LV&<4 zjy=gaC9&se#|f0QgW1FQbMf&!#@QBqgL9BsXQ3Z2E zHN^mHDZZqR?%9ylt0N;^u5rOuWw$)6DxYhadqnH+fvMVtBZ75OU748t(_bNLJ6!6h(C z{!+e1^X^rxUx6#`@YPEBx|L|aL~wr%6Eu0QXB=0dUGX@D5=>1#vr&`Jk0uu1)fuYD zHx#SSB1SW6pE;pQPMUnvN|h-lNt3@xlW(~mm%pojm51d!#N9ts9+H2w$Np@^F{=Gn zzR0oLAMzc{A^DO0`U$}mqw=SS8<3odKF5xs>PQ&dqbgN+j z@DhN1_*)JRTt+g80ZwC((@1q1L!8Dir!m5RX^eClqn*ZBr(rsc2~H!!X`JLVvYbY? z)5vui`A%bs)0pNoW;l&mPGgSKD0CY0oyG#EvB+sGaT-gV#xkd|!fC8@8l_I7+-a|@0emr`i(eaIG%YMO^=q1H^son3Yd6<9Xs(UNAdPIAYa{=h2B*Y-2+g^1H22$RuH5I^vTS)_9 zr)&k%uohEDOA3h+nrVSD11+<*r7&fgnGQ>7DbtxQ(@vrF{O3ODS>CYQkWcUGzI*RE z_niOy_rD6S{`p(qA)+%ljpHiN=gpy~kiGZXTH4o=(AmdM0l~$DGv|gtcPPI38|!wD$IYFu6jB zcqH1zRM|YH`weO+R&;e4@r2l`7;RECo;FfD^;qp1H4<}*?RD&ZGo3ZI(e`1}u$U(r zUl)m(2BI@Dok;7kc4LRRDjYV#h@jmO?20E+(lx=@jkuk+r=dHQ=t(e@SYhf-M`N{X zb+fxgPfDEHqg`=5-J8P3>V}VhST;Pic|AdFxXVb@uaNL8i^ikr6-*09jnA;41}irq zI}M4j;Ufpl)~JjEOpc1mOTnMn&Xfn$&FZ$W|k@LEjov43r4zu}yj^D&Mo6&UAOwWLhFft5T~ zMaq^c$5)VsiFEHE(@g;N{BcdtC{4XWpy<>U2T#itgCe_Nw!glJb`g%G-AL*1w;EkR zpGRKf#_sgca>|x}gPX1dTaCvjAMK^9HM&MnHU%-TSlR?Mp^W}d9Wd+$IVbGnnW)4%cL8};kF>qM+fL;jc%b^t<<%r^;B9> zc2Hj3uF)Ox3TI`yDIS(>+^NxBsw-y1yVBi)$9puoS0T@TNTob;OP9|p+jJt%0&U$gEj9VnfLGzq8XLd z28gDc9@XeE0kd0yITn{)KCV$8JpmhL?^aXI5R3&!a7j__lNvoGnJ-EwHiGa{l2(b| z*ERZv1k0;`LW@l!gh%KZH+_?7)+qT|lT3jZG#mxOxeW*^oF)WDdmoWP@*@UZpxQlZu%i=e3-QI(GO_0oGU-pXgYmP zs`m|z{*``$MF$ZAw8uD@abP?$`WC(8rnkpI1Ou})`ZqyjAxsCu;@f{{^i%pz?6XT0 z=U9h-zfFMe<@TDd5PkG6eISD3XR0hJN6?Tm;G6R@*?dew$i&4H%2wsmcFsKS1Pb5TGpLV!*ho?Z%dl}JYVAl;=C8rwyU!Ca0L!apSM9}HL!6*n;D1z3xS5l_CQ565{MfH zNUc)lGgoO`&A55^def=Z7}XFqWiOq7$~TZAiNZEgTN84wbEnWEw%mlSJ@^haRf_Uk zm$HoIbHceJvio1e60N)hgu`coFL!S2ON0 znwo*CY}X?QpDURFq2t_`g2)aQ*KR%^cdk)9kB@`YBp)U|xK-nI z!l`bgPy*`V3!xjm-ZoeinB~sQK)Hu;`QUN^%tnnDTPfMB@kN4j9F*y1rFi6hr$(33 zWe8#<0(6``S`0tiPr3M81crcrc=&RNZ=ZK#GOR23M^bP$5RFF?Hn5e_SFv9Ny}-K0 zTXPa{bxL7nKZi9oIAV!E1@q!TBD*1YqjtPh`0=tcFmXF)< z!Mb>p)Od#k%424|@i45f^?9*nOq4A_QU*+$C-O-kP!Wly%(SIE0kA-SJP}`bg^@~F zVL%zX8Fz=N`R>NI8)0$U$7z}^Ti>nm9@)BPCQ@l5Y)Loll|QfXUL~NSW>Zfxy+``4 z!okW{3uc2OoKsknua!di!VpyFjF{olAEj^z@m+8-U>OL5 z^a_mKA_oN{^T#HEunD;O(y0xJMk8j(K{~B@q->~j(xQjSLvbf_2Hny3By;!l#t|1= z_Ls;>`Tc9@5R+pspp>Od71h1k{yEd#8{g5JNF$VwAe5)#y1fjNl+ftE0F3nGsB;JID1xA`$?5c@b%=KgTOQW<>vHf;%pJV#Xl(@Mv&gEq94@vedQ&08`r)-~69wRF`6SEbE~S4(fq3F43!_Fg;gi1wnN&->GWfCX4TANv;=)i zrLXP;tvF7N#cMJYtU5{yGt`1saT`7|wBZn4e#R9Z&2v{2^cpvLcgpgR^Sm9XWe1*PNf}WPI{s|d6w$H(0_yp7a{nv4z!6Tmtf`B;k9IdneN(H5$wE?P!OT0vLg!u~LwiwpjFbd(zDC2FEqX*K;L#=U{@ zKgHaSsD&p(;>@6RJfGI9EG7Y8WF?nHw7v@%vueGBRsj|8Jo;PY(?NwihyD&NC%kx$ zUZz)n;MMtb3w@8ikFwZ8J+S!qD2pzN&_7^Y5mvtx9=K2%M*-}A!g>?vpJC<=^7aGU zZa1-;R^l&9VeKO7r#a-!>vhv!>Ce6^rJ`Z)+_XP$L?ulaWrUj!;1hPJ;!`Ta>wb7T z)^gs@aK<}=`}p%OC=3O8{yjn_jPMkS@CO}#$LUAM=(PdBxAf6XH95F91L&`}Rtw4t z`zY!!v{CN#I|b$LK48>squhl>-mLRrwb%Tf4E_5H^sXTP71Eq1=>0lxA59X>e(2ny zzFOm?=cCQCih}2}Vnwy?tjRV~1GuY`YNgG{VF%3xQYwIu<-kZgD!PL%M~=5rFNNrT zTIBi`^xup3597^~v`yh^ts=^&vbdTHeEte}VXU)<{+s>>R+eMjuYnvV)~uu7068wq zKa+lopn0(V0{R{OFQP1XIZU2@0LbAc8zo^S%j#v=PNn(^D~}8mLPVX&SK!-~Na0l| zi>tvh*ML&5rUtqWK;I7%ydEIFf#P(4_R)=W7oLak{Rx!DlXNRi@7q9rx6^xc2jG1> z{h4lK58cI6=x&})_izB^BPqOp1UN!+Y%-q1wS1Z=5L_HiS4V0g&*U={8CODA2n9P~ z^|WkjANquhU9gtxd(ZBZLh2@8Kae08<{W92RpiQs2XkCm4(|5Qx#J#*D!>gf3;vtY zCt{&Sa#)?=vs!9ip+NNsKD+rOz11PT4u`Y)IM=mEyC%cSYmV3%n4$E4(Sxw?5RQ+B zp`#vwNO=^3ncRb@@zZa%Iq?s$ZCs8 zTWVe=(F-3QqC)3k$6?zMpQ^B@ahe^;GhAjHF3TF$po4_F@(lZhT%3F!H)IX3kdsx` zL=+lfd`bB5kg7hJSJgg$+lG(VC>u^*YjaCK(kyHnKFPrjMDcZ-r5EAF zcM~TY4f4j zf;5>gMFK78`l+BFxi8Efx*W8C_6fp3@>`n~4Cg5bhDe$ds+KQ9X*-tZQ3rx@$PUWh z)`dbGke=7r(sREhJ#P@Xxy6>72bA38EeEMtLKJ$AyO>0KF3xFB-i8wxiu5KNvmASf zX2_S@vv0kE1uzmEg8RWOU8q?WZY{9`ZDKowSg2iaFWv<$9ZS#UwQTSLUe%0O2|@92g=` zc%QfcW759C9{+Y0YM6HOA@Y{+ZGGedyIMDq621edlXvQWq`NZ*!7c%qQvvKMnvKsN z0VJ;>qd%tk^tuwW>**)BguIDk?``nrn_z@DAT(a5V;J*1%IrmO-mCOJ{g6JOkLhRZ zpbyzcKW9Jv0;2XKUPQm-v*=@9j(2P51HO=c!x8!|C+T;*3+-#^4}2U)w*dE6b!jah|oa4)9wdvpz}eSICCWI!abU4;0kOJ{in#}pYll9 zWz&~S-Tg&cJ%o#mwbn{xRz?+7m+-?PBKXlGgBhJEZ?PhKu_9bIt;MRjCVO?5k1@io zx^u;U;G$nUV$oL&F8ZbZ|5mECXapQr4ae$B4YqXTrVuKg_kyPSt+u@|WF zQ9Tz?GuoTbw*_)?K-lZH`+8QRNXybF1$22@3IG5I2moPeZ9iUa6syAt007|>m%%m!Rex6tcofwY{x8|=Y$gLN!Dtr& z7e$gR8@7guvY`k@z+eKD05*WFlik_P!0zn2GeM+cX#uUZRohxy`hZef(6(3=*if;N z+6S$DzwN`e+SXQE-><5m{qM{s0fA{N)0kxW|AW*B}RGdbzv|;741UsQqP0KL~YxGQPo_n%cMF^)W7$Y#L zJPr+GF-~A~+HhLB3%P7=u5LTJbO^m$Ti>vvZI!-8k7aZ#9qVuk1aW1Yp&=~!Y6kdh zf`2X0vxcqUOo1_jT#I`w$IKdY)+UU+W9BRc69rCtGwOvsW+rJA0+=MQ$_sxumkK8a zuSYeRL&FG+R51zS}uFRCwFkQ+~8JLT7N59A{sA$GJ70eI_zFC}x znYcheVLtR23scORvJ7huU^de|CJk$mzPad4F6sB)|8PkNb2ZHCS9O_Du$dbHuz!Tq zSXvh46lbj?9dU_R>#>@- z*dp>d+#uYt= zl`w3%+pWRGD%UBNX?HKpvm@3om)SZE84X!j#G_y5l^RIo#NFt4EhMku3KR%ocjvC` zaCFCZ)mCrGQ-Xt)w<87upk9w%__Nq@CQfl8xL zkojec1b_p>E`TP@uO$wUD8K)3rsE>O!Do(Z3_03k!G^rsMw97)VWt+_FL1r z)f0Y~tnqs_?3XU`XACRtbSwBKV>}f2Qm1e(n|ziDc^`I3tOL>k-{P<~v}R@=)sFb6obJg$|~#9LWua-DNk*lrHmxjMY6(p|wO~PL>l9FPzO~p7vtG zl02y3_hnS&pf3aXqW~2Ai9YI|AiP`uVbsgVBN`r+w=a!1ZGXp>t@3Xg{*K3Z-^*#h z8y_Z>HE?mX`6mm0N_t%YPw+Nw(Jgslb8JX@gAK=;e^XRlI9HJCrGtWKz~dFApd+^@gtE>g>S8-O1cu6Jhm5sySW?&l~0wwz!)Ap9o1Zgy<^ZY z+=Wr(WK9sc3Styb(=izB!HUroA^Go#?7=BTysd95&heJx`}#@SFn&KKbneGloi%6o zqRyj7ikQ-iru!q3PSVsO&fSMN_iJVqaiMo;4p%i7^MAp^dolkWEO1elcqsGb?lNz; zh<6k5vIxm~5y?nJ5or%~brHE4r*FYw!tA;&To^1ulj`M{UG> zT!%|=6G!P=I4N(#BJAQ=EWLB8XKl$l+vta@u#UcXpX;At(+|Oxu41@72Mv3aA~ZVc zn#ncmi+^~3d&SJ17#STG9oJOE#*R%DVqCOu`y0$05k*J?+ACE*8`4bH^?Zd5&(Tb*M(BIUH&&K_dE95U?Yc!}d#g&jh zq!jVR1K1G{gaQW<-H#oeA%7$q40t%fD)}opZhsFcz4#iB`9pysz8(##P4TKwAXHVv zJqNHmu7*@sjoqE00Qss*{MsOYRWvwUD%6X;JRJzBMeK|A1%ul$D;gZZbKlsCY0=kO$8Z%aarJOZj0eer5yR2OkSF=||l-~h&`0gMMcj9>|4)KM7U8Nj%EAlU0L zo`263jLk~M>>34 zdRXFX@Dzvl7nuTwaJ2|xy=dYzJslgxENl`>uvx6ZwPHQ4cN6GFmJ_p~zr46&laRS0 zuEh6nkm*%LoxaaPtH7CJC4Ru&3e<^9@qZv!KE%XA{1884*-aO7@MHXhh3g0vKjp4M zEnmma@N)#H--GxCX;sv+55L5(pi(adzs7G+O^xL0x70``?o23$p(%JsRKJ0xP!tFS zY}OCCPyYW-;mCr+xWIkeaTs&m#qQV8!ha(0ldaScR#3A_QtqhvL)(5lEGv6{FMs~r z9xdW8TQEYt?!#Z(c49@T?&p_^{vTc)FOW=)gB;b3-tp)_3wivA;{O*PjYo6=S=^Ir zo@AjdsF>#4iSdzIpYQU$cskNp>ucJJ=VX6)u80>l`9!U+k(KwzU~0r=Xz&={9%sjU zg01vPoX_CS#?$No&#=;;r-NU>wSNrujd%&S;$>{*`|Wte4ed;Zf`*quTfrgcB{#Gq zIhenU|1h-Y6YCW>pc~jAUS+r|*lo^s!|iiJF&34FF~Rlj!-p`1i&s%!3eFf}z2^4q zL;nR(O9KQ7000OG040KLKS+9%=DrpH0G2TT07w7;0Apbe%$fY@p`wVSQB)uP>Ot=+A4soJ9H|J?Ux$pTFMgde&4 zxo1EB`@;9%e&8EKG*4^MNn>jC`)YgrJ;6{2&DPpJe}6FC6AAfhW8uE$?rzH;ukH5v z!Qz2itr?xX51uG3BXO@AEYjtybGgHyX zRVGhYf5*!9&ZdsmbzRule8JjwH0O79EU=q8rjpk7uI3JqXl>fuw6?voxnq6vDyFe5 zD?7VZZeG>ArnRjV8<_H&BH>uv7mly@h59XnN+IF&>1rF(m|kCWyA_Q!?rse*t<6rF z6KFdaj$6?kzEEwOqN!xe>#@R?L%r?Yp}rave--Vq@u)8x^GAbyap#84cjtz#6kT?!euQ!D4EGv>JJ6O!T2(!obrnGFjrF~ zVC50hbd%hatJ6%T8QI9_q1jZSQ#k}pi_jGE#bPE^(j0KpW5qjzy?r68-Pa3WEGe%T ze<`foTy4@AGIgqDs?1(?IeDf@`Es%@3!a;IMZ$7sfk_2&W)V}xsm`1qk1pto278r) zmY6hF4mMwC z&>)1btGAz-zN)&!o?Z`aH-+f2(|P->P6# zbp3ggzCiyc$y2J%T6oZ`Q8YM8!t&5Qx>F?mB2(!ofg@q^B^2sf+4yCXzCw4yNqn$e zIH0sT^2CTKM^=>_zh=@sbT3H2;UkhpPiU9S?2Mh@bbt=%bU#GP?u1DPlbTv@mlT5z zVO*<TM#v9)v4X+lpqRDn(J4_ zMC#4jEx}DNytcbLW+6Vf6_J9Z2mPH|flYy!vg4x&j+KD`mc2+X^?4%x_r#>%A7;rD zx=YfM9{M3YuG5d0CZ6E()qPPZz&{@5RY}T-uqWuJIz5R(odx?QfBlSpuHa34oiA#I zp@FLM6RK`#^+5;x*3i=?eTTj)_Z~Cp88NvVQyWd{yRzEvaNMz)%!%3N(NE>aF%jzplm1A5f)G987fVMC$Se`0(EN)@ zf2F@EU-O6h0|L9|f1H|Ghd{+9A@QO~|DcyZLI9IgFNMuoW$9&;UXdk4f4n&ykg3;9 z`j<@QsohXoz@Rta;G0Fbf1C6lIu5su^>0mH2972qp_hUUO_5LtPKEex7(;I(2ht$W z*=Xw~vs~qh1usL**~d9>e#V1lQhDpie0?70nykx79ZYukf5O{kE)Q9gJ-{+qtEaWK zb)6S=Z(v8`=@rp?jUDTV`Wb9W^}x$0jy$sH#7Xd^+Ro!ST&;7}PKq)Ute^*s$ul=XqE0mU*yj%{r)dApt zUTJcpA|WqDR91smWs=Bvv4VubEt!jgwKM30+Sw+z%A>m>T3Y!gs z*Ne#I{9tqFlves$6Z-`F%h)Y5R5bhvRrCHSh9UeNsyd z#^YvcTO<}&WjW>zM7-fh+`G-U!%E9E)x($5ogUuHSLpnqWEd!dDM}`PSUFdp9T@-$ ze+YiRW$;H}$>wM@67@zMldJeba9s@;g5V;zCCi93D;29lX)6&wZt`{f3A-Rm0*px= z$Vy3849W6(zES5J0FbgeVDhKrbus}PpPwUw*q1wevq0y{4jq@=WoJeM#D%j;J5iZ0+J$v+$ze4t6>Q8N?y<) zygh<~^iFsaB*@?8#{^JaYA{|SZc)`{(d!RQ{*hqyablQluwNvG$dxCVsP9Gj@iLeN zf|n13!+DiwT{Nk|JZws76U}y*ef~tGWgffo}Avyv?jX`Vc9an{G7?Z;A8gV-IAA+N90UmqyibF zaSPLmcO-fSCNlVsOk>g$kg4Nck}2aBk?b!fKhJ-VSAJCF(&VY1<#)tQe{#x-qL$?b z!{R-79P>X-ewkmvlw@vEPSuIOlmHYp_5+}y^mVA+@}Y!ZGx=XqHuaGGxy{3G@V|9_ z6aG5-DU<)BzGKLZ@`+A}yk+v+Qtw?t1QawfHI_8#mR-AbEhkBC$iqj-dNhri4J{YN zWf{4cg+XfA??$nT6cLsJQF>(>*n>HsH^ z9t)1EQ5%trDOL8rgKPib;Hcy0x9^Kf!fEJP8^r>=*FO|VgVV+u$tb^Rcko`aJe~FC zWyQxV1iH&AxC8DoCmHKOs1UKdar363P=DuI*&2NdpOkUutJLpJj{!%KR7QxVLeQQl z?%0U8bjwi~0oX-o`>IlKHq;r-(FA?{JPq=$`)(V87n~Zgv^OWIi9;S+ z|G8+_RpDa%!v--6f8K>>l*mEzX&!_<7;NJeJ|<%yZop4o*+sI`v)nD*N~ZP{{>JD~ zmADhcdw=Sb>v66j9~&m!RX`1Wp~TH@hIe6-LAzviKLcg5S@Au>w2$Sxyfv3?%p7Ev+uykd z#Azc-5%{_mWnJJK|4!y}bx2d2QIFd2AKiv{F7J1T4r1jOWENoH9| zz`>lbws=J8?_@fQM=6ws@T5y@qhRe6yDdpX}m=J2vhN zlzHi`T<2qCZIKuiTs|@Dcbxu%#ANNxPh{8H4WJ0 zpb+xrC|r3c89*?226gpm@4&WMZkj5Np=2<6&I%Q|A%dprJSop)IsUGztL1Vo^rBgG z`a@SE%{i7|>p1oP$42ahAN58D_J%KR&rmVa+VSClL*g&ewhoEo*$_~58Q1N>|9Sa!h*%NM<2Pzngz<1uTb=AqyV`>=JMyA zt)j~al%Yhbb?wiGDZ*UKFC}M<`W?DR8UuHePIg45XYq7`>p|l+4(p;nS-`zAuJV;Dw&`_Nrk= z@^NI=Zc$)+^9unUsRxZ30>b6xOdq%WP8;rt2eyd=U3*&s*OzJ%*Hb*<0DgLv^mK;A z5#1kf>mNK6Jr^Q26+KJ@d(f|MjE1(xI-rKlfpd&%D&XYD1zzDw0_TNe7MSg3Kr>3` zY%&qqn_5E>Tw)Mk^wok3QOtl&w6X~Qeqx81UY3MMQD}17|=PwyXCXjYO8YB2xukfiAkINP*;O^a_0LkRZS2ui$n>&PJ$7qN(0iEI* zk%A790-`CvGyVMFQHsYcxO85iW#ttpOPH&05on4g(C#P@C6vM- zeRY6#`4jyT@7EBfbIfj5pw)NjYrV9DZN`HFm}m-*rP;a1IOm1<0w1o>Gp>F@4-16( zPa*8Jpz4MsqthMgHy0x{pebB`ljc(je3C!;k-Mo4TrA}X6*>S{korXT_XFWP0xsqzD0nOBFO;!@joT2{7dtvbLx;w7}g1xDeC@MkD4srlVLXK180|t zBOkb#AG;e-aq=uCK*a$pWL)R$&mTJ0uoJvc3@PgmznqF0mM;>MZwBL(FUDh;>>r zHXVbyf|j~Eb4Ev9%+JGK)|K70>;k&#O!I!4CB1b`X{APhxi8_?Ejc_)mM_UTkKKU- zu4BDAX^oG`Z8FZ6KMGKwI+K<6LlVm119tMQcbYAe*g=s{fOqg4UXYdSLWnz-px3EV zu<0G*h7Fg%W@NOrvaTb$!a z=f$;HzU~F5-vz?+tn>a-?+ew+BGoyMKgJsOC;0tP1f*bF;0Yuiq!IjVG;X|%<|^+3&hu|4k4u|Cv(fSKbRD53tvVqX)nX_l3S zFj*i(517R`)!3~;j-&@=>n5xZCkX6Ne2*8axK42RgSXB%EFd-ofoP-Qyq8V$Y(Dya z0glx4$|vUf=KMn}aI;(ThXjED#I9NR#hiTQ-zeBIdz?QtZ-$@cCD`*l<(|NSxWHvcTzGkkbxd-2wJFbo)J~&-h5YH~zBj zo?wPK+INqjb+yOQ$a8|OI^=xa0h6JQ(jdiLCjo+(X>sH-5$@{6!Yje3S)9GC26*0I z<~xqm!irRWetTEWWKYP9YI-ncel){#x_prdWeJYTvaL0ZBSqMPwY&pTT%Yoe#C$ta zfdIa3w$m(c8oR<5TExX$+0Eah{U>ld+cULzJ)_sORTznpCu(GT-uDklt4t?UbpCaQ zZPZdBn_oTMAzUA;9*fT8NG=})j=)|bjzSk?JWjRJ+nXh8)F)TvHN1S8{NRX-1zxpE zwkK%Rrz=`iak;hf9$W4m$H(8kIr{r-VE|(26~)RUr_EEr`@Z1cWb0p?8vFB&zLpZu zkIFwNRU_L*M&S`*Z~1)#qW5?6|5%_t!8+jSrAaMxZg>Qaw=$*+LYGEo%>a@Lz$r@nlFgNBz$$j#+Um7!F z>#}}!d3t(sJRsRs|D2Dw`BN@Hqb75d;pf(hL0Yg zb$@d;c-#(@KO9Gwo};ckTkZ2iyj|qVf0`WN7gOufWV1pm%&1mv1QND$Ozp>i$_iGn zm8B0&!&}SwlmJfgU^P4KN}dfUrCx?JX| zT=P&UmNf}5?@J_n@_luQZeKq>6NdD?9XNiOy(x&K30kjPoy0tHm7;a2vzzY818ocm zb?HvWHb0pyhdTZgqCYZU2&$xLYn`mZNYFQ6reKmKOyKx{!fmw@gM9K*oO={Qk5}mX zNcvW$xy&A0hq2v3KWVeT{FLAdfTZ3=`qAGgc78q~1g(lwBHZ|^Y0}MEo}BKxUi+%H zIa^VMi@r+Ap|Wd);Jp!rt^2=M4Pe!-xslm)%r>nh>7-Uig8x$g&a*r-Q*bR0pV#HU zVu4V@5Sld+uU4>I%AGucYYp`~lYeZIWdCgTc~!kKD9ypfRsgsCd{Wynx6x57)p6m?HE=RR;+|<~fZeui zSz)s%Poe6JU+jYFF^$r#b;dlpxB@vT#PGal8tx5_SPh(xVBjC_1ma9$a@u5uBKSyh zSD5ZAQ&)abLabIlbEHl+qm!7|IpMyx{Jzr&cPD?BOzwv{7L34SGnE%c^!Klz%V@UJ z1V!hXa^lmnNHYNB0d*-@UlV27bNBre0>U|B^EZL>Sn3zmKJGl$2k8VIs)11~hg&ql z#j7zrhQoh(QKbzI{cfJ>J`*T3<-vu)a5A|M%Z_=-STL>-(`$=E=M_UtS#u5}AmF8| zQJrz{?c?1Nq+ly!DsCYQQ^x>T)x5*+?nD?`hS4_2s@{bPAcECAA)bZ&52(;z#;Mk^ z+78)sb&_KoFv30)?o4Vu3>TYz3RWE&hyJHxc%5QO61q{iVAZ;nZVI)>SZUA}|C4CG zCSM^9j>A5=7O}nwdmFM*2C$zsNmXmsx9Zf7*qDx&vGI^gPJ6g8o$Uc&6WLV&+)Z^Mtb%rIM7 zCA%55b{_pE#b~>W`$1a@GE~#BANx5JP}^<9T~<*dZ$ZjQ=^2(C?jP%mqxFJj|WfHO-zaB-$1wkmpM9eIZ z7B6};^;EXHSQsNMSkah7AToJF>kA*ArC*OGaV`!dK=>ds zc~tp%8sNqxT-g_cgAK)^LM_o^v6QJ2T7e_Q>q$dZy2y7E*&eFYU=>fEtQgOrSxXC& znN>%AdmZ*7+R1J&+Q9+M-oX|BN|S9{*{APqOp*0!^!7rH;}aJ264OKId4+0;qly0% zsWoP;4wi}w*%GM}6Z8LRC<{DVN=vK2DiR0>`+*;yVr#;sR9>?R6G0&ny||Udy8vEn z5S~^rj$MSO4j|SY*UnJJ17u=4p=%z)3c12XsVZb<8bL@`71 z;22?1DZaJ z$GA`H++h3Q5nH}fW0jyGHWMqT07@uXeD^1mfd0GZ47g?fp9O_fR0c0rZxcIH)TY`i zKYTh@daevq5A`Lj;A}#M_`a5`h$3E|ZENI6e5xDNwfO&9eaAO5wM}{}=wxuEB5D`u ztYR;>Y#=vMe|gG#7JsW^t31B2k)>Mgs+6=(Fjo0p?uzCvH{K{>OC2fJO4-IH-L4$L^ z-T}BK`;&s6)M^~q^4k3EtMVG+G~%qeW~%zwZF+yO@9Axd5cJ=BfOBlatcMmmvSd@x zeMC)|^svuFu8Hw9MPOZgmthjLzd(_K54Ywzwjbo%RY!adDG`hRiu>4!cQ3xBQNByX zmCBwC=BOFZiuv=u|GpkaPq@}-mt3bu5fW>JeR++~PtVcoDk=Fh^LU8102;YXCD!<( zz*|{K&RjHxs_Zw`oYh-Yd{2qjwR?8;7cti(;E^9b!?u&7Q*IWC@PyfAjdjc_t`#K} zd2J!)9T=At`nlb8XDU5=iK5yyiej-`f?pdWM_S^K#8C=yRzymeTJ&s0c&`!js*VP6 z4=_A-2DcX2KjzojFf{m*)?RKK{y5Sv+1WKSyC7qO*7L*vzXxAWxJSb|(zkE3ME`s5 z(pO)EJ z!!`erUJIx!^)S+~49u^P2@*jjQY{);u(iE)u?SKl)c@X7ONtUvn2cSmJh#l#3A&Xtvy9he4gGsi{n26XaeN{a6?1-PHP_MG?3S^$iT zdaJS0HAe$M?TvAoElq#x$uceAn?(DZXg6^p*&$GopZNsmmGR?9^PGGjh92>@PAt^S zzK1h6r5cDDx!T%}ID`wb1!Wpgs-nvLMqLY360W)GTQn6eEiD~6(o{-F$a;Z6RdGb_ zSt5{MAfravF`(9&i3@cfOt_YJn*+j?8nEX2hkxA^^`T(M{G(@A`Vlu*Mq^F0zbGRo zI|LsfC(KAgA`-z4yC;iUuQ;4-9nmkmu!Cid^;FM_wmHE479@q~RDBXHDzX!VN|st+ z%--X>1A|(WN)v_^=@xRzxnmwZpI5cd9nE}tMbwx{DV%2V!0Yy8Va0*1*$wDXrIJxp zGuD2O;A{-g)!osQRGXtrj?Viq0#k;shYe8rI!-k}C^R@pol?>@gZIz+RCy^zdbEj2BpBcV_S?rS(mX z=&Hz1wv7RUM0`?~ZmDjCm*I>!7%q%f{u+kTPD1=k4spdqg$^^zYBvV` z*yPJ<5@bMRB6^P|Z8r6H8o1SG!5&($O7N))E(l8-EK(VRKJr!4IG_%T$4VHIo3q>; zyi%9Xrn#E^ne4?T?I;siG0IE&mU)Hyz+`Vvt*0ds!9fXWsPW+_8b zlAb$31jWSx`Yp|SEsV5DhWkP>?v?X6RlZR--U<++#MfUAO)XYAXCdb=SFCjs(XI5K zazv#>PFXkEypqp@p6wW#Hz~M5$NvcaAY8%Gq+JArhHeW z!Lw+8lsZ_?LFNr}D&u%V023KFfyCzvI^KKOR-6qJ@sOnTO09VzF+ED90-&KpU;Fak zw@_e*QbxTf_X!h5l>5dW4i7 ziH(vg9+cgQ_D}fa47zNPaeLN6a+R?8X()2@zklM9COncIQpx%Fdtj4>d?BOoQMWvF=$5(N7dFaPVQlDL#Q{x>A zwbJYetbqs?sbUv;>SRysx^Y|+N4CO%%C#y)w^EAi=+7|&b}YooXpWUFW}0%2ih3t= z>lT`U%kjeK$K}tyO~O^=P;UEt{U71_q%#h5<|zrGXWZ8XKBHJE8t!DWvy>6eB2{oQ z+L3=zp^ZO=Qymy=$u+S#gw872lOGj!zkmy%Sc#exjZv6AEN^Xqrm}4^xGxnz$ag`axGg$s+^z2;RCs2E2J>Lg)&7}l!2c{Cluy+ z1Xe3`EXZ~&N3a;(39iU=yCE15&?>DQG0&MOGAXI4@G>rXX0uM(L|Gjqog%ZDwM8}J z+B`#sNmA&{ljfx%DMCr$5nIpja-fsDr$9ije+>jIe3_YT1o*g?U6ho@tlBsZY_m&q z3=;y!aes9NRUhvCMlD)Ox1t`mCEc|MdBo!NFLq^N67yF?s^|erxl=i-AAXA_jpBRE z!nJ`Jb){kKM=s}3TOEJnOC5@(Ci?AByxTKs=Q$FyPnQ*_9@IIPM;xC?|1MZgn*na} zZpOUmk>OE4(}wlwc5Cls5=98}*|T~Bi|&Pl$Q#Su%~N9YGv(xk-UeGp>yL0RF?e%$ z)x#W^8^n5*K-lYwAn}(83)iZT8fSwlEHw)swvjS-5yj=Ap(ii@0!iH^LTc zR6%xjYdhPaTuSj4_NokQ~54*+j(C z6KiS<7Qu&Ftl2NjXX#ux)NNzOy7W+~ahQ>jJBY#4;R9U@)(8p0O-UEmiR5l*^X(zFfex z%tgq7#O(7&O(Y+pr6>6{hx%nVs2k;JY@A@%jLvF$y;0|a{!I6gi}eJePTE7_4_wk2WZ3xtQU|&Y!b-B(g*+gDa>{*9P_?0410=OI^<63*$*g$nx_ynej~ATR3a@SoZ+Ke+58T$`!POE()L?^%)_`wl zhVytH(pU8Qscuu2UE!3?w~a>WCrED}kJp!Xq?bMXCNKFY9YW>lHugCtr=8 za3N`$)rFoFfu1RVo>Taqr%@Z_7bbrqG0u(*vj$3PYhAOU9)aH^e%7!NKDBJuXKt%M zgwO=Z1h(?`1s7&?Z`YB`K;&# zS1)7kYI~D)nr#KGoe{0uZT;ps=h&ftooHF}V|Z9a+Qoga-y?WU;4}bmwi!Ru)cjTCgpw?p5?mOBbd*_7%%Do-?b|KgVfAD*Yz^u1q&#yajYz z{G&A8OYuP9!b=H!LeCl78D7t|ARi*et=`k$$>)CI+w9I5-huRk>^g8Jcb!F=ugYke zV+rr;17@`Rn?07su2X}61ayf)BDp=R@$N2`jj=d~J%Ov7;X|yJYyBsLxk={g)qSkQ zLU%4u`bM4}0%E#K5bw{Z21vZXoMJYEOM!r`-L9P-to0C{)TJ>3_dVc>{bpr}{*A$J zNY6KR>^wz-f{4EyxtDWT0Nxod3MsE=?y-9g^ejluj1+)Uq0|3n}h zcvSdqWu6o4)?bZJB7uZ|5Efb&qg3@txqpCaZ017fWWv%Fq9=<=0aIa}>^6Ju@e3t6 zSq)?O=BL&G?Bh?`2|WylC%OrxMH%(hNgCZsco}D&(VJspGL`0g!MV};v=WxtCpxgM(c&Lq8%< z+9|1XkRV#5)4fJsZR~=2jvQ%KR2S`M5IYGq;WQReCT~W4N@um9@g&ogX)}2wav0Oj z{M6)AR+qTCAvyl$B=;MGPrE$2#Aubw?2DUHotKV4fmxZHD%u=}AxBtg+tje9`l@Tzb>OJYkg0<7gC~dm+GDR0PxVb@oLLQK zm3f-smWp$lvGgALP=&xZXXZ=?Xsh0Vk@t9R$cte)lNwAf#!AUPxPHSH|8u$H#5<;Y zfws5ctQmNy6oFX-!r;{Pip+h5g5rVQyp6sd!@}8*^>y4m8V(*AM@}j*;|Pqk+>3~B zv%s?H8@s%r8?T>6D%v{``XrF=Ej4_zS3>X;ih4ruMei#5d(DnNczMJGw2KT~qglVw zcl;7yq|e!Fa@hQnx7C#R^EdC0$>G56CYzd=ib{BHJHFwAQ)F=N@0ZQ-aF8^ESxP_+3N`EtrDD z&ZE`)R=e|2?ZxQEjbF%hpB?dpQfHb7u{l1M?-yK;yq|~bucNP1{7|_2_&c6M{(0*C~H z{59Bz+MnNI;Pe!8cQ;=fz2B(xJ>|4Hx4AsE(cSQB?f!dEw!LWJ%Zi0taw$zQ(k`XA zju%rj+oRb=fra0c=>Q|YntG#Qdj^!1Zl;!{{cM~D4DWTS9PC$^Dj+&(=XP93wR0LE zE6(pv1CAS#A|1P&Eh%xr#Ae4JAk-H4K)s!LZCZEymjtoJvV!MDxy)c96@4g?!?Xk1 z%8bRTvmXpFO9Ejy!oiEIiJ8iMJuK>z&JgX?+uN;o^9&j&QX@+|?Ph9ODF6zE^ck22 zO~53#w6bj|3Ur)lr_Y8$9uuwVLT_O@H@ceDksMFeuslgtY?le_c5ptcuy1rnlGTQ6 zBQ*vBauS1f_|^thtpuY5s=`Hv5#J_^6r#SYEQT!}m#u_Fk>(2TeWG7@o(3jqiwBF- zT!Gzb=%Cy~XD*TUa95|u83@cZqVBrmbj%h!Sr`;YJ=_CbQX7lu?GIM=UtVi!;^fZ z$#@_~%)cgMS2XeP7e3n~lVTh8{W#g~Li8|R+*L%Ua3>8c&H|>qS1)`-J_Oy)B z)K}o0tC2VT5>m~NT*-nTRL(OYjpJ9+kaMtL{*L-|OW|M)4P)9?e!oN5@1nJ!JW4xX z=x1h@(68PgvTS99+k|SL=6o7|-GCledlVOmtkjs-CgDH829Q^&85r7kv=`{&dvt`w zY(x6XfeZ$;*C~swNR0HZ62z2BzNLm`IKnCQws~=4$>IN=Fm~wd>8|tyHz_x3LnYKu z@6fIJ5)Y^yKFO~e?#lbbA4|+tNSiU$QKx+7D3gD>=rNT9&8Z#OLE1UXr1*P7+?oXi zIE+SlJ9Nz+0U+$QK$3NFRFbztNc@Ko*}Dj>9E)=ip7s-81x=|7?+j*mh+5V&ZBLr7 zCNp2t6(vV3HFnHBTeHf@5Oxd;ll|PQn#52my4xO;j~*2-v1Ycp6Cnj%wyL(DkYa*6 zt4ZIx4EYC!zS$w0Ue2IQror|2K*cE*v(cr-RiS?e5Fkb&SQX^8+RwrbwdC$juMUTe z`Z|Pxf&FAfpev#-!+~g^s`YGrwr$Qnq4}J(IV<8Z*?;Td|0Dm44A0t9U58uTw|{$M z9tpFraaQZ&r}*%<70$78(o>FF6Y9YuKWaEn^9M`@CtXHgT$~(shk-lX&Xk`&kKP{3@FJ z1-&~1Z_=z26i{_A^MxfC`wEQ~k!8G2HfV%UUNM+&CnLEu`0%z`xUxrMU)YnUi**9v z?WsVTbQ%dc2FPyd6oQ!j|1lBX}i=@6&;>BAQoAd$)!oGIf-@el(T zT9ZY{aw+hYm{84yE29Tqcu*YLap@;xIr$qruv#{83xz<6_Cy%v_Wp5u$&Kzm)j+H# zG2)pobuV|v&@vl!a#0Mjco+c2=`cs6MbxLoRaDYuD{_S9!%mXqm@L^$lkD=*P>XeB z-EI#mwPzMOdZ`GF@SRGRwTwr#Ej8}xkfREVM^*T+;4ON1!~ ziDfT#xt-=NuIV2YHXRz1J65~<^M|6-^^2W3LIw~a(z)pxv|Af@7$E#q851{@|YtIyZbFv()%{@^OB_`h-GAyo{&MMFOPVM~lU3d2N zLr$#{c)erM!fa;S7@rjQf?ZihZK{=2>)|BHgvCtuN5uRmBX+^6RaEniYm4f!3_}rV z<%Le*wQU*n%$b_)$*dUgXvb!nFoJ{*Lfxow7X`Be-&+Ph#&W)zS<88OzvxSA=3G__rl7*gx{t-^-Ld5XG_w z(-UR2a{If39w&z#|&I+QpHTJ+CB>z2d#68v@-l0|f@{}P-4%y)DRN>wI;hf^#} zOYsnNynJtu4YXzzk0_V6w!LEmRjQG{g;!JWqb5l`4$H7Cc5Wkq!Dj?ATO`5%QWik+ zhd1wLYC)h$n)Dc$y=Uer<(L(t+&et6I=yHtCmn@|fL(Wd0<< z)Z9!SSdZA3I6G?I@{b4d)hg426c^wZRd009c5J2$2MKI~-Xr%2& zBkiEj8Idd+RAMwEpC3)%Fe=o|E)xd9)eqmm0=ke{{zhF>!?_fSyEI&(zotCD2TwtB z`ie2+4QnL!1nV-kYe0sJ*Oo46=onU0RyaJt6LOpl9SDYPX~g@J`77r{c)P1Wv4uM5 zd3|>lXPO62X-1G+A4mu&?_Z@^B-LBod(LSp9n2Zes(oR3m|N^Hv#aknQ+wq8ZZlZi zMH-nixLd5I!%q3FV^H2Esik83^j^}8ZC4?$=$^jgb^srg7ir7wgpUxs{4uL-W0$uk zHQu7Tjhj>dS{sWNUm2S(lK*I5l7x%nh+i+a5EKH>I^6{v0zx6aN(dizqS9a7ol?}s zbf2$wfrf;m!HwnAO@p%7^`mrJ;1R9GU76sOUlx;MG2SOJKz;Tghwa0{LqBgkD4gq4 zC*`w`zG-axwPU+@yRYYm%k!M{D}}X|J5 z21EIVeU`naW};tx?ReQNBj&v6cgtWC_|htUk86{ij{Vv%T-BEmn?HPWmbCAjFXmAc zR(Gh;HePos(Xe+%=JP{U5(0-MiZOmYaUx%ET~{!{JDh|(-wV0~J>QGQ8}5#HlpiqB zNX++wU@3={c)WG}b62g@7Gbj+U|d(G!1b`_kV=9VTLEMOvh9N&?4%6(EB?f6wz}e>`fHB&mT5HRr-6@Xqg+zbpU2fHg)}{CfsbC`-qo_W zXi{i_Gzq|><0Uo482IUmkHQi5Sh_w$X2!>Hfq3f@N%;3_=WGnkV5c|^0qq+hfbuIk zbDg#0+bG!+>+hC~@U6Z^@38mB8PUfA3?u_&H>EUh>|O7M;jwz*$cS}nbL+S)O>($G zX&SAuS0%QA$|A70H!BKEOp#T8;Zm|FuFkIL*Q7KVB?stw1Sa@2*Mq;|q8R!9ZA;cd zVv{rMn%VVbeF{mq(ey?N01H7*nu*3xODv*R#qWl4#1D(A2f4eBlnNV&u~3L|BuQiM z4WZ@7m-AN>s+p8Var*VI_jmKcA{}*WQn$$A_Z82d;`A`fz7L1I(L<(TQ+mtaDeqok ztjT9zdJmARnLCyJg(p)U>{q!IDM%b$IKr&aYl<|SlDbxfSNVj4IS2&7C)a_ z$!fgVF`S4|Q;}{}fdLdsDhpaQC?3Kd=DIu15$6JGn0+TxarTa1>Aye*2AMXwVD@zPu=EdDTqo!rQpVJ`JOZf^^_?x|XJ<0eu}O-Y{;rP<5C0h|07`Z*>m!Sm#8;NEN*Em8 zPi;~={k`%%QT2?h@JM&N^5Z%%x5NqcM8LO%V7E;1p9lzs9>atMy7k&8pWcvL-%`;8 z_Rq{khjLIm{KVj2*q9ER$6q)K!MhUBpE_K2SmFD--c%q%z)Ao9{9y+mHu7&C0^XW3wn3xl~L22*LQ^x@Tl-RQO)!=ysLv zJGtA}aP#jTRK$&IAN_+z7Ea;Ai14!)op|Cfl*frR_&i~XFv>9GDB`iWr-q_ILM(e; zZ3pfX59vQ-#k!fTK#9TyVH?!9Z)aao$$v;%f|LqkURa<=K0+yss#NA|*8)p=%gF{$}76EisrqWX%23r#^c z%`^}Z4q2}S(j{_o79hAboycD>RIJNFC_wDV2Wk!bat576S%hDIlngGaEJ$W%wAf^U zOX@UT6G_yEVnLURVXu6!2O0K<0{^c;aiq#za1y|Ng89#t22-IG5nBfHsk|8c0$+Oo zcg_jHTzqN>>fcDbE~Df{Q^k2|#`1wa4$j(m3fPlngk3jd^+YRTV9P^IKld|IV|L|0cXFCpaBM+3^MTS5^#nVvY z&~z!WVuRm_{WbWWr_}!_szI4Lxo-#Mh;^yRNLyof?1>-|i=s$tyeOv)j%v?3V%7nm z7QmKVEwU!!&u&N|T=rt6$!CdoW6F)zhg_!yyYKPMyJ*UF*&ZTn#3K@@I0zefqk^!j z9u>wr7@eudk&C3l?{dOV^05t#SMGOWBOXc_#Ix8c=dnSkPgVsa$R&ywxYz$g^NN_s zJ%H!fKZ!QMV9PWjUx>)qW`JXhMUVj|&T`mqvHO&mN98En>f8|t zmah1Dg*1)^@(qy_NoLR)RsI%)kZo4)Q1Qwox4GRHb!W|>8?yhl^W!bpfzKvuP};+G zvsq%KJK0j6#^qB-xC!ALCbTPy#c@oamnG_n2?)5A6O8DJqUIN$W)9`f+eHQLeB<-H zvcawc-V$k%766!GE}!(kH*9~f=HOvc?^|O2;_Y5xQ+lt;o!b`5@04;1iB?MbQxMI{ z>r*>$IGw}YFp{i}8QY6aT74;3%#QfRgAUSzlnZJSDi_j4`ZHH*kSa9$j(MjJ7oCHx zuU;w;y*t&W=NUMi19HTIi&;P-2^`1XrNt9{)htCr!&wAL&Ew~bqEeNNj^eyOY4o!K=$7Tl!8^|EYFE``am)Dlt>j;RV5Y49Nt1q z>pgIDw_Ns|bM1H3&YK`NZ{3#bdqoR^;o(}A)KocKv8BR9%BQ4md<1}XeruZQ(Paj! zHYOIWug(x5<+C(#Y`+P<)q#x@is64THu!#LmzIi>9N?Sk~Su*m0@FkrTC9rZ!Q( z+f8jEe*2i(#Qc6Ub?pGDZrcTntlHn~lMmegsr5iB*^or zcEB>7#X=4^GPuPP$(|Dz{3IQ?@FX2T^duXodLbQ{e32rLZdTQ`cqX|XR=W1FA^1CF zRK3j~6O+A0-eLPVwd2q1^))5kfqpk&v7uc=*d86SJV9;z6Cwt9XhN-ZMBd{TAAG#- z3^2yb^{DP%o0WK-z}&ulnAqdgUs@wrnP-<5Ky@B=@isQJhiFZYKQHW;(Qfm}rvF5L z^CVFDvIpf1-}Y^?BAHiAWcA4o>-QYPW@q+6wI$UEGYDY8(V%BmTApZ57kagUYB*$X z`H~hM{0)%xWkmzvA;^cGZ77e%=2je!WqrRgk;B1W*feT;AG@8wrWlQ@5yf0Dcgk)2 zoWA0=pHj@T1lQ3TS1WdO>)Jg<5zxVnrygO=hLr&fL#e*|vVuXNME5tiWKFxc1?F$W z|B#;PyO7Dzw>qV7o_Gk*tmD3Cl*9?hvg}Y2AZx*gfkyd};eA(_1%HHZ8*rAC z>dz`AE(mV%V~Vs6BJ@|93A~i*-|t8C$)QA=ghjAy7Y_X!)TLy|xT}}{lLp0+;|gqi zhnwh{O@|cFF3RcJhG>_z{&nUR0I7j4R9A+2`$VKY(gf`%`e?n~^~YWK9TT4DAp#)& zp*KVTF$7U51)@TbsnM2>N?{JZchXjh`ugS3>guWgt~c!g?h=S!X53TPMk)bqiYrLQdc zZU&owKju6)-R1~Jx=IRCmTq>x`t)WDa{5RTFBGj+L*R_)jj+Gy4^b3bykHqDFcwZ0(!wh3EjDfIg%`<%bgYOg;jSA_k~oy&yGGpP}TCz-CUIH%SIIxL0p^~3_*!T4y)47L)g zOy#jzLHR(7WT1${U@NW zPK;(mnbG8$etW$jQ2c?$+u7_SI7v8(oI$K~^q+^TQ-}~H8JUCdpAGwm!vs*2x`{0= zEv7Wa>FHL?IrKyybBAv)D9U~eh8_eUv7q(OP6j>%ei# zhd}+7PlUFKILV|WSrOx#r|sz0lvo?^A~6(e5|$tXC0$#}LCe(UmPVrCrYHT|Xly<@ zD9`m(tI;XpvFB{x^0!E(TBnmd18OB!!)12&EuQw`7pTT-Qon;oPNRTUMZgv zp)wB>i7x)$h9};}IzMGhiXH0D%V}2N$sazG6tCw_K&0Vr-;g28Q_x=fv(N=5ajU$e^NizAOaLT?cTxe)9VSht2uYY~yM zk^mdb&QL$EHVQui*B-E&G%s0NWr8t|k*7A$16hna_LBY;pCdQZH0=VD z{~*0h$49JjLB=!DKB@zmT? zNZR3}>t@*=d<0t?E>|-=b?sCbIT532WCuqzTcoL~BSD#QyZFcoS_U&_L#wKe?Lr;y zTh?X7BZh3AUWy+8*LlS<@=T-&Whd84)g{>R7O1;uFEcc>_gG~9bm@-36`8ttzG6j5 zibSm|!HmQs0qNZ2dUMEm+d4>P{GPh`bAXBD^V}RTtG_>Jd)_k`99pM7Kuq&^5I0{Z zuc>gMtjw$U+XQ32PCq%?yV^}l(vVV;>keZLlh5glC`B0XK#^1E%3FQF9WjsEWHly0 zGj8)G#$rvfc%I0W)9)tRB|;p2ts3=5{4ArM?PzOf;SM&3)hpq!zR_WV9KQT+@}o26 z<^Y0<-g$(LQ6UkE*@=Phhx&{&j(0u!rdwAvv&{B2AM^jt_Zxt1FMN|JPJ#NoGI|*hwO8l-H$ohe|z!dk9N2Wv& zxSk?El+hCJ@*FaF#hmk^fqvMT)?2#f(vcgN&tCu)7mBWYD#5ia4*bA!li^-&jJ$nVIl*&; zd$uNk;=KK!Zd;@O@jCdxw5Q}xAEN^-sFxA8i$8<#cMWx~JJimftv0XTvOu68>x*F2zP^ACM$7mt@YO#{4xfs=W z8+bJeit2|k8f{EPwi9+2%9Zva@`6dW_<;qG+z-p8ax|zKq4_xJ3yx1*vo@$EDiz_9 z?k_aHW8~sUmrm-$k}kW014yOwZ3rnvQGF>1!zq~HQ5D9C2vd$R`kXc@lnkdUa*uWi z(~2NRsURM+ElF}>6|Yh|M~? zs0||{B`=Xlpgu_zJh*U*Ud>M7HcCmCM$$Aer&1jiG__|HA5It&EV^0tUrg5}wvo0Q z`_mXZ5nirx$LgJ83*W&t3i$YZ8SA+4oVZA4)65yy?G6HnuFmUK+|_HD-B(5A%NZFh zt9mO|j%e&tEBLYgj5ZA?XJC;btq=p;bW^ivWu-K!CnxVMl1%QLV`B7oSw)*gQ`J

-JL-b5U;1b2PLgx5=Wg^*zL7XmO#?ou$CA{_&i^=gebFx?DC1yd4G@-oU}l zJ}o^J>g|cSglj$^K^u*VN@19$4~hRaT?~y5zZ6?e94Gj?jEa?uyId)c1x6Sp(*>V& zP3#I#=9{4&I~SdjeUhCBP+KW1&y*7JExGO9uP`p)^ghB{lB?TDl0G9RRrO7HW)>#> zg88)K`~bshFI1}1@5@vjd$R_JA5rO6r&yI@H(aQglvYlPmII*AHJ%+@NFnN-uD7NKAX!*(a5WC39Z5 z>R!ijw>2%Cp7PhjVO^_ZK+un^B>Att%Hv1Rjc3$L>i!BZ9OE6(ZE6FgoPnYl)%lXH zKZcKkO~|MZo}L*8)8dfRG81bQ$+=(;%2Mbu%bSn=V*YvvI`s2Mb`0T0pm#+K z8!_lz)Wm5;wI;fyD8WJ$^*>)XP+SR>EM3;jwoZ^lScGThLS8o6$6PI_p^{ND>i0!=%`+0OTGGG>~ zwkvjtYn+|6N`j$iGBC9?4P@u3C0^0J9&+mDsAoIca-hqG5X}v)ox=oHkS^mRV5<%A zz1;Yel_@ncrhXOS3ZV0xTj7agiL-HVPGhpVI%IKMRt|*n96AAX3}UvtJrzbvhTDkT z-?E@bO$w_|f2unTj}eQL@%?IiAeo>4JVT!1|B9t-xf!TC@^XkeahJD%KB|yy9d{A} zufEGl@;~vCo9Y#uvA}$Fp6Q46E;e%j*E`qD@t?s0V|UMBx*b<^&Jdroz!fdAS^R7( z*kwh}<}H?*K5zm=D*!U!?7E% zUiY@G`nz6|C6CmlsLwy#LQjueyy9#Ra0&VCDAAcM{~~k#C8W|7z-11PO}O1f^H0%a z$o)!JA<;Lw9%DTdvW50siRy`s_K{4f^N8A>0X!oJ14{&mtPp9=3~--}-g@+hfwJqh ze$0SP(gtOQP0HJOghnX-LrjRx=u{UThYOV?HkkK_vM_`Zk5p_3X#xkIqNU_E$YptflBdN0;|DBqzmD=VGnn^5?hvZtsY^?QuMes-hH;KoDPHd0WC91;Bn_TE6G16V+i)3GRTIlTY3mMlH7XcGQ@m zj&yKRoB<(u#D7b*RGBp4`a_^ssFVy+dm@#|J8S}I@`*{`L2 zAd55EIa^KNn>Bf#dMb1qC|x))j^ZJ=ImAiWF~-u&z7Fz^(i|^@f4U;`c@>ON89)7( zZn{Aoa;rzYb?GKJLXh%-6T2@DeB_w*L6F8;IgL;zr@xH!u8x8a(crW8i_0#8wu+uQ zF2NdrjFf6{k=NzL^ocJ&E#PxJdtkPwHaVc~0B$?LbnZraV99#&Ph2wY%4L+eX<~dK zVZ{btBGfC zmZ<Q| zkF86{pwBhSVs>+gc=|-DU9fH*1Ln@@Lm`Q|Cas19quB?vs#ZK$ACdmt1IL{ojrog5 zXU59e)HB-q{e%xZ*HK&+5>0)rkLa26J2WAua@E9r?u%d6)-|=%%>%rok$t&cd0Q(J z=~Oq-&SFyKK3&g@bR4R;XI_?c6Ico0?S6jNc_)eGuwjNCZ}6(yV%ty|iP?K$VOHiZ zJ=_#i@B=(dONfw1B<^nwM!YN^foZE3*{A8-X%gXvt|8(3wq2YZoSVv|Aa7+KC#Cr@ z(G{OkBC%<ylX)K3oi{e z8ccBgnhgE^!CbIoof4Cat5Kd#elIXer;NWXN~br8>3Vu1&hOfMJyIs4h_9>es!@}; zwhAT3qbQ-a6r=0|p*KURW<+i8L3I4D+mI><6mK67#0P8|GF!ZN#(yv1wyqlTJ9G<&f)B%=VAA z3;TA8U?=g+zODyaPcr*$PG>a#=*Me86(XP>?SNJrYP-ed07M%HfBpR~{>swZx&tn- zSrAFex`CI(bz^4ekC$-0YI!^xPC-6@wxxA*_PtAr`LlI1E|ctak(^vzRiaIOc6)h4 zQ<~(m|4}np+l&mCl}!_%K9IY9&Y~jz1IpsGDS3LJw) z({n|*IJ$7pdoEwJ`=Vfv2$nxabA>{)c!*gspg|FO3aKaSlXy6P+nC0W?!Ee#mGW^; z*m(u>=a%omqq!fz{v(3spQ(Z4xSLKx+jK-TL%2j))KXiEpWi0pvW&B%E3D5&y|8E_ z&F<1f^`=^QsHyf4v84&4vZxj{J;_`(xm0)yjIL+!qRNKJ^VlGBlqJOuQlW=Ot@q1H zw@jvQEJ$3dYh9+OQr7JkSSibtg)rp{Z-T{BR_%$83{(vI!Ou(bzy!iK`2VGA%n~eH zN>V|$;2L|f@(njcM7isv;zj|e-|#zKOb=e0|C8)rrP&{ z;HA!{f`X;`k?`Q8QVxJ(0pc|B!2_2;=U@J$&swDD)^oF*0q%z&81MRCFJqZzud|@Hd`B`b}{Lqj(P}*Y2A)+z?wvdsHxzA!r4-lxLjL1`LOU0CmC9I2CcGaXaPq z<;^!s)NXR0vU6B@m>(p49%BiXfE)?`KVu=m(iVg9b@4(egnF~^=_!vx8*{nTLMTJE zo6tJfxc|}}ZpxVQu>ZI-0WjaFSPk1uDL|d=|B%nC6Y=cj2$oG9t6CS=gAyO(5PQ^ioHxK)Ye5DXI6c3vBr7qb?70+e?=>Z3(f=#C}ewSTdN2rYLNIg0@_H4MX}!!NtJV!6@@y^ z{<%=O^F)XQ;Y5?jnf;>4T*{Q|@ywEg_SA?Z;T4Y3BhaM+1nIs9P2itWhfr&o(QviL zm?T&yG(A(PMdgt3ih9WAV6D)_I&=w{MQ!{O3XsTdcK40SeimbBdHQwMZMaJ)(R^gt zvlq9Xma0044Y`zB zm1vDb1jS^fa8gY$@PD8Sgptk28dpU-Zd~;nBe!1g9~+5EauybLJmr3hn@e`QP=p!g zhC>Dpr>1;fd|x_tw7)-3R(61_hvlizDNZHMMlfFE@g)Y?YROdOC{3|D7U88VY~8H3%J=r>Gswu8?4C9RHGZjhA*jD z55N!8DAm}Dv=i_#O%#=CjaOsDRTu|bbMiL&46UFv*rhI>3Ul$9nrgT$(kU0V%5sSi zc`9@3*?CPD1^SraSZ}E{aF~^c4O|@;YeRXCu?^1(ZE=^^;JQzumbL&)QTS*YMSs9D zM5C6lEcevwQm&|oMq^nuNIRFUN(Y3{up>G(^6+hP)Fp&Lu!c%?14Dv?&gIe$eJ7@< z>57EVukqP8^s;U88`*M@Cg&m%t7Y|uog^rl>4F2>lC6ceUX3<%Wg_;XaI)^n8 zBGMq%)jEtn6B(^piHq{9Pi!9IZ7i3Fg7UwEs{5=~5Jq^F$@^GWj?|Z42ng11H=4vl zGiRSedfqSW{@$rS9Mr)NYj|_xaWKoYd{sR`W?T)ykpUTaZAaz21m=X=m$N_w;5K$A`>JZ^dGE zgN!X!hCgm9X6*wg1_z0)6%qeC30Xh0t-$$E@>8O&(;|=Is{SS}>}&eHniP>Gq9qz} zn9M6H3{GBjsACHvRK}x{pRWbID$0y7S!iZjbii0m@TG7{CoWxPUbsmX#di5y5@P%u%(kX;}r}N%(Zfqc3;&H@nq2ZoQT> zcZZ^9-YJv>E(e|~r8rRStlnn@t2%m=9MJ3}w1c#Dj%rC0sBI-Q6bDETY`BM(O{yqS zRIgH>vmWrm-5dnsbc{8&md0)I@lWp&qn(`SR=m$^(iy^WDEMlp@O}GW@fkM z?lF5$OBe%~f0wCj^LgMSZb1BVy3S1c2Ut@^wA#3iwkF!h2*CEV5h>;GM7XTTqj`%p z1SX`esxn11p@(ff#q>JtQo8(%imnR>3q+ME2d!e3#e#@ zuI8aVt4C*;!zCtRL#qu;%==6lgQ^Xu;;A*cC%P{etUD4zv%=@fmilv z1!#q$HzH4Zrpr7C84kJ`NMq14V_?APj_?teQ1nKJ=y&qN;`k+VXjJZ5tQqbL zRlEk12y$R_JAh$2{^3;Uow@CynsurJ|Sg(zkZjvz(Sz@ZjIFdXJE&p> z@|!>-M|{@r&;>HcM22X$Em53-4#_&jb^77ok9lc!9!-r1XpJkWb~g@Lx9b%w$jMTf z6K}K=RAvUc0>mSijkpOnLgwB(9D$ddDthD5C85IJe05z?uRKG~{JfDT&1&=&`-AF$ zjB>Nv;@7<`O>cL+ryNx*)7vruEOaEgqqeodWUwg26UJ(sY$8@fgDTv?wrHB0Jvz@g z@kNJK`&V+y^^3jfiP53ouS&~j`_$gFnl43+REMY7-V<6ab3`I)Lur{5w)`IJr0T9i z5$ip3!hPXIKiP$MA2>_=yW(vEXnMb~k52UeAIQWX`8hNb2MGi;4xi|s`oC`~8B!k9 zmX=A-RLK88r0`E3uQ6yKAVTz1e>5=S)Cy7_ctElmj5F#I<~JF42XhA|gRX`FRJ>~7 zq5oO{vK0XmAwl0@?jGSU8?sDN%+h8Q(Lh=f6`Cdl{MC0` zEBxl?vgYQK#*;^f^#!ROFEb|1doBz3$?dk6zc)YMFFtpHB{3ixLHt$}3Jmn7VjV`= zY5=0Xzt86KlTxn5$fhibtPSbr!b}@P=vXRXq8pQX%oz-c*>lIwHg_6of%1LMZB|A3 zw0DgrWR<$6UPRRb7i?}cEeV)lQh~s{DdVIKcP#p5?5<=^bJko-*s;~bd_Pre%(}9s zGss~6#ys0~-6aE)$U+vX4664UnQ}w>xPUem_8E(;IQt38eG!`RVy$|M@k-{66-`Y+ z8&6G-gIdaGO422?vY6Hw3uq~I$mZ`9otB=l%{{yNgzw^3N<3V2fL1;3)14kz39HuF zYf;^#(%=?Rs+jzT~iAPiH z$kv)#bp}j?MdK&5!cRrd8rztSa=|VrQY4Ek0$Z$|gx|n-m{tMM&}{6G&j93XYtQ*R zJ&VwF6S8Kd?Qs@2=eQ72p^YJbT`nat>0!69Zb z-DdpC!h?T*2BT;e4!D9qRUTT@NNgHMaVChwhM-Q^waR5oTBrI~#G^Os(psaODc=x4 zt<2>)>*XcqL01%PPi8pQECJwe!S^Pz!Cm)^Hm}DdPgodky`>{&K+-!U&V@}X_7b5@ zV`x^6IH~85b9iS9&>1k?a4m+7t7MqfF-H zxF^{D70xfto0#E zUKn@}^>?KLCLBi>U%~F|Fx`() z+n@7dZX}4Wc$|!T%yRKXxpIhu{eX>56Pv;F8;U0{e=xGVPcLw(pGsEW|b z6>*_BSV)fJwAdo%lMg4Y(`C$MeY>!;QaI5?IW;4fRvLz697HV&ZJ0$rKA@+VMvN64K5 z;QGx*bQ=Fo*}YyjTi44Z55Emc4!f|ECe246iDC zlww|PVoH~7pajg&V*5N`c9$i!M5uj2fB;adG_F#Jsb&+#i;V76Q*IZM^4hdXKZBXm zg7CgygpLgM;Dq2Y^xzNaYryYQjk0IbH~Ha2lfDsmruS=JA3y^`UzPpNyx$Lxt%D9@ z(o{MNIPeF{M6#x9Fa|3lb}xx3syRXuWCPQ@K;xmGAOX{dUT4K+fgB%@F37#!cS-&~Q@>l^-lN2aCPYWBE;vllS21hzM8F(3+b>V%QFh6|E8gq1Z4u3U**i*aRA*c z`g(Ppi<#NkmG3OECHLg+K-VQU&Kx4s(+jWecCh;cG=2} zegGYZcn>0+d3{ipG^?sY2zgS>HoI{xm%LNnL7E1Co)b4i#;Tl96slKvv5*$3;sI_C zzev48ZMt>%)DwOz-<$kh+2_~Ev;dTLg;SfFkuI&UnG|io6|R*duGWXayU=ozw5x&C z4gi?ye&5DDpq{gWpj++OE2I&-1$%Nv_Idq3n`G6iQC5u4NW$c?3l1oEE}nHbD|yM( z^iOU^%w0D|j7FuQ2jVL*7`Za5m6CekS99 zJWB6L^`u=3f4~?hD|vj_6)}nfb+U_G79i|Sa@Q6&yd%3cJluhny)W}M95I6t*bQhS zCcgs!>YOs$#&e2Ukmu3jMEN^P*kkE{*xn7D0x(#@?Q|K=<-7FLX~8!Bg!0 z$djs#P+rnl1QWbXJJ4!#{ACoF4(~k8QoBlL8nZVhy8aD;{WBLwj9syBN~Jb-3h=E~ zMqw!2`qhYmpZe7pCp`v3o%QCB=R`Tqfmf+GK;?s>7R!%YdgsXU_{Ve=`#$`j z7VBPhE_2GuTWVB19zwob8&Hp5G-7Y4tO4gm{TsB0KP=Ua%wE_cPqC_`vi(sq~+j`J)*JL_Ya(dWJXoxJwwEZh8lr?#V&-;Hw|i_B+<*6ZS$) z?IT{ggJM^3{yzDlGR)7Y3gEVP&N<$}?T{7>*kpaZV14_eA)4(*5rBp_&M&;r1dAi$ z<{3>^Cg7g_nzP)Y`lv+&g~`(}AlVOg^iK#;tePum-vcqrSd=m~BuYg=k25wEkw6bo zmNfPM4hN|5xZCUx{Tt^YFPN!wPK_(nA(!sKN>ngI)$lH#=k6Da=e5+YHhjT%JxANz zlgDo9TQ>kkgBymMoz%hUhUAW9MQP0rCiH*fo|@QdS(M@v;@+^(OMcfxn>qbdj~qTglI_{}(35 z+^|$utSyhCi1=OHVPmm|PQ&i+n5f+WhK_{lpN0_*6_yGr zl#g&)Z#|ykn%bxx(0v+dv5$W{Hi&_&FB1k89ggq~1PJ06&2n-L93F(oe4b=?n%?YA z_x*W$!uI2=97G~SrCBJ|5G~O$t+T?>iqU=?FIIez?J>2Ecu-twxCC^0F2x9wHl|Jz zFHmIK&bha0*VFRHVC2=DF2nVll{`A~7%fzDnXY;KxlXYWs@!jV zxjX^YLW%I8Nk(tB%{*$S&Go<$DpISt9dKd8OUD^(T9v2(Z8FTHBh<=Fq|Z-Lck_X7 z*>8;jMBX@6yxE|;bpm*~p}@MZ+Gj_8Ilb8&9lgDI00;eogCa>*mx)#gWo;y)oJE7v z@*8F;1ZK%2jArw9#9DWdB+wNm6HvzD0%j@5Ly;8P2sv0sI8WB?Xh&noS-Yia=%jr& z;AJWQ?p7w2vHKj6Nr7_sSZzM%g9tlbUcQ%aZ2%E8dZZwrQvzfX7p_imwc6o@w6%H^ z)3u!~RiyZ=AQhv^%Yy$!8t7+W&m{<;*p~^euX{wTKiflHV;TX zyJV*8>gkKUA5y?E#)i1qua9eK<$OXhQ#O`I3Gf^z5C!0N)^DX1;!P^k+bZK<&jGob zBYv>Zc+HWv6-DYvX&8)4=3hh+t?uP*?S7-u)U$%K7FlHAEKSv}lx&vWVjQ&>@8qd6 zeIsFJKi#qSuig&Aww^CU5-k{pu!OK-jS$F=8a5KhXiVm3c^A^sz8ttQJrS+N?LPD7 zpDhqL0Wm@gDZXHB?~FN{7WOV=zq{3|z-Xr)y3+jrJ=W>@I{HMfIHo>x$X`Ojxg^nW6z(Xt{kz6w)0^R9Hljnnwccmv1LUb# z)#@U~fg(zM{GOIdmYSP&zqYEsU8~lMcQ{|B51}HLWSCB}e4e*YxIZq{x4&+zN`BD{ z?Kq=C>C796eR#d^hl7S*p9;wHtffk&0p_A?*3-?!i;Ze^r=87#7hKZ=O!`BF0d9XHSet|$KC87JTS!HgpxjNfeZf|eTH#awVco|uZJQuq}+gN3SMKm`< zCK(q7Etx)RAO2k~pPpR56KQ14sk;FCyt?Zyv@lyp=?#F|pw<>AY~HZQ(cTe=2lP&> z0M3{5x>ZuBCPr>6rHYVK(n%t>6UWJ}VxmOqx@&PzZ>uf|zgxEmcFU^IP-iogxM}!7 zsbEk4h1f4*$!poHecMuR{c$CSq+ZQ9XGF<1<)T}mo9j?2gX#_B1n;Lv=F zZ``RU?arT%EoaJ-M8GM6 zgD)Fq3t%EO#YQI*%*lkIdMrJGcSm_drMruH7|o|F!hx@oG;!ou$iqvFlY!{Rd(u1F zwl7N+U*`RyRu1?-zKHro0P9XyknGkJ+syDCOGhl6pT9I@0)p26Ltxz%%L0mpc09346G(bzkB@2qC z^tRJ%?^G_FGgceppNiJfHLkUpqr4WZR=5^s>;(4MFgIdAq{VG;No!P( ztzdkwE;wCZ(Y(DE>?X0p*yn7q^&z(NmyW?6m?$!C8T}0wteUA+Pi9ra*B}>%W!FPt zJ(O#)(v-?F#ovB(z~*82=A-?t5Se36r|1Y@vX_tR_}=23Py3X}o9f!O9yM;S?_~Ti zhxxAI%EqLgr!7M0E=VR^bxG~Vk5>gv9^Uw>!m4l+1w3a^xv`Ox!GP2a zk|u*lTCTav$-WDL-`LZcsA>Ew{LMrqoy(Vm!Kq${r%sp%5M!{~+wjV`W2L|~E5>(E zs$4Wzmh;7slZtjc8Z?@B%{Q`)-^)0q-b65~G9`%=3RY7})IlVn5Y z%^_Fy3t#Sjx*(Xe;F`wnvAuJS63X%`srKcdQRJbxH>->Tn$3s11~4mSwg|=8gTSAt zvOL;Xt|5+=0A&UKh&c^@RNT0nXPO>Kw4G z(!OJSSee$ZrA>e9@O*&AhM8{buX)l=#AucvbLlwwJW(uCOOE8=#!K-L95p3a$tUh` zm2~}W211Q!^s$gLUhGAqDV~-MBRhypNoF96&JXwvV5% zLAWs6^TH(LyogB7s)lht{~R!fP7V5$KKCZvL1_&D){n*fV>eoIrGZ6V1&6&ik8{oA zXMw!Ed6ECiX@NniZS|Pu`Biij9~uXXvuT8wq_#t|P|Ox%c5q91IE2C4 zCUC2qOA6UqyJ7c&OMA&QcNE$m{m~8piyF>y|B!cepoYeU%mW6G5hF(=uO8BU$fGc^ zSq%F2lioXt!c1;QMuV!1zpVGtKS8>w((>&>z>_tt_L@Y-Y8c1H2)D-4wIBpuf77Ic z_ASpxcd9+F<7x=|*b`$%XHVic!n4Is{$@`+;x7_WQYAw?@Ih!-W!cfNs@h<{kPEc% zcV^Wy;@Rz%vuQMqp7v}@wOyp{XDOq_4w~{+ zX!-%>{NM*U9_r@0tERiGRyg`n*x3m!Nqh%pKl#l7{R8<=45}HPr;vr!V-P6YDU= zcO(Z(o;TJ{e-^l#x_XHW^+}Ut2h$0S4!&XXS&wxc)`xP>#h`>4q~$t*NQ&K2(?>Q# zn|qKLr6Xn3iIl9!VodEeIyZ@IffKzJ;v1}VG3$LAf4fQRuno#*Q+&&8+`wP44$zX^ zNTI(VONLB`%d7byb zy0zHGnYK{25m5u@0FD~cF8g9XeP-NV$GNrXeDk7g_LBW^Vkd1;#6ZyYvN_Urk~88q z4Y%WQQU@ZUazVi`+UM+PR#@E9K#moex2CYLf(eLzAu){4XiS%aAfjYeMUi-X7ELhq z$|uZW@?sx^b!0@diO|6+3m4tzWu?VtOV73_4tN|k-med#%{JBN$_Qh=<04aMLcTGi zvYo&9SeVn0#TG;Ea*=7v4tIBJ47IRb;1DX@_kr8m+Jz@HF0(#oJP6lIEc5P^opWE8 z(F0(GFVU<}*Oj=3$y?1|Mz|KENEU;M^RsvlvN2d?q3K~;BUEba==C&~{ZTVBP8kB7 z3ejXs)D)I61!EJEG)<|^c&_qk^*ab4cwE2^7gALor#Hu1Hs{C&&1`S8V!)ShPRFAv zLV?Y2Oj{q&R<>wRp9?=Ua?Fs_DO=n}k_9X_1_)wVPfFNj3z9QtGh}?sf16iM$h2E< zCDw7FoiToy=w(QeQ&SbB&pzav^JM7379SZ&9v3N}u>_hjqQTFA8^)_h%~@v*B<&a# z56o?Mr(s5fwUMRH@Fv9KaHuPSsqXhm=>MhcR^nau7l$=Ds&P_gUASbLpgt^RS^zjM z44Y5VbP&M(NV|{~_?#K6h&t*-=f8w`Upx039u{eF4dlMbkQ$lG&jHVc%#jP%wf;`w zP@dCtW|J|p4e!6wJrY!eP|h~DjbFW&cw5nwMyKXVO(9U>s!AIdLtV&Xe2%(843?d1 z#XP^+G9cR^#x^V-RLf^Z)<%&O3kFQXw-b%!pjNYlXft|*HnE~+gw~8==ZU0|Wj8LP zyTtSZ7rz|@&%FWxf4}WH7dkmP70O3iDLm{|XR}5nYKfFo-QL`{S@9?sWai_HyC`!n z6c&pV!c2_6U+zB6V{z@|Hpwgu$CvT0Talr;gh_UlMTbxHpH+t#&*#Dz1OT86<1*<2 zK9^542)n1iC3`b6AIcu-1O1nz1+#0pc1e&xq>!XbA= zhm-Jt=45PRfbrXv43Dj=U;$d-qhPLyD;a^+M|vR)CfGcC1aClcaZu<*Qy0+CeWOJ# zL{S(AIW+Xi>wW}9@$(UwDH}REbQLJkB?IkFF>1IHt~gq{rRKyz_ff0;9^=DGv^T@`exv%tgZtG8d{u|UpR%K~&EP7|RE8C@0guI* z-84Cc;nm2K5o>4WmjN16U6mqJ@p9=g9|`;}!m1*=WjgF5r;e9v!{gFa_!9U-&Vz<* zMG+R%NfcdOmPUZOy6=%HfWtLl*@gC0$;qZ>^#Y#-jEt%TVtkYEgini*C2`c55@I1M zA3o&PYfdYif!M-<;fb(figCy^8MyUQ#pP#LU_Fu74o5Xb^#LVzyS1%tq`d9$qwBM| zC5m8paMAt8q$zix!QDlt8;`iF)F!c7WF}lEr^$PWB?GW!HMX;E4pYPxa15b;d4y>_ z9&xVNPd`TYL+#MmeFf(bPDNqNGl|EhOdSmctg43g_y^w$Xl?P!Ejk~WzE*Sb38~0k z1M-C*QKUQ73IT^4i>j=@?gG}}KW_=T@=wi>%Q+2@FB4WhWams8hh%$zn!RXN>8T&Q z3OC=2RNQ*7R2QQou?9r9G6i62PCwZDcUy>#2g`-(PzJpt^c)uOdk2ZT-^fJ)%e^g( zzy=!$DFN>_5#PnqCnA7#*`HqHiE$>R4cQ-HM~~yyGXTE?h*M_{4b~|(uYeu(JAGH6 z4=-}LxbVPb9Ckun9nI=0Bpvv{7ORW9tTuory5IQp=+LA%u}Gkj2h2aWwzgNWrR`8c1_f=q|7zCtq;wtR+8rmM540((h1WD2*6HrR!;dVIBX8oLB zVj)H;7vTJT&o7R5j;<<=gO2y;b{>P1SsSkfw`^K;BsZ5>*MU(^k1GW;Cb){B48Q2c z;UzY44qdvgkDQX~(BhsW-AizGg1L3lpmkAmMB9=gXRM@`7FjZBGd_(DC%|_2VWuOu ziFS3f*4M==Gt(?Ehd+QW)GkCz=wbl(_1fH46M(CSZ?1_BOY7zYb}z-SxV?-F4NgF? zG)mlz{S^H3dO>KMdTrimwThc!+q6w9Jq#LaWKT{1clrUYIvZ$Ki?v~&+1+1t+CTu< z;(GAhhBO#eD+Pk<|1E- z1$YM(j7>b@Ft}6tIpT;-d?ZAqI)Eh6kY=h@Oyww~2!yfRISx^Cgt4TYM~-p(PBPg= zoit>s=TAb3?B9vB)6AI-PqZJUhH%;r6F0ldX?hT8qRY-mCZ{VI&SaBw7Kj?wtKL!B zCbk%HX>tRn&+yWn;Hcrl@460Ec)$sj0&4KpIjy1!S@KxGbjM99bH|WiWicBkr`+)# zJj4AXQF~HXv}+1_hmG^uKXK?Hs01T+M<_+oq z#XG-BI*x?7u85n&vVO*9-rQHg^a;31_?8xyiP==NoB33&<$5+$TRW@Lm4Oa4ewu;m zkcXUW?QuUBg8`=W)rwce^ieD83UMDqJtS-E)H|Pu7c2M)Y#}XN2vq)Rc_EM4 z*^oH{fsnr{pRAZUJUmg@fXGFwN|5fnS4uB^?XTHxJF4%L?nqUe(OEZCeqv$K<=H*! z9Mw-=S_CO{+L;%$e;@nKx^iD3w1N~_Wm~(tWp|Ep_wmOA5{S_o8^=Eazb z?Pc3)FN8zPX-M;a&e8f+s zDrXAUcloQ``p~e#5I^dV@&vecaKC@ELB;mH-Ox|`pO^FHKjdE8cK{Oxe9@!#H{xtd z1x^Y5p^UjA@?l)~f367dR~ih4p~T{xj-jqJwXPWOFMkl~6^J%>+UgiBhuc}&u~bN; zWFE=6Wh=Z0ba{bh2+Q1xo(&?b%n=3Y7_6m zXvvuC8F>gS1zk+S>UJfXr@A#>%0tGgUT0#A>r*Lp84#t0hqvSm);6^C--Sy(d>zPJ z8zCIm?(8vE#$`>X4I3867@uyP7iTTe99w#tO;)4IroFLHY)=Z*od#&S0)N^w)-Bk* z55yZ?0YUHZaAh}$TcU2lns}c6*K^y1Ik^-kHg7Yn#cOEE4UArNu=!}`s5Rm=w_W+O zGXoB8u9tZqgEo6jFJ`BCO?(`0#C+-@&u>l;?iKVeik?|#P@dy|iHBc~j88Lt2ij)4 zVsrYo&A#wD4xTV$=U;B`RY06u zcPD0MrR8TQzPNSkT^~nI0FYs;*Sg`j1orc{3Hnk0-MgQJs39 zA-Fj`dKc8FcfKX;NpC#$v+8M0eK(=0ZRqWyo%~E!7&_|b+j7!w3;HZ8JuoXcd++iz zfESO#7rV!DliMotQb`&Li1*I4dVxQF>rdN#Mq3v%19gs}3wf)m8&>!u@jd_FO#VxRL3ttvy&Kx~60oA#J=OzfcLsQPIfS~F<>@%(IK$&ex{3Y8L zi!8{Jx-IywBeli+7NHJZx}AalkE?eK4kX&zMw5v>6HaW~_Qagn$;7tRnb@{%+qP{R z6WcfEz4e~&ySJ*Vx~liC-9PtQ>k%D#IORTW`WR$BpC5Y-wx`3f7LRj#Iuw-dC`N5B z?s|P}U-}e;?S?a76x__M(1o07(4^luyX_CTZ~nTHOLmM&AnJGpqM6}yu!MP0QZr+~ z9_31Q^(6nF<2Mw$PN!0&B1!qT&~`r`l0Je)!W&SqX47to93N&%U_M+>?$2cQ5yK1o z#|pJves5kmi)mV+E?c${)s-t5lESV3GrQvEjEcVU83%h$7Sc>mU2zv&;*VeF%VUbv z%sgJ3@9v=Xei;HVn7%Am;?!!L(qO^B!eqAz1RwccUB18EhH`Jw4l`6%OIvrf{m+g< z^S~zkHLK-XT(NaT%u63`Z`rnKRHfA z+WU^`6R+W^M`4)9o{EbCba#0vZBTY!5MhO?*^5av{wR!V&hOP&h~yV$2iqe1SpG$= zG;_=7w1w;C6;GC!vwezCc9(tVyHgluJ~-`av(#B-irGk_H$E37>tj+r`0LABPTO^k zp*{iIvM^at+dW=mQq3d19D>n+*PXx+9%4W#*W2LVfR@Ps4H;20*@`V?P&B)+E$wp$ zDf;$@QXIMo%83q(tABaAkPZq>yu5&Ve90%Oh8fWO!gqm8@`g}!3$zFL zlH#Bxb2PUTB0l_!?e59Bx@B8DnOVEDZx~U7kWG<#0r!hkA=?Qrjz?<%%hYzHV5+pW zrU&koBaiURkGYt)?}pEzd-i3=1}3D@I*$A0v^!460sU^J84Z}7)0d^r3l|4r+S+gq zr8&V`o@fh(&1Z;If*2(!*zbW;wtq_f*7duihW*u#*LAZJri=fpx=15(acG|KX zf;Km3>hv~#h!SAqkMf`Ze#pLCsxW;n6)*JqE8${0xBx#m{~HW|JSGR)Sv3tc(}f4K zOZEcMe&ghhhyzI2>|Rz~KeDgh;m5tCW(b@0lthei9I<|6v%QnJhw%GnqL}dY;U7}} zdEvYx(S?AX>Un;BHvcyq0N&z>uqZ*ZKDnen?nA=-#MOys?TV5yrTo)D7&c256mNKU z9&tZ_Me#Y$Q@9a$kceXp9{brxNBn6$|2t5ghLCkIyfSe|1)n_iD`e!3Ws=BnEN&W6 zNKcH-vF1FKsOgRW??GrKS~D%pHis4a*8DX~i*v=sSQF0jrM7xa8b8E$x=|ZUf+Y?4 zHNk`B{&OZ}b43@}gSwKo0Ir_GPJ;Omwg$ywN;ao1`8q4$R7z-~z9h(>shqkVOO5dLq>>5Zo)9oq z%C5+Va716NyLLAJ)rMnMgnMqTA%{KfoZEXQ>;cmeRVnZa+8Svs;6bc#iCCYNLq9EB z|2W$hFW3ep{*F*DNBEW{_WDie)x=a+HFpU;Ec2OEHKUiXIl`SYC6J7Y`WmFzR%Gx7nSLMI)uZ^s^$gDi*EIekAF zl#^Cjer$n(T?Q;B9v%5@O5ql^Zzm?b$MgVhAj^{#cWbHH;}gFSMXz9JcqiI-d}r8! zLOY#tD%&dZjkNRsCg3o6S@BPA!Hn>Hk#9PXk{H>*aFhOL$eBjNgV#i&2q6sh-vO$% z2E^O{8MWH!Kv@2lC_|Ks1~Lf>0s;Y^R5S3U%KAD2r3udlViWW~lhptGB>lrSVfJg1 z`jhs*J5ZbW>>%i%f!xU!vdxihU(6xflZ!T34f0Go+ea2x@^hJtbMyd#@_Y*t(>u9tXt#iXxQ*Y4H;(q(>*h zFjy8*U?QZjRF)Q&*lBcXgI)H9q2|sF8nw!1!(OC{@G&xyfGFkzlt!(hCtER1&3mtu zkMWz4BK2>FWUb5Rjdc~UMJ=GOn`#`!{$jnO515*K)a>@O6D8f|IQxFSsqYSr>m=;d zkag1(23YEx;_J`*aPZ#&16_%m5yZ;dIxtmt@;T-1%c`t7oJBY_*93_l82(NdVbbtB z%6u&uAdye|fGi)~)d-!52%_oEzpTpUw4RPtvlFl0u~6x|5nh~I3rWSmD#p>bikij& zD{bRf8;;)8WFuG1e{n?)k6aE2Y20qTY8K6(qHT2!hj%+ygbwYT@phG!be_k$Bs@Lj z?JTZj{uc4;V((9`1VcI!1}gU^s#5%>V@(qknRq2(z$rw$AJW=QMrTjqGFcYSX)t5S zeYH0J8GFiUlZ}-SJ3rpK|Q%@w-pkqq_P_YgxzZ8*cW>Z(%P0e@1R^iL`+jRGCv68z_%KHhrBQPMRyb zkLSlNfgHc=aggrqU5`yBC^K9~Yclzz@ZrcLu*DPhTR*eCGS!4~blibNNHp~L$mk>T zaLvA195m;&qqx}lvPbP5Q&pSC8%ef!Qm@x5-7zP%>p(POq?#cy)W5|FttnZkhBeuA zp3#>fgy&_5u6L#X{u_HiDbprzo~x(gre9|8LFK(ajmvPYP@;*e>%y$ERFVn+M{Z;b zjC!Z{tp7V7L?OKNu2y|PJp6JtbHg0(fY2$!PApqY@|svWTG?Q@gtK@T1gx^LlOiV1 z$|thEL}qb4yXRM$4Vl*AMNdXO-SZmUh7h-PXI_GBn!v-(TS1TK)Oj(2d!xZPs8+N7 z4WCv$h6NHqE0N*32#;SQJXadz4EIj}`h~6(!O}^BLGP{w3<9yiR&|Pu7d+2QY-&C) zq%t$T=g`B9qlxj6X|H3>?2S3tpZVn8jAhqRNe_~U11=&tBuqZYDLI0TW!rvyh!tle zB%m2Z#uYWpH9^3JV@tKTX z*?IcD>I@;^4^`uZKDrU=JODbsI-W@SGR1_DIH9Cn8Hc1NVu76CiL0(1lvv{YAa^wQ zLFyN%B*eSoL$O1!bnBiJHmHAocTs;^VKjFf(D5V{ z62G<5g4CTGa()WE+z8rOAf)!|@URtFdA<~Y+)`ok6I;jRNhD=<9cB0sA0rx)9o`=D zs2&f0cSJ%|;r8rW_DhQQr1VQ-eiCM@*WL2tKRw20eyaL6T>^O6&U&@TbEL6iKe5QY zsJEVoLJqR|?mO^2%5WgCGyB<)Pa)C_RQT_l!-vxo259@=X!~z%Xw5{>-*$mLh1RSo z4Y8@WCD`7{fm2>0@4wdl1u#`RMMh;Zr?$JU`5kwh3S9^aPqzsPo{mWQr8%?{op!9W z{#>hT@_6UGmIKz($!ak@x`9D9!Y!g7B;+>}dXS%b^YrVdiJ>8w?G9p7aB(lbUGt`{ z_*>3%agQigZ6!{Frn=|CZHQ~hCM{w0l0 z@|=;5)yH>gED~@X8f<>vqVaqDZ_V284o#j?w~&3s>rfyZHGbHZj3%zQAenqvyxK#) z@djtjl)NuyPTK-EcK5cGcYLh{^%BPIrO4IhfmJt)Y`}wM;MKQN186D;UT5Tqj;TSP zqo4Ekp2mmQQnKJqi`)tk!ZlsE*5FR=LUFU6=69=a5wACQo3Dn$*jj^Zm*^ z!7|o1-8pF+vh}TVdHdy{TTu= z{v>Fki(yrV82$m5a<1W$&7dS%gQ?zyi-M#n_`qgeX(+a}D;ZRE`~J*gR5BA=A7H6f zqYAKzjS3SEspYjX60@u_CR&Ds*P2rcShw6*Lr7h&u}*4M>vO{j9pwCeJ=mk8 zSxOl6Mrt#Y{G**soKY-s+Fj&2u^QmiGTf|#<}?U}MC>H4QUUY6Fe*Wz3X}Kp{^J;-r8rj zLP2P-U1}?`KvvT2yj7)z&bx3ulB5L^(ZSTmEMTrcwh*iL`Fkz?<9>zKf|H(KVRib`p3tParI5SF{y_qL-j`@P zHs18gt};Zf+K1Z+>f!80a33_Pd$cW}K%CW6+qy3>obgn&?&o62zL@}ZmC56e_e|=M z)s+_%FmO)weN34SZwY+FK?W_$qVh@mNUHNpfztLoL1-xq*|CGFpt31{95|1Qx$vZP z2{MfL@KtKI;AE2;Y?+GLTgSK}^Q^$R?39tUy*#-6QDV_egLByN5YkuvHOBT)-hz@dVs_E{LAXA0#-Y*=o%B)TK=*QdSsB$togxf z2@wWaoyxXlwwAa0eBA3fa+$Lx+Cg8|_=>pk35bzXX@pMK&;(hniHPipN^^8V8T__` zCu--2`+QtAW*Nk(gRipu?R_j$eg$@RL&wL`83e+QUS@otc*Ty zFv#;)(=-u>?YFs}h68^{O7M31ZJYIUjv<4p5XxMntL$!4pS`p<8J0L>$_&Ha>{Px2 zz5YKO75*l&K!`{L)UV!W(87w}zd$(t78?Y_j2{GqE9u%Af;I_(p65F-p#kHmw6MTW zUe}(Ilr-|XaXbu4ZSny~)HIm?O`pMFPB;T1BQlRHURZ66^&mtECTKgLp zVHf>FE$o(5G+i_`t?tVz7FEtqi}qi$UN_re{w&jcc9pEs#oCU#J-@&PrxN zxQR}pY-=o_ZOt~&7G~)rS)UGda%-p&Z+0@{*d&^I$aX2!&x;EK?BQXfk;;)LXwRi{ zR_P4}QLDpQg4R;IJ?tgUQ?D?0LRENF=1F(e_zMQHPQ~l%RcH{KSO*tnC^vva(f4F~ zMdqWxwY0Nbm<;LB8A|a0ECj>RleWohA}nxdt1xRoa=xC_P*BD!LUw3YJVKOYL0;jCYZWyDTS=&(mUw3|^0;?F`n@{W zxywxP*T!$ih(dcyHIl_d=%#v5wK{ehIPAqQGf>KcF_Avl8Kd%51qyiy6c(&0mKTuM zX%Oof1hgNPp6%bb3GhHRBJ;j;u!#H_S&p3rv(jH3Q2ccakob`)qBS!9NGsuyyDCQ4 zEW~nbU*3gfXB(ySl)*9PiYqb{X?V&;!r{)f`f*_KdQKU)xmZ)F@eC=yjb$5Y0F^${ z;q@(!mflXe}IVVSDM@z~Y$UzkN~0p zz=h(R5E!v=)$>#tPGF)Q1rNjbqjK@kEkO%!w zt;djsF{DA*Xw=D)NS!&$qg);sjvBJmMq#p-dk$>ezfCwRm?)$XmU&#mlV_|&#(CV9 zOy!l{X4bH*EF@XZ=6}&*u`)v_j30i*ia}Q7H2463wp5y{a71Vp0b%_r#o)fprKzbZ zlXCHVKBG*&6$6Fr_beEhQye)e3JGpraopZ_ct%>@KFL+XY2z2JeK7^@yrlAERX>1L z-z&QF&$RmPtj;i8Q=^3=+gvwl-2kptNTSosN*dA3eMq8On&?*l;R705U0wm&>t)_; za^Mm`YGp-l9gfyCfIM*48zO!?z59?@*l3$ejK`}}VYGa#m_w-@Tv6;=6*zv7Co-o#^YFUWNmKB!1D-SJpoM+^3FE`E*b=GdBIL0^eePPbx9+D zTJ}okq__yA_E6K9(5k;&XiO>1I1fq97Kv|URJslRg_76D(=zPXh+c6aDZ%*KdpbL) zIIM(Xz(Z7XPXf9%vHvh0mE9K4#H(9Ht_>oK9nb30R7QMKsb2z%car5Jx~-uLJmgt) z=7`cSGjY8i`|XZ~w`#@#O17e`?>q!RKy%oyD#f#c@$;7L@`0O7`Q5ai|I&&HC!5^y zMDS6^59WV?=l(nPmOcd?AH80zTZ>>zm>$!Szhk_{QZnK;hRcq@|X1UH}ZaV9H8Tw@do;RVji z-=66Fd|a)Z70X%?;m{r?pi6@Km2ocEk=Cr$z3;BXCrIF-63(VnSsg)H_lGqJXNdNU zNNR`LTCZVgtZHlB*asO8;{vS40{I(#OtyZkaFVgwiJ}D&? zsP2D^%VTuuaPv1_tK`O6J>G7d49`s3lQ-n4F^Q=L6%o6w)U1>Gsb0m69vd&VJn6wD zo0=Sw_jQ~q-1JUt9M}@R4*3-tbXW!?Q`_*9CZV{aUUNf26G@t+w77LZ$nv4Lb6k^B zgGw%qqY>9mv`IuC6V}>^wb?q$6B<}IYNVz5|sxGsj$2{gJB-)C9Y|qfpVk4?vTbYKpG-4%3N)bQ&0OzthpuUD{ zkqe^_Xz8b2`4qiVTOjG<`|2BY0yWl12r~`k`Ou!4 z5y`}N`hNS%p?_&PV7*`qHM4g?i@b@IX~B(gjvd~euN$jG4P8kYnjRz{l3R4rA)P3y zFO~c)9Ov9o&rAV>4CS}l1N%zvfBo(rxlw<$EJ)I!*#r7^#KF6E3WIsb1;|oUuZUvB zw{s|G!Kr*XZb+^_#=~~CEZ52iaq@;BLxnK z!3{p~#STU&6J}H*xrn+1Kh!@KR+5$M*1x=iaHF3VIDLujv?O=Qe!c#v<`Cj|__T8@ zmOimPj5)ELPd8Y9ibvE;#v*lAjy0{-NAu4I|5e`apjLnovQcf1?HVyk784xmk)RbV zhYIyv_67}9#Tx_!M9^Gcb4oizBC_LW!Bs@Xa^pG9rMzec*)Uhkj&-f#Hv zG?t}$BwU@De!szrMwnR;(t%=X=OR>~a!V6Tdxiw=_?@N52E8#@wVKq|$62-omRk8b z(PU-_=|vtpzz!}M(eO_@6_3`QWg{-TPi{eImtfBJAYDf7XnX?o_ugzht+c;^^e;eY zpeAfBE!N1c7CV;_|89zYSPXix^fIzP%X0aV1(e+>Jz}*BJIU#t?7dF=o{X>>q*LGP z|4{`jVEE-dl*k)|Q z1p5VvN%AO-`IIJRJ(@$qN=5T+vKH2JR7g&p-KZ$hZ6Uj%znV;*Zh08V-Q0pP3naoZ zy#9jBL7n}+%0B8e7gQNfb$CtUcGNF3sLj+?9IjQy3Ilge#E5;`|Ar20Zv*+}JlC$U zDvT`fO)#oNhT*+w8YD;hb7b<3vIwxeuRWn%-JEMk^Bwcev`%%V-)~8XBrMP-WTDKp z#F8dM=-J9)2@RZa#gZMGt8x4mkqyBToA?_hBd`fUsZyn)@y^-Oya1C$ab-nvfkmok zUb4;%k>*Yj-1AV#oUI}sD;0|7#`H%vBbGBzR54Zw`{sBOQ%^x-- zv0d|%cqP6jNm}M(;rdeuTEbsF+qfO3@EPRsU#*Z{L;a|>Nh z?~bwY_#hv52IhsfU&?HaQXv4NliP24`c_i|dxgRYq9XRNbuaaOh&o?RZO20d7H@Vd z^~SVG^+XqIcB^wJ)h87U?Q|E;5AZtwmq__!4?7I)1(_v2mIsmUOw&rT6U1SmpAO*{;0HNE*7>rnQl8O$cV6;Xu!o*6pzrdY(I&GS+qrXE znd1E4z7QC?Tn_=v`o93!oq_F0F73D@eKCmRhrhGWXyD@1jI2E8^!uMyBqchUC&ob& zA=T^cz2OBgzZ;f(?nTpJQNeo~&X1M_`RXbD+>yANEAM10qs$3;t=VLCHuJme?6$C0 zTB75V+>H`xRC)DFN1Bo$zH)_OnL2c?84DpPYIhlTaHYUpya1qpPK38G8s*dkmNWLU z&61T5l>S+(KJ9@9TR9N?0UXuUo}711hF;0d$kB1dG~8V=FSt3ItvJkE`M|GpSikCD zuBu-qIVJUR%n!P-2+_JtR@5$Y(x{*3Zk@)$y}ld|9w_P zN1BLidSZCDO?pue)@|3DPNEn>^I5Ba7IJan4_x3SfK&B$Rgi4cdLfc1iKPY zOC4EHPnc6fBr)!q{e>-b5xqSs=|==rcw?Z=Ufb_UI1@mvH|Bj)ju-eOo~L*qc3K#K zS04EzuxL2>9lBo^Ezl{)Yr}j=1Z!HbrgVNL+OnD_lJcrEPL8a*A6WkD4Z%m)$4;%k z1sfN>O|GP}A783$g5D#wJ#_5McVe7m{d6$6P&9|*jqXbOC@|k*I91Us&Kcc=;$*Hl zF&aksgghu>92Tx!xasn6EJc{~?_{3=03}G|E*uF=-dtIxF!9x8b{7HaGCn>T#f5j_+?3ednauU33kl>lPIV`A>zlVV^>7EyP1~mjjG^8$oUg58I zj+GPZ7hS2D-WiqKJ<6mDs8_z4Fg||Lk~@#9^fColO)~>1f(|6mH}^BvX!wdogLF>M@m1X>Rt%qO6~?pD&#(mahLj$ZG5Vojy=l4 zYkWvYCqA&qkWh#R_MKmNbVYdJ4*X?^{P%g*H~*<@U;Z-7m)3#+gSl_OP$5L53>;1x zItLqVwNsTu#A}= zaq`CbY&Q(@Ra2DJP?rCbzHnmVtfs23#&T3sL)e6(cA+o#2Hnu{L8BM?)D^r<%Dx?& zeo)rCU;W*GN(WWehEom%;vttAc3Dl}8!vT{xX)&oYAwU@;y_UPMWF$QKYVk~XonN@ zTv^oB+KmcT4;(9S#D26@KS}EjQ&K#!$}4%678|OI7{S}#N&KD}8>eaDQ6%-+kbOyU zo@M7U6OwlrSzqjQU_Nhgx34T0dcG>#)Q$5(pqTy6w1zgy=*13{y*suEw^YjOkiSo$ zjk*NDY7x%eZAm+f6-cQR`Fac9UCN2e0KNan$$g_kkfUUqbqeRx-lgZpPj+K+JW&fQ zRT=aizU&F)1<5DqlFZmp)T=n|^@+~Vio8<5wR$YmLQ-td*M(R|5_z&>N}E!jwY6fR zm|3(f(JAbGqu&8kOyt?BVF{sBf^*T0@vfBiY8MNcC5z+dmg4=gSl^Q| znyNQe4|}nDxgw@+*{K~F(XJ#K04cKzr;Ik4%l$(T*#dMG6oGVc`d!#2qzA`6J|rrr zT+489N*Mqh5te-;mIF(Wo0@BTW0i*(hPo`?C8pIxlY?47vU3U%;bb&ZXB?;F{o?(B zTnLWQo&p3DW31f^8r5BBVG{rV-oZY^`RSJRf8mcPwSM z5e2D2E~!MxWX<26rN3%4W5xpRC~jB?>&lnW2YfZA8Qzb%7@XZp&6{@MWsARgyV~Gk z%&Gx*Fp6%_GDrHQBIz{#Nwj-A^{^=&5N;iQVz~p9IcY8NTrKe4+OktOKiw0kk8$TG z@bH3?EB1SMGR3a!c*4l9nKH+h`gAXUfs60!*tCSAv}DNcJ9EXXo&2&ksV)7hQBazS zUy~BPZt-t8W7Oh&IAc=Ad4F;@7`MftWit|3*UbsaLttQpENlctOO^Tq_hPVBdZz*lD@2evPLkF zg2V_%Pzb4yUVt{|Ou)$a!*2?~tbhK`4stLjrhcN(IOTT-s{VPw@tb=6)w$4+m25$2 zqKM=KHCL|wdEs%YU>IR(u>NxYUQi|x(AcEDJVDlt(!0r9(GTQ3)l0xVAW0 zhHT*mlu?R$j?cTLF{`9Xcrxf~CTl$IU0U)V<2BX%z*ZszHP!qZJ;?d5vNdxwNi&B2 z_L*p(pWIHz(#W%Oel%?I2~7oY)8T4Akzd*2%{=tr4y35{?#+?T>GFYwaq*(tHHg&y4c6DP$S-9`fI z&&C7#UF$jPKrm|9c`5&V^+MZIV+B@P>d8tcJHN7ck{bfN$r2xx=wFmgWkj+rvGBw>gDmhXHhNVD`5$d3u_O z;_+u|y&Bak+Ul+~Y+$aGVjA8U8Qy5$34=@OK$iLL1x{w&J}ReB?gAyf`yw}=N)47+ z<*so+_UJf?_it_D*FV5a+$olNkapVwt36qHo2oW3cJ;NhlUq83l#O!I5a_Z;r}<6o zpL?>ePhe|HrDJfdptY z!$%%M7dX888zcOaM;Y30C?nAISW$^pPEtd;C7b(25^@7Ly5usWlxz)$(M4Jpc93bC z+U?x?m+wA(>09$N0I%gsv|T@#m*tD}<3naqJpHJ;Q5y0aMRH-fJ>oWrH?-_CgV7F4 z^iWhz)+SgPfLCPj3{yI|xNxOdADmT~@CIM8b5xv>Rm9BrK|9`(uA6;UGgETe(ny*S zT=~1Q&($SlqilZ$V$^B?Hrk=~%x&$_d@KM7c70aJZ&NPks0GZzY;rr+*X`zOKimIUjXO;F;9^dTjqd zRh~zu;O`q2ZeP`&=^dodpz`OuUIRJ9^|^=gxu@%W;>GF*@)f+ZfWg}=Q>M=pm~Sxg zZ>Uxi{KNaeti0jn?>C#LIlz<%2<+`{lEhWV5@#@W*B4>qiP$8|r>f2{q*OtLT6Y^KM9{%}MY~)#5VQQ{~S)a95 zZ2YwNdk0)39P}gYT6gI>UbY-@Z++!*W=y&5oL70GbuYv+`~jd-b7Ky#kHsx5!>f7@ z4O!6fCBj8gnVq@jen_mH9Cn%&Q)ja6pv~i@1`jb!^py-Zd$GiDr5AligFl^aK4gGGM` zWiMW#Yr0U4#{ejb{56=`#2vqm5*%5GycbemXR|>1=(JzbvLxlU?sTA^&sob<&w(p5 z5SubEd^)D+U?Z}*nHkTE8fJ`4+0!b;UF0&!#zbuwanWFtQN8z@8{e-++%vz0J&p)N z0j)*p982S`UA7Fny|N$KS^?d6HlwC_gO`!dwCIJ%&ma;x#MIDImjrF@5@$9IGvc@Vtx)1*R$v8{ zZ9)JU+Ogu_RP|8H#%j@+giyzfxX193e6f{(mNa^d0+o}HU}|CLtd6`3rCpVOvQ{7Y z;K)45j;nw&^pp}l%Up%I&{x}v?BxRWb7L=%KlLTq&P)|q*r_c)Rg)^vwec8gWTx#T zv;&FFZ>|s3`IZlJwOyR_gGy&eO?9jD59j|Zt-D{Go|-Qg{i|%MMri{-Aa*V^P(f5# zU>|3t@!4qZ`8DOJMWv!Q1ZF8)Y(DO83U!cKHRp9ZCfA4*M4j#UUM zipo2{X-ydE-<=heqj%p+mr>yFSH_sSLt0bi`8OMB}s6LSru8&>`LL_3q%QbTJ6|5Pf z%VOs-K8Bnte({pO`YPxtwOf*00n?+HJr0|gzO*o-#xXGAuHE<^ZhSCIRr>9kNb*-5 zs}IJt6WmsJR_nW&2G&s)o%X8(9FSwdXF?l0N^wI92b73U=B3E;iY@JGqIUujm(zT- z>y@s9=XHkHOHbG+RnUp_*%J?jp~y=wXj*7j727XVUep4h%;SvSC5gh2C`YI z^ZXi5Y+xKF}x{Jp^MRxY5LH|A#@u^l=5`)S`2?OT-nebGN+XDX|$pRCti{|=L2 z8NEh!2>E&`|B;V3C4jd7ZK_AyLSIhm6!{mLkI1~yhR&QjAzZ(}X||=OB{JXWn><+w zdM*B!vRzDhs#C0?V9uG!Kq4o39o4Rr%O)c@yru+w{_&Ch-)%@1-)bXJWTko}a2Z)? z<;6#-Z{_%Jv$f5`C!@$gPIu|z;-%0y3w3TA5>iEymNL%%kW41E+68GUbj)|5Qk^hC zny6&9vO!=|yn&QeVJtCy@kM;==NzTMCfU*@$|SZ?UBdkcQ>kPGJ&M*2o%27_v&~J; z={g6S%Wg~iNje3Sk%KP_DSFJ46OcBjP`$Z=yi~c+#b>V2^6GtF4T(+1e{7n5P6?3t zI>gxqm9Z=uwF)YXyelAw&Biaui3@PLmGnPL>8JS!WNHUv8+t7yb26BV1MM!ehVJ>? z=u6&sLmZnY$!YHbx(qCAHVDcZ>G&8chY@J{TEX~W<~sglS?-?!##vX+CW!eNd_@OW zPFeMKIV0rlpU-cyglC7c?%9=Q%sJ;Q8(WKo;&^Y~U1c;#tS^V|FOcUrW`;?m8>#z; zrX(BZ()6D= zyJOs)G61Ir0{izv4hOq#)net+%B3*`5k`$o&bhU+j^yf9*t?2J)20WSZuUk_(%jZ) z7_k9S%rwe^WE11Nt4eXEA?E3{WO0#mE5>(a{w;`a*nC0_)s;xWaer%j$qoXst#jK42w6itA>ux z6}AR3wNpvCUa{O(_hg9ZbcuSA##plfbK#DKR(Pg~IUfOkOjh2DUM`HCr8zSoWr6Rk z2eL#Qs4dRePR=p?M&y1avcBDX*ire+xumoUTFEDH1a8NVM`9R#R&-@1gm?1+!sj=| zB} znriWAbZv=VRQEKx&-1G*gZu?oh%TdO;4IhsljcMGJ6$?UCJfr4=be5tfl~B977zR! zH%!%x{vlu3-MdNL)yZobZD)4X0KeHX)21U7AYufB_Nq*4ax5y0 zo75JNsA+MmDqzvj@+>Q$(~OnfouY_ZT@b5b(Twfl_a5Zj&~#$Z=@wi92&zi+0o2cB z3Ww@%M!h5~#W)F_k3+IF(}*3-+HV?}wZi zl`dcs6d1x&bT(KSde zVVB&68%z#1e3|WyiWePJu;4cXQ+t6N`QfvB;{wk1ewiXM`xFIAns<7L_wfWtb+JIf zDzHk`>SfTaBE1l>Mr20?$@fRg0!w?rmWMx2RNg!xy^<@t#fukD-JnfWY9~HxsO8B$ zt;YZ4Z6)})g;{e*P4-)}`AzXGueUA|vbRRshkpMP+Tyui#d2II{*Ghi?j^ zx}L2nEUFZjwglm_yxvyF8l8M5Uda}BC(4gC8N&U__b4v9zgx@=D*hZuvI}+tl9Q?! zbrz9bVI^&D9yrr!BHdU z&59TO+V)6%gkKf88)R0z%?_aROn1RdW!qJQm&Lex9bE&L#r$&ZW&j<9MShiR2OEX0 z+lMkp?K=DPL)HyE%Dp}L4)xFcoKNu)7S2_@g60GEY3(l$k;*MsUTnjrIUAZOEFL1> zf9#~0WGy`>?2LO5E5gHJ+~MQ}sxQlBcr19)9sMb;qa1{<(ATXtI7t9D%=2}+T}OM? zclx|R^pz>UQs^xPHc5U$IC@N4d!Moa*A+El_8(L|WukfO(#5D%nAH2|LPiRvsKX#G z@lx_SM->%VXvnenqk=-xCeMF=K`YxudWk@`5L4vc&fq9HCW zsLDM2#})dWmyMFKk}d`izNm@M;|kZn%yW5>zhxUped=VX;+S%_O#OZo=YYrun{L9L z*^}Zr(__e zhQ~%S?uJD=o!Rb`C-3=ld)Kbzd;0^ASZ24DEDqA4ODx1Ci|i)}lJMaDdi;~z|BrAg z04j{o1`Yy(`;|Atk@OoGlA*~K0dfNTe~rvQ5DUY@zB_&-i+9Q2T?|C3+ypEmLq4?jB?0|ca$^*<*I z0O~NFTFMLjCQKt9%x)5rLbI|~e+5Bj1X`!#1;Tz7GSiRLk4Pt{IHG)q zp!r4=T8kPj!a@=3U9?YENcmWZ9OEe6J~8Kj%{DKDEu5@wl~=Xa3aB>o)1dT_h1&t4PPr+ya3@d{BQb5IjFLp4Dn!O$%rO085)Eqx z2x6f%U$YGnASE!I)T;Y09$QH(GovaOJuj!uSICkxqp4e9RT#9H(b0Yfl?9zKm*V%E zc2jiWflVDNik!a-_OW3fmI;t11+;UjTigsylQ1_fwn$s)&O^c9{CGc1#XHXhO*{!u z4K!k2;Wl-S4c`Kl+vBj5+wpZETPqU2AGlKpdR6rUTv@~@Pq>GU)~;2B!LJwV@eE|)Xo?*}aonVyJ@6RN+f(sTj2SqpV%RD^l+BN>$=A`H@c<{gA*c;$l{+E* z%;98WTl+)dbVYKFg$wQv!z7f z)nvOT+qP}nwymi%@B8JP^BeY;z1P0hwRVXjLj8(XtIdnjy^3Y>oMU#)&%1kFLWFrw z`0_O}R@S=;exm+06*xv`zwq3}S_N+R&wst8u3SQ+#k zhJ|w9bUUyxR(@37L7;lMuM;iTjR=-oTDus{DzTjE3eYe)+T^aw^|6t>_%V`ny&}-2 zUU5oNVyUFgr`QhwRyOkp!C=Fv%7L3^(_dSuLSDS{f9o1gw-rdMA&9^CT;`YKh#t;* zBX}|DnTtI`wRugF^H&)EhVYuQ-^vFt9T)N*Gelng{yW#}E(!&mGpk63&w9M)!uify2-a2Z zRm%YBhcUnc1O=xmZ^cQp)l!+YfEnq$z8X^$J5eUdWzVFR4%)RKdB|4ycamZnYR5;u z+~BA?SbarFe#xkS#GuTsbub8b^7>75)$z=>!Eh6k_TaH}zhy=f8Om=$_1fLgFl@6|lgbruDGPA@?)982jI%yGt? z>zzF!haZK!b?C!r6wEbkORL#~I>AA0%5bW6KaRY6pWfohCQE|rzYC%}t<91zoqoM~83=LmHvcnB#WpMw}q()H4 zg!Uvs?=wfO0&pG!>?>5LwybZmhJC+Q?^=aF6JV2bsUlpw zz(00No!Q&c#GXH~?*;D*w>f$7%{8vpQwfNpS#1>ACMFz95&M(^2{NGnps~3}-XC=b z@Z%l@N_rWuNpcb5vMgO7r}V|~Zk*>+vee~4R^0ZIiaZm9`8(*VD({DV(x_!SW-8SI zC8#jYHNs0XA^Ubo!cTJ+MvUpduhEAZV}CSfC$)t*zKx zgNa*!hys0X&I#NmJhdL3AiFlURa#JiQ^KTft)zjpAtZh8;fV#^GRFx79)hw{T-~9E zq#_d`Sv^+P1oOdeSsr!qA+OU24$F10XV&-vdwVFPU)q>>?Z4Null)i(d`T2&{CPZ5 zdoEC;Jge)L&0BhkZs5r1$~;fck-@9Ao}{!KFXcfB5~Y`#YZr*(?K_g|ykzbGia=A) z{nvY6o+|MOuOOX}9vgC_X4T%9GfL1BDQ%k`&reYcI#aM%;%?zNN|R%HS`LYJm^PFV zqMChEhp(1GyX?@75(WM=XKA~mFcg&P=fBbsgA;m2Yn#zAN6OcOQvFjzrO7Ud)&nw! zSBT>58=b^CFnJssI=|G~hX!W@itS7d3K-}0siItpR6nI4?b{}=gkrUtTD-mM-fi?| z+sj>JC$As`X{B8|zmTxyrb$Lz{opYaF;!lzDXy$knF{EHqcp zT3qAt$4&S)rP{kIc0oTe<|00pwDm_7OQzD+Zt+Fva8ztedo77UTCV9hqE~Bq4M*Pw zX^2ntQli(P7@;wDiTfKHL+jUphhos z6drUDL(xCU5bg+$=33M^pem=26)B#}Y-Lcub4?t8S!u>&ve9U#)A-VU60}M4r?F0Y zpcK{KUqjQQ%xPFp?JDuxO=C99tt%8DIUA(IkUy?D&N$eWd2)XNo|A(03A^z8>x4bN zU~cVX<0yJ(4ax+5or8%raFaMZ;1}x$)x7qBo82h(kGui3UbR*VK!7gthS|h|cd}Wb z)mPO8^waEARk=q?0xEFx>!&_iuGnKx$A?3(j~Y&?)LH&$n+@3|;y_1_B9kt86f{=) z^3=ZVa$ytct(YHvF9{#^h=sm}{jq-md0_>U&Oc6pNw5Ugk9O+H*|<@hId$W(%uKSwJh0kj4BQUaPBOtwXT%qIKK@C%v=?bBk|# zes}tgk)N+>I^69H7RD*MFPnYG9@xc@Sog)hNq+Q@6XiF7!$PzBSZeM28dudAt`N<& zCvlE)QY0iez(VROr}0x03=7>`dNk%pRvJVF_AyNLb5aiu?YP!8(o4^6eOe_mBhZS7U?AaQS3c^vwbAuq?>#Jo@S6o#mbaSi@V>X8S;#TeV{BdVP1=am}`x zwcGKs@epaRYFL=4y1pXI`m67n#`q%X(h#(+)gcyWYR&d;_q_I^0zCT_YGo@ z`&|+d;1{+24%KCKt;1KTnBdW(%8IY+eqCd0mT0Is?(`7Q={LhV_xg_l1o!UYmod>| zOSp@68&Zw=a^a7&kxaYr|Y8X-9c|Kfzs3^KN| z%@PbdY(trF*;Ay#1kW1$RbJCOut9(m3U$5)bgrm;jxSRQ%fU~Z9HA(6zIF*!P8=}S zoR-AP@NcLVXF_4@ECmt_s0M6lJ-2>o!=6&H9y0eLS_aySp+1#xhMz+$TMC@PPY~n_Z{6fXBI%Z zPAN-)h7z+`(drM(c~64!7r-g_U)1w=zI!S71=#aT z1SgDjD1X9i=zj{0p2_^iRGT-$#67R1gzt+!^3jC#tytqD&p3XD5 z_c)1%_0rybmMJjk(Fw>*XZv)G7}Xa&n=9BFZ{1&ejJrDgeiOJ~4AJ`Mn6Q8%nm18O z7ft`MKY>eApB z{4=tJ#jfqiPyh6RYNSmP5BP`~!Po-a^uM=C`hCdBgWjJ*eBNo%W}40fk+%{jgq@(vgSO*uThP&Es_%%7nfX|GpVKi!T9HzZF2$T937!c^;E z=MWIB%4YLErH6q4O`f&tl=n)3)8XE}87J5}B=wbI%K{|Ml!l4DkBNvfC4?Nf}~g;Hq*E44WY%8 z6H&Xo8ZS*>_YBb%Ga-!&`7=q0jj9P9%ApW74A-9r)UTT~{vHJcQhbN1s+7A9m1r>@ z!k#*zF_|KLZh9RHf^cIcZ9C{l zH*@OX1Z+D7&)+L-3eU zsbA=Y4`J$+LCI$LP4$LE+Cb_?COPIozB{)nq>y|=1ItgRAIL<%=SuT!p`VBMCm(1W z57k}0v$!6-GlCO_Uc;+{7o!6;rR*GC=G&6|-~Zj^ov>uJ1|!?0TDo;BcPH8*8KxyM+A&Pz9q4!Km$ZpNwhX#C*HrJ`2YCJ&UoS{AcgtldLF})bd zaURekTaj+xKzvb71!j}^yLIbuo%79u6D%fEQJOQnR+uqVp^@Km*-`a|NOnhiRz+UW zhH@k}Q-28gDu(te6!Z(@WG$)LybQcthw~f&m>o$H^W13X3Z%)(=cKQYegN#l2b>9` zS$2~2n&Q9X{tDmVz%;;WdsmK;>6vB1O1rhevDFUiL*IYzuIPa{|M?ZI1F8CB3SqNB zigIL|KV`Q=`u5j8&W@hWpa@@VXCkoKU)uDGbda^%phhm4J1&0Jfx|jOK^}@m*mxWe zr%xNhtSRBa87W0GgJ%H4`u>w4N5ylIt#LUy{ODtEPwQ)DU5_)8hgac_=QWqlrQXoC zCyR$ykMmcLOcunc@aVuJ@}X@l#7y>WE%;2=x$E|OE{{%1cUwnzkH&x_l51T)#Hq13 zKJMSoF)%Gs3+(q_%xv8WlMi#?kK^`$4#uC5qgU(#McphGsMtU4zX-)EBpv~R(s-P_ z8VNVhK9j)^fMObSeFc>T{mGAeBWgl<*SP3yjm45^ymAxvh#NVDNtwiOopPt1Umj(} zfgrbmCt}P$zFZe@X%n%tZ7NvqfA+Yj|goYP*Lk`_*s7yGQo(qGyDyx8Tr z$RW8#b@MszXO54QU8ir?m0x&lf&?Hl4{{(X4tSsC+d)#aU*6wKzkj381(P~cFXS^l znl%2(=Q%?B&a;&d_f8VqDU3Lv_%;;XH}6i=H*c|b&Cm3}&GgVRm+~6V9Y00ITSu1V z9^l3pHwCE`KUEYrfBV>N*Y6(y{DLxjw>lxG&@Wsjs7UKXo-^|SG&V@zy9?VBR}`nV zYI8HMOmj2QVYuVITAnX{{bep2qgZO!!m-kLt+dd1y=ON_6?8Th=>$#>C%8j<+dPKI zj`o|RbDQ=N-80M|WKXel;K?Ul1U{qr-U;D%5Zo(cfH|#_Pj|28N#Cdgl%pz0)f!MM z{SE8Eyg9le?BZBh{;dL2!Vwg+USi@4ZhJ_}uqFMNuy`_?LX9viR9yjo;E0s&!tw*agKy`Js0 zm~M}f{xFskc!(!;*t&Hm5CQJfa_c{)7KmT(o1K|_H4>HKnt0lwJaLdXouup zo`(_jU)RMrPRkY%!vzGYwkYB>+A*VV9)fZ~jv)ktl;3|F$W9CM`DL=lRgpwiiQhTP ziIZi;gzXy?N_W#NOJ3SCQ12P!6udl897$u0K8#2Tj8>h;cz^^4yAqS22DXt2DdGCE zV#i;q77;^#pWBoa10Tt{ZjUUS0hio&QRa# z<#~=BmYZa}b6~q#^}^PgWD;E}NvC%DOWm}OhTVtt4qx(l<=H}_oWAS3#jVqT5<9Ke z;nF*DXBLuNNB?ar24YZ~<0 zDs-Cc?fmV^ZGJooK1tW^Nt|Xf+XaG4bZbhPLx&>0C~(QOG5h8G5kj9jO(b5EQIN0j9a*zpY0Br+7Sw0akoeCi|>( za&WmpQ8@^QO8t)jo3tE1#U;Y4j_cU*SJDafAmeN0&4?&HqEl3e7NzEIo|Za7?xP@g zs0j5UpRRx?9aiqn4uyiG(-0cs@*KlP@S;m6h=i1d8h(w~3b&alM)D$9C~YZmYXW$- zyydVR@UlB9wT$yyYl5T?(B1dEglhupCPTd#e^@F7XphvJSK1u@z}IU`V{2LJ<`7=X zZAA!Lh@}juv#p-^%6n)%^iQLUBzzV?%}$BTdwA$9At7;LKX7S|23jVj!fTwht23_7 zEp)r=X+IvMhjKf7XW0XV>df>}$pJ%a;=LnDrYw9p@5*gOax2Ekp)_nU$j84zkze*8MxcfKllwF-%+SfPq7l+;j9F%l*G*R|NTp zoEf2*O@B$OT+94nVvazOxY7%)xDjDan}dsX)vnpO|5KZNQ6_xO=_q6-Jp*)mxVSjz z@QwUZd&Fl&5z*Zqc~h*mH;SLs;7KIEVpEDhbzM#fWg8?2UaP}sw}(ZjW7lQoHO+-7 z#6oLc>(-+ik;!Af`MTbx>jF_mqtV9Jq8=9ONhDyo?T6V&j*{Z>1-aB%)NnZ}W>mL( ztz$|A{0R0>2pyY=gG(fv21|9og7hsN5iNyicy-iM5j;YlXEG#E*6?D=P1g$@aa0&}x^g zxx_6^xU_3lZ~3G~Q`qBF)rm$PG+806bu%hjwWLm}#OLTc!O38{J_iOcEJzX?v7e`Q zRL*^G99K85GzD;xFHPB{d8I`60BuF~Hr0kwgxWZJ*;xyIJ>D|4s!}%+V%+GU(hjcB zsbb|fEtbrp-LeS2&@GmmH4pzpx(rE8YZvViT~J^K#^V0q)hvd-*mE4oJO8B9U5$Q> ze{P0yLaJ86iclNd0bqq@p<3czv^OckIJx_T=;PnvPB@nOoXgW5tC&5Lf@hF>OUNh? z_7Chr`B(3`=^Fc&Mg{7 zExRjix{9k<9jJLJ??TseOSYe(!Xh793 ze5JXI(uU69tCZ);*+$77v$kw&_bt{N-}k&zjnaw&P}ut6fZ(11!uxj%{;QfVFPGv0LAoC-s@AePV|h-&{#w`Ce&r>e?CnlTx$cSHeXo8KU_v{^Z>F zXi3GRzjG?9^SuL|Lyu_$OkemWZc}TBIdz?(P?;oVB=`5$(Z)uZAfG1m*)u}Zdp4QbSROPt`rQORn){rBD8V`Y_6->hGV-t`wB~J z2Cnh+g=}TG*+seTTKX-*+K5&;4;BUsI?B|vKq^OpT;r^c(OU^WUO8l`(7F10V`{CQ zeLkaR+hM=AJyL8s=7$&4-Xhapw!S1k?@wCP%1@puJCA*gi2 ziFV@9YHd}8zgt<60FOweTVGt_fw1Yil!myeJ!|a3g*tjL(^XEBo$D`ZBMcU{R#QEst6QGtTZwU)+_Etws# zVZx|h7E8Ki7k0&acvpiO!xh;D(a=f4IeZY z2trm6#8^I!qXdc44wQ@7A!Yws!_+PS`W@xob|8R?p46}DXEm98qSc33fAlhYjO6)~ zCZhIae4tVUGt)Ada+M?Q!@C${$(#(Zwar6U-r+|r3lof!j)(mTc0`kknkWS_BQ)1? zPjhS!lS5slu`9Qa%fDXVR(%ONg%5mOXwDa1(j3YeJY!^9L9agPz7s_bedw#B^+fiI zP0i_3xgAxPX7wlUD=sUeYDLxj`Ad-Z2=>bA#P_2Jt%cJzeQpXl21a*Pf`M!E9D%;t zP_gG6fyI$rJVBx$a2ffX>{Y_?Z3n2Q%pR1UWCs_xXqw|ZoOf%?`x$D0kHfcLeXV8b zMv*wJGgN*I9?4FCutag2L-Pxl+pg!jarzF>zxdws4VOR%=7HQwrHp5!tT3Yn;jsJ112jL@U=IkkRP|$G(UIH)tIo%Rq`zsbi8aeQ>+v^)i z{t?$M@cEc=I4bZQ{+Ar*U80yxF|9{Iq`+q3)!Z_5YfY15wgT}Py~xSHwU|VaP%{bT$ua+3Od(et z7Uhns6r;*XOPmn!iN+cmNG_;MNf_*|btCO|?;OT}}Ed|g)LhfpwcL?kToK)+*Gk6m~HT zg#6gAtvycQk=-lmIBp^bjmpifDvR%luTFXSHj597J9ngXO#c1dRc*mWW%wWdOfcL(c{in(Q5>#8!)=|FccqxH&0rDUqprx?u*!)LSR4lNNln+p$=iLE z4_v3GL!^}w*TrY>&wtL|JjNd=2;D(LL+k`Y^(1^b?0e0N{zVP?e0f1Pq$N2 z-|z(rd~qlnuD0h^bv!bgdeB;jX z?GCbid5!@htA#kSa+sf%v}6OI(541BDQAs{J8jqsM+EZP*#|W4VG08p8{Mrv-*(h7 zvg6lAe3E1q9A{r;B(%eL$0f9SJ@Qer-iKyjeLyehOimx=iULcJ{tofw*LtAz>JdhZ z#-#`b{k;~_*^>$P#eKO?4Ek5kZmVgna%d%TiRF|b#{ph+^XoM1_FrY$c<+hJh-`@S_}TuKrSRv*;+K#^B76|KhFIyrI35$ho8OP9jLR}YboRl(M>*leYCnfH zlRmjc&^rQ~@47#iAap0n^birt!ZGqN_G5a&WSocRZWp%~VT1TXCJOueN5z<_dmP>Q zC0$#czlVQPqeo7ozGB(ps+pCv$Vk^louGfvofK zcF(KN&#tS_r;n?N@K4`&sV}a(Y>?z&4Jh;pe_V!k0Y`lvkrGL|6}$e1m=cR|ar!)q zA7^*&1hEW|g#IHsbt5S_r_wgCz9F%jhmYSBy&hbOh_Cw1{h4FyD^tC67G=#Q5^K29 z`K{=2FktF!7rYu27b6Bl&X$eIa3MxX#A|66=@mDW$%E)rj6>CzuG09kJB*^x_}F zsafvvYO03yzZ8&?glW0twlmzZOOda3ruo!}3SlGa-^Oc;E{4!mN54#F&(NeR=*qZ- ztWL1mab&D3ZxDm?$pjIRLWno@$GxuF=Bg-E0ZTmOXF}m9GrI)ZJQP_V@#qz_F__N3ZdPBak14OA*PnfyZ`n58C3i7T?joU`m@8Vvgl1A!;I%61f#k&`0hAEN;l zg4&KbGBCperD0 z#UX)Cks$x&If!yEFUmsdQ0HOIkA;^y4rOB61rOi-F%6v{@>DsIHVSaTkP8}{z zf!*_^hdZ(xk}5`4r{)+$v8+l~)}YOTq6gF8LGPHI$RkfpCCz%Bj7{b=Dij^(8jeDZ zud;oObZwg@yL+`WW0ez1f|rAo=<6bS$QxS87A#(?s*Y9b7^)Lq)~lHdz%EY7!<(uP zT$cH!SN*uQ%wh)KlU|l?H^;8>sJfwkjT-m5v<2+V15W@JS6fPGuWJwS!KKr;l$*wZ zZZ*z-n2cn=?&YwHSYv}VwauRL^|j%S4qZ#(If(|=B{M32>q(zUf|_te{6#RG2dcNc zSl52RAWPYbkCj1F>;x4TK*MC#ecNg~8$o_w2twgE8;r$`w1anJt3)_;O6rk~ zj*XNE1bI~tv8$XgCZBu+pfALWK%^T8iK!h9q3AM@zUPK9j=)GT{{Thc?+NgcGIeMo zKoa0Ua>AvLwCofUKJCyZs1bi_0B4-hIv5`lUv zrlRJO;X~t{uZCp^l$Ig!u+~=m!`&;?ghd&(B?+;I*`uR?G||Vf*Mzh{QD>$7WiK~; zpUgql`tV2jF5`2Vn7BQAAZ-z;Gj-Wtfp+%(!Pmj*+XsR7crznC6$$6L!R^6?u_fqa z=ndR&wgwxGIc8$j1r>I3)dik%XeK+s8)m*-5jd??%8P+hO|t&ZgL-`VSGn#=AxYB? z-SXQ34(r)~=pLVjh0hPBV4qjhHrFuJ0(3Q$y)!MbSwE$KFMwz&&A)j5&U~MNjNGq) z2pP7EMjS@XkIU+&d~;Bn|1W`B6qJ=w6&s&6hDpDs_Vk`MO`Zh3p6+&on$l~bg6y}H zE!#t^O4*hYySWQ;dP-EbNJAPs(R&0jl=E44hzcA)=Nhuk$BWr(5hAoKKRIZ5%ieSj zW#eNMPv`l9NoFugx+N1kcIlO|E>|UkwhOhKw#%fPR$*^3J|@fLouIFX9)CbAl-2r` zxv}t`X>cS9mYrL+lslL@9ZUth=!w(=+d^PmpPVbbONH*Os4r0Jo+)*)koebklahXy zv1l^@VZzoD~G88xsu}=*syBbn==G5oyjjAylNBPcQ8H)i+Gu z?^oNKBN04g1~!-WI-~Rb;)|rWv2i};gM(}BJpay6LglRAf7%gks1w8)axqkGqLN)j zlsr;I>(oqhE&MSYf_&tlXP9;85btxRbhM+c@7$cHAB#*>rWm!&VD%ag1}(o%-k}4y zCy$&YYtL1YY8ZZ{X3U2=%}K&uN0E z*W$z+3#CF^1|L7CZ02hCfz{(QA3u=&9gz`3rfGxG8 zHco`Xmr{t9ZpfE3#M`qwj75MLe$UZ3BYXe4qT8`(R5F{}XX)UjeC+N!G-T(Ly9n)g z!@d{4g(LXOIq4Ff)DcB__k?EO)F9WD_ex_r$yWFp9L+d8!`24FR>YWlVxK$Y+xs^` zk}Yt7A_-BEbvsT^^3@It1?LiPC$CC>&2nSB)3+hyKn3f_?4lr^sV+3_oNHk)de_ zmp^n%q+q|?UzA3{(cVe^XamKI?be>X<36-o(sH4GZ{6YKnnCH|uY`|w!>;RKw!<=^ z++l!oKag>;Z2wY4dIroXIR(g%%9mnnbnD0_eShK`5Q)-v<`{4UqaP9-DfHX10;QsD z^366hpjuUkp7K*Q4e*V5MdwYzDab!Z{c*CM-z0qDi(AXfQ;eQ(a{4w$i>|R{7Svlw zg%?w|`{%j&-ehBA+)z9c%U|cRmnzgZ13T-;KD;gS2x=S02>_xF_B@@u{lR=gp-YlW z+BAkg)O35N7}2Wgg1f{$yLxf`@I!9I{fC{A0Kst4%uiqA+!WD|xeUaHghqx&mYtji zDPxuceRIbpQ?V-7mhd<*&{ChqvM$vFYyP|fw*tY(7_ z#Y6e>#eq24$Q|sz15?P!Z)|+XO@TI0yD$LM{8xF@4Uu*j>WB@Ck-xn%KdczyWf7cV zD+zWp@nWDoqxF+D2$8jJt5=mHWr$w%YCOM7;6~qkB?C@k6#fRNT$*2tk z3oQC-v|c$LR=iL4cw83m+QL2^PqmPTFztM&!0#2kWJiuU8&DJ9uO|JzY6*L=GRQba)fruu z^PVB?J+{VSac%F( z`<^cjWwz!muQ9u>8m-@fT^{UzMD=-WL-JY5D(bQIh*$?4IsW__jb0rMmk1PE#!PYF zGo~OStR@}+^0WlqDUGjSo%s|cf)N(Qt=p*iu?GzvhW~V)9$3@xMw0*qvnhkAEMwn* z%PYBND7$75mAdoN!|)7Dy3~rlE4XYu8X)D|9kCi2>QX=dTQ(~61s|!E0vGv;+MY2d zG6TlgeDZE@EU%eTY$T#bn)VnwT;KX(@>1nl@oUH$l1F9*fMS5=171~pW)PV`c+ID& zb~+m+(XO@$_EGxlN*wUv@luiSyY*1C*aA?1vz^*P$!r=2d^5F9uuWgE324U;fs7$j zHuDea30w6iF&(|3yXC!()Tr6SOw@+jjQm6i1;%X)==~!P8*rN0a$0@OND{XVKBivb!<44Un(CzDDewmhnh z(66(JKz9g3Tgkfu&n;q{M^ji$dT#37?qNas*l=t4_hm+uzE(%7MGWO!{4s%qr?2e> z2kz`yK52)7>}g$jX-j3X*H9yGD13je1^i^csa5*Q#b{O&~!w&zUPUab#0z)~-ysn`zN1Db(Dmj)>9A<8%4qA^-XzfNT4B5z&}UK}NQWwZh))r)$e5!A zntL~Hv+qVC!}C$MgpmsJ@Y$FlNqm)lkIubuL-?AnUB^;*hZ}qug*Fias=*dX>wN~k335ia@V4_oN?d3j6Hi1 z2`e-dokuSVlsfiOXL3R;ZEweyA*zNgnpyIms3$xX*JkCTZI;PW>1_uq2C=a^6k^sb zmLN))2@lQpInmqr*MDQ0K~9lFVGIqA8s7agm7PB&wC}jiWH@M+mV5VTVr1Y{zRip# z*A7(B<%$HAyY2onxQSdkuv8-pj)&1)h1WJhl(~Dpq8`qR=gG)rF>rE2UiDy+(=de$ zyfxoz+?*7Gztc^;aLbxIEbYOkgcNB`b^R83hU*M1y0M$x>}P!j&t%VGA5#55^EEsB z?UGh|(DODccI7>MfOwJwF_jX+D{JXF;s})4G6@&@*J2hB8P;YPr=K{FrapTXxezoF zeE8+UhM7<>$k!<08^06`UWny>*wQr?uuRwyWuAh=t{i{w&$iG0p&aF^9W5zTEzW%SwZ*0hfa9~ z-70GS(gr5^^`-Sc2&bodN)4qC*VQuc3)WFt(-0^##bU<5Q31ZdAAzh?F6K~) zh?CihYFY+R7q?OcRkNS_CG4M72u^9;SF^2E|B4LT7dvv8K}{Zl&xG`UeIIQEvo`?A z)3s;j%^vUfN7{B;lN-{#2G@TB5-llm$|qhEq!{)b3%g6Ve{Qr%{fMJ){dUyJpN0;Y zXQf}fyZGd_V<;~;a4>g0b>tQkaRiUTTHGqX>cvx@-us4S`LG0D2C^TAN8P8hx;R%E zFJG`K@;t*o#QfEE-=F)0{(tw5rb-{EumA1PPHWo=+HkYxX#Z=vhOsAi|AJxywBdbJ z4-y3}%;QpJ?FnUJm~I_BDC4mTzcCAaM+WmJ;{*GhZHE(=Zu&hHmNgzmdO^Fqp&@q{ ztswwzCP};AJUh3z!I-GAp~B9!tzzx6)voEnt!<;R%qIO~%Kb`~q7NdVZA{@X^Ue1^ zA|ycuzq_APnZ&tHA2lF4*}65r*R110?<13t&n@Ah;SF4`htOa}$lGEi>2WgAU(&J) zWGj0&D0B}^;$_*rq_VgVe=#fKCT-z#agX>s>gD5KSqpXk>UMuD+hWAO|MXxxBVq(F zRk!3sg)T9fEkJ?q{9*jIvU_&2G)DuyQRjF4q8B8~ZJ4vUl}8H~YxoDSQrg4jbD_wF zT?!paVlr!T4+-e#aj4e#`S=ikp|J-ONFV#H_{$jXEN`l9XQpHd_0^~<)#JwBPC~$x zkqi1sUN>B3@vWE3@ClkHPzVCjU#RE%G||mdLd9cHTe^>LcFfG~72Gq)Ut$Nxffj6; zsg8&BM4D--D}-P#Ula-eQCqtJIv}>gmuGocAzO<8?&Xo=wGU;RQK6yJh0Zr^FnUIf zlV`21C$JOtN*2Qe6yYrr-r1&3kfMSoSoHjO#!q0#=364T9j(gzH+LIc>z{UpEky_p zk|RQ5?5J|yx7s8~XB(}d2WdmN623XCsTEuW#I zF)GDTkvbY(n~wR>*-MW^W0H$t+$x(Vl|VWj!j>jJ2dSN})L7%zB93bYTcu&CDpjpz zPS)H~rGUKf+ROB}KcLrnWUR*w_dq!cZQ3bPU6DPbV+srIY=I{G7G9Z|{K%R;&04oZ zIgc-mYMLhUmU;+~N0ddIghi#SC~U}05ig(;%h$>qVH*%WLrIhQ{St}WFilZd??6P7 zWaU@-rUKCtXvm;X;cmN79ND-N)#>+5GegpFBMRi@Z939I8+63LeN4k8L^h9naKQEo z?&SVFqdb4miT12P}Jc-%*A$%wL|7{G*J zOE*6A#`Kasl%Si7!S(G55zRWw^e=V7YipFRSYfpvg7=q!yi(x?$3P(6YeEAFiH=bx zO&{YX%wi^JT1egx^Qc2E++*4 z_k5+kHB{fWznqUUMv;SfWQA;Bfa%pQBn$#xLDq}{)90Rw+of7K;Lu8%>(P*A2Z4L)aQy!>?XfK{f^ zX?r!7IPYuMH#40b%(I8SzP{Y2cwA6!)py{01|q1GG`Pct+*U#6hewo^w(!NW(N#X{ zeJ)7<*IHyY+Y8ACZilEr5Q_KTubNfaIl`#ROM|J#>8>4l5?2q9+Hc@HkM@)uvKC^3 zA&{@F;#)B;alye2wBKpBW{=seSXf%`hg&Bns|g*a-M2OMSA6vHu3%e(C#<<=!UKTa zY$C%hQS@t(NI$ZdzLK!u(eFrJXjiUY(F~potXsmL-SrNgMWn?c{4jLUKOS@uRj1zL zUJ5ehsH`&`+^dPK*t3z3i4~egb6E-QqnnF{g#|@=tzZo0|0ul|)lg%CvUV9$3V$N~ zetUZjAcn1=Z+IZg3w)3qhQ9&)`HukaBsJLgs&BrXtVwn>{Kl&Wp;iDWVRvY}ky(e9 zGFH->y9Oj!(3l%i~?L_9zV=i-`b>{d&Q%x1d@#?)k(iE{$)eF z#N^yaU^i381cOtwx6w{$c~vqCVp1}Xnc`^qQM_`xw%o^6hKr;~?BzLldbt2DDZq%i zoLiH~Y_TfhWe~04!c>yYAtpPLA1n?Df<0cOlr`L4dN@=id&e2^*=#;#C2?W5ur7DR z9NxPQvMFzYxYv8tCw15#e9g>eim7d0#Xbt?jC{-bskG^tscF?ol}%j0J*yfB^EdMF zE7ES597Cx$GxT1;5uN_Ls;xAQIWxsx0rlUDcXX2?;#YZwIghUf<|)9~x(u!WL%=C2 zCGZDp#gllh9Nxr6cN}9F!eos<)yPWPgl`fDS#a27qtl_|p7W2UV;)l8%>4UKtfAXA zi)rw~0zG{kPLfUg58PIlJZI(Rve>Z7l{JiW4N~D|;$J+!^ZQ(J^`tWqDvuW2^n*@s zggaF%*M$iCB}JUW6}5pf$2_kpofWE&pf!gwl}soUVeP6LC}F35OpJ874}PzZ9()H5 zt9C7i*)-h7%5s(bG%(-VI<-9xS22}4uZB(Ia)W}KkOi;sa*O9aYeiAB!wbp$g62sg zl>gW3!*lY2|BiIXC}67+qUhF zZQJ(5HYRo^HYdi!*v$X$zx(f=J^N0d{_3jkd#}1r_p7>fFMhOTygDaVBM|?qOm?<^ z7Je6Q91cL%rCuhj3ZKV+6azp?)jrn$d*|m||Le7d>$RUP%I&l;W&DS&D;8D0 zjr0J0l{_(CqI!OVzCD^&YamOKPNMDQ;t9gDUceAxJOCnBZ%E09L|GC4^CGa z+H>d+@(Dl$z6Xx{fN5DK_N~>cS_7^=RmOX2{#uNYrAX4>Rlo3Idl2{RoY= zd&!l!HD_(igvPZF`31Db0cubAO5{$#Y@=-TAMnXzT44*6>shqK0ysB%$X5uhewQQ) z=lzsxQ~BQ4#k%E48&I&4_8C=KrX@_2A7?dCL2 z)#$51Du>fkGv9%AlTPOCY{q3yf!+hy>&7f&uWleh{A7G=qcv;<9=MOTrUsEAhEv!&M+!?hsR z6^+`-M|a_+FMSPQ41X-Mfc6DXwhJQ9M}DEQRrm7N(hLWp2jfB3z>qCB&_Jz5+rS7R z1jRt0;Fi*sqsHqB8i|Jm4kgM#M9?#%lPxdwEtoDsP{C%SE3lhn3izE960I{pQ2&;* zDa&#D^RuY4Pk*1^8cztaS=rwL>m~hbu3i(vPNeQD!dVK}MpYTD zqzCZJIg`1KYY`ckXJNss3+UmmrJU3OCCY} zl&EBEqK9N3q|16}c~$C|b#_W>P3rHsUQ+bL{)1J~dV!#t7wt$J1KejT)!0$SOn{-y zXlnIHVxnAR1+~hLb4nN3KAq>PWO!f?>W5ESFqj%pZei0_kNirqgIy|2E6LL)@FH_J z=nTb!F8oeIATR1Jd~)a869Yb5?Ga^xJn_x?USm@+$8XdUQbZ*`$4e9li{bVO5VAi{ zj3@OPE zS$UnD-jaQ(oNvC!@uJi%N!e*~SuAvN1}j`+U?^(h*+kA;47a1Ac=7RK6Gki3F9>7& z;>dZ9>&cmA*`=tqq6<<|w+kDm%IDA6%SrdZ`v{a`O~G{UbUR0QRSw;fl#Hh;P1*)X zOJzwvwd6v(EH56Jr9#eDGmC|lsQE?(?UGVArWkzG?TztC_apP1!eiR(*G3M0>F+#D zZ^8XCJwUC?nj7$ znb?m5gs`ZZd!hC*Z3Ujjr5oufFC*g9&P9${p;3(+nP-%e>>nz2iw7gE%A4V&MRw77 zukBPyvG2@&!C}6&?$`S<^FL%s9PRN0IEg(LT#CDe4!ThO2uE(kI3WwQLlxzV#6P^A zLKM@d?@kXZ^gA>-GJuer`$=`uDn|V6o?CzoqM#;K2wZ|4 z2APPO-wooOpc6a(K10at#yeJCpOsmbdUJap&*sNR_YX!IIbkpqDQdFjJ{Z0Q%Z{Wl+5J^hKDP#bDs)H zrdTCBq!6iS)yv7p7xzwd5&`=kKu&nlw~&wZYBW=iaV;E_gHB47b@JS#S`0#^)I~V( zg+#cq)by~t;EQ6#a_1Db@#mItU=uSsoAnaWlv|nX4|^$@`Mt6$+}N$Q?eT>>Tk%j} zHDv|c0o7J)fh-<))TqJSD5{fr&R7&WM4j@XTvJju#!L>C#$dU@ zsaXQc*+&V;{C*cDj9wny(~U}CkW}R$>sXk4F;6u10#Dbs6(t2$)x*MbpijaP`GN;U z!L`+mRDoklVk&2tv#eD!MRPW|Rhh>?S|U`gBvCpl<=2ZNf-)~{C;0~j^4kjpl-!}E zqp9ZJc%}5wS#$DuX0FnLbW-!Z*Z3gw(>z)jZ6acC!fkz+d1hE^rdPVny@H#4vN<#d z>$tOiai{p(QpN0+h?yslR**7jpDSY$;uqzO8#QVxS_7tp)%Y%n>pD%+(nj%mV%;-! z?X%{$6P<6OYm+OZxq8y>j7=5sugt zl{$1m`V{)pQBxd)R8M?|E6>7h=zsgVni=~87HiC@r`$HYzAC2!3qH4Fb_G%*y2-^b z^@Q{psDCi=QY#y=#ifb0|M5sv{fy7gnru_8+9y*>Cu<tj+HV546g-o{PS4+ z4Qy_zC-VS8pj5SX1}0I2h{- z`fM#PKTdjan*YPkWQ)y+MFh}`m%Ae9W`E)ced%)ifTWT zC)sxBvjqg@2xvKenMRpeH>^{_2*Yq|Kn09KRUpO)1EJ!&_2OO$qf}hQaEjF&bUB2A z&*d>T)lTqoe}7|nqPcR+i~C}GQ^@h0xCftW0pMg`Xf0|0h${5~`n}X!u?*3fnz!GS zWeD~J2+A!zOXS-ms!?>j-gVZ<9NzH#_BvbtS!PkLYs?lY_19-$NSCtewF z@a#FRD`5LL1_RKlq0=?y1a03kc|@PeWY??%?ANAW+Ay;`qNVSCGn^pUvTM)vP0jKM zJN$0f$^-KR?yfoQ)OtvH-r6vnzsi-}syO1cxhYAL zj{M_|t$%eYZiR}h16xX(*?jUibkxMPdF~6dap;}BMd(~jw>azqJu2mRJXA;)_A4?L zP;@PA0+rAE(2P!X0A}iAPGUJlQ_O=kW! zp5V7_S#+NGS!!K7sT5g=NP96)cSDR=S+aDeZ9v2$H6qxUH_?QTx)7pA>x;{gNM!1d zvx~Cl+?FIC{UY|btGHXRvZn%5u^UMszj8vUQNer;!i+uSM3{atzv^1I9rup-kmm07 z8`cLwlAE_yGGUzyT06ethfh^x{96<0U>%yIYCSc!`Ix!x2NusGBw87FdHj`#BbUV4 z^LMM^&bTX8C$$R6y3OPTLG@eACys;3qQ6!YtqQ~`m3yWoP8O$8=?@nxjf2O)9`HYu zlaa>RKI3$bNv29!vThlRPq`i$RUp?az||)6ukE&_-cg}XeabhnGgKCc`9;1A1*xT@ z-eWF*>mY^hGoI4sM6^`$WX=p{xp*LJe2nW8KD8I$blfoUv?^cD5+@FRu=HHhHGmN$ zk{M)EA!6Uz6XYevvD7EndMD=rV|odc3D=l*;)A}5&p!^ck>8~MpkdZqpvoD|Cb_G0 zFH0G<*tW^vX;t+?WQOfg4URqV)D};kX~MvMen`IQ@Ay9cBjc%v{f~Gf>D8+e$#)4M z9R>^|SrjALLYcttzT=u&wk@<%K`eC#w7=wov8yB#xW=~WV&{K|<9{1(1*Rjwe{u*8 z32IRj`=nMnF{9iM0mSZIXf)+%_QX%2*GcJ>{YwXWzJk)?=56>w`>={#-#B!K)NNnpL+4w<9zK|u}P}}X-IY7-=C-~d%F8FZs1LA-L zSTi?KK9ILvEQu7)e>Jp6fVyw^AK!WGFjLAGjdt0b?#|wL1pvm@C^9yrzZvk-O7xpJ z$s}$FLDuW#;oY5 z$DKcBkj-Ictd^$hh@2zp8F{=2AwUm_;;P?#Q8mLy2uRyE5D>w%{xnEppqQnNsid8WsfVPUi>Z^Fp^Y=yKbDfIxuw0`f9`2b zQJRqZ#)!sy$Ql?#)N|8r5RlFhd;;St%+ik}J5>Ke7+YFubP{f8+sRNy=Lys=`NoZ` zKLnTSl(*Y_`ogzZf15B51c#$3-oT<`1G_U(nbEO#3Z&ZW41abBL5|0vDSxxNq@bSF}53mWcBJV z;~cFD9KizNlr7m2pf57dZ4XK*@%CZmD469Nb_SCzgU)xI;k;)h{`OjaXdOe2(A!?n zGP7(rH*IU=CNxB0WUUqR@*kO*^HWLt;i2)xX^FA!kY<|9IzntC+5JXyGLbxD+;Qjw z-*Tqo^W#{Wli1O$uc*2c)A}G+WvVv^DKxqHj_X4%o<8MlC?-~7G`&QNxk3E?udog% z-x+$VJWoLbL{Ka4Fs_;?2i7nD-n6E_|VK4U3e{N zwH_A(C4S3yJ`Q|^wN+VvSae=DqrrwwvU6)vHE(f8Ei+Aq`Wt{1F$=YhSXUH~h+SZ^ z5A?;)&33tXYr)yEBr1;lS^0 z-6LJ%a1x64lk2=Q10}GfnTC^{L>B#Xkfwms&Pwhv3lEnknB*$N8})ie4jVmR&ppX7 z_DPn+fM<9S6vhV>=zfL1&}m=Z%b?yzC;X#za{zCUsLXfLnE3aPBM0b-_T)4WS`DzC z|4b;03Ig&Ki2tv~h*^r}#=}>{O&&=a;!jZgw0c$^!2jc$^q)gg^0g)WYc1>(IJrNa zvk{DxoYWzEi?7#BoaWrZyCF6-YecDf78%k4(Ilxygb z+Nd`wTzK;KP@|zh5`jQ%-WT;)gDg>cGotJh_hHi{^^j^+7`C%9Rcdrfdz3! zwJ-n)j4AD1HTQy~RHs6L#IpA5;Ee}ase+|_ggEM7-Upq^9{S#Z*PM!zFv$r{Bmx=g zr}KB=Q;TXpO(yKEpG=uj&S|UIn;3{?VTl`S3|qOPo?=zNfuNv-tyKI|i3tULeX@23 zXlSfWFBuHyt-~j1z+c~G(H7AP){rrXupcmGFJn>OUh=uRE)8~SJ^FXkRuey$3q3IY z4i(;5ctr#jmAZkYEN7m?q_d36zKn{A*~97>>wTcyL*}5qF6|Yw~izycF4_r zs+UZ@U&W~joLqy~<4~~Pr2`Lf%Yh)I19SF#b!0gn##0kV)k`1VE?+HLT@j|~Y3?N;B^y4)rMFp*A~yEQ`9 z!03%Per*{Ua-P8Jy+|?TtYOJM<_YO%@wO=#4x}~#k*r&11ENj?EBZB(Yc+VcA|^(h z@hEMj*U{Wd2YJ;M6*aXvy~7|OCF@Fygz1<8X&M?~azX~$Wb`P_m|N4aAh7T*FQj$&nJMws_)S_Nx_vk{|o^C ztz>FR^BskNXc&|XZbjK+e1u5m7!Nf4c z=H|qb*F_4vAm<0EMs|ZB3)0qf#$`{o1Eo$HP`liO&p4qC^pe*Yk0TtR%OxmIyA70E zvg-%U+sI~zrb^8T%?9;KAuf|$CI{&&r@!X<@XPPm{TWGiFp|D?C1$3x;g<82 zV%sv_&1y@Fl&3rwy~%eBjK+MuwguZ(N_UHlciS~k5_){6Eax~IX~u=a+u3~^=qT{u z;zH8=*-gnrReRPiI5P7$I;&p=XhgM)3yc=8jUWqp*9uYXe^6oAB!q?m39Jxd!as#~ zyMl`4`hd_oA0^1ScxD%*`_P3&+++AVaMGXzC@v|CdCciJIB)Q{S`)DVC>KlZC zB**9=o_OhP9ZY)n{Q0yy+? z1I=ly^0+U$1pKhRjGx&fcnc2I%YiBl^?C_%8<8T1B$?{BLiCejQP1)~nbBy&NsZ2J zyv7nK+!wiZ^iHn`&xxf zHF4VFpa9AfAwhizN)`Tgtu{{Q3EakdN(%fuh&L?TC@N1;{-pi$=KlN^74kWl6>P+k z8wq8DeWEoA%ek8Ei8e~c(oNs~_x|;Aj6|1vL_Z+nTPu0J(&Uk{{BW#;DNwhtO%$4g z1xL$P;;v=f*bo_G;+OR<`dg%?o@Oe|@>h7NjMt(_;q>At*&9N+WGYRPiS3CAew6nj z8Uz$XZ^_0VxRABX$JFj(w$y8bG6w_~m+NvTzf)LtZ`ruYd?G=^8kLfgK#j?)Vje5V zAD4&cOso@1jZ2zj=3CW5~wn~hXio3BXFtsPx+ zL&nngRHvST3M}>`7YG=aj9kAa>Ln-nE_yVp&DDRZ|6EjRjz;$0j0BoawAN~NC#qWf zJq^L(ZI36ZP?M%tr*+vBJ!$TaN_7_J=7O>FQ*!^6sikXC5mn6Y7VI5?1(bdDXkV%N zu2u!ttVHY}WtFN`qmh;ps(ePWyTy95g5a$;-hM-C6xR{_Dtd0flYsCb35U>tw^ql6 zeT$a$P!UqDEsxl##svg}IZzB%tnc=dbzsQYSroWl^D7X(fSZRuuhzFMmlmH&51ITU zq80Y?Ej=YqOwc7tqBe;iJt8FJuP10=38WHX!=f5BX-&Talju+>_nO@W4t zPtB-O_rQn@ylYtlEBXb4+`G8k*7T4<10_jdJr@ppVh;W2ub;pi35k}GfeMvigmZcs zl`Wt8qCC_7KpHWtXT>2{WiPAPZwx!T)zqvg$7KajYL}hq+o-V1($6OBRkSo-iGGd1 zW&4NGuOtgsu-BY5JDu6`4}sVhckyy2xjNMwaqLUDX%#(<(M*R z%n!wJFcw#hffK-qUmIPs#EZd`$iYHkjOGi(*}Q=-Z`XJT$IfjA;4uXq$k}Pf4it5W z+42a-w~??OC1R0F!AO~kaUq#i+eiHwAzHsYwIE0soCVYpzh5TkD*qWzHK}(&)cNz# z(a-|--HBym##6lCSF2+05F)wkfJ#1rDIQJz6tyz^hnoW?xyNPtD-5;dt8d}mL-c%y~rJlo8!02!QD%A_w^G`XJ(`jz%h0vtA9=WXHPqa z!nw0~yGY6(3_`%X|(4E}s%?ou4-hskZZkVY3T7rywui zl9B25o`VJM2?a^8V^s>j_7{WT*9qki;RMKk-k!pRVYD=L5|va^S%ZylxI7(h+-43>9dK zrGKArjcTy6EL!6nYJ(e8cZ)XErA~Ry_b0-kf3DfI#oh^(f*!iid|KBCEZ{Q^(ttp`1+QUEwP{sD(1 zrL?6kkZx{TCwD=VlP_b~g42%d8QSZfIu?&dJ5>G-L#{{mPv5-xs@uvpS&Nr>Nk+fXwA29+>JUM_7kYQ>W*M|kdVKPo*Os`g5R&7+z$r0QE+yoQkaaI0}Fm^ zy4C$i+;q=pQ;{40An6FF3=)4lJ*~?>4?b0Bj}9q&&k6Dgdssdzzdfs%jzjfeN7CyA zLbc;Um&xCpEj*#5@I}uwAisReC1?1Id4H_R=8e?8hO|>9Z=bvoK$#+*NOIq(J7!khSB9zKPkLhtDP-j-Q8J@y8iK`uGFD%Ccr}BUZNKVEZ8!z zE-kJZ)3b?xJ$MYx#}7y|0jpOQvI@ah^bOvdcs$%_7BOGMY{+39(I(kGh@oubxY}E+6*kLuje@!@&l$8zw?3Vg5IkwXr<9y3G zS|{q~PuC-j#V0`##+YQgX)~n-M4gOa;;y2J>J*_zy&X@tq}>lsqGj%CtZz7#EXM#}l{lHx1Deo)RU6Fb}+#9Rk ze|?v>gENSo?M2qUc-szB)+`@XgNmiJlV=cx2cZ8F5nL zPun;9JPkzv({{UNQDh~U-wAay_Jk~*NQw2HH^BJ>UFBZ}{gih*Ex!xH6GWA3Ui{9< zJP51u@#Ca+W`SORAZIb!qAo6e_uYA|Q~MZFbWMLrG$Ck_KVn}bH;RXE+=L+@KWmW3 znOYq_I|lM_=NH*A^>~F2(4`0KPlNMsBPTaG8wUMs`$T1YN?Bn_OfYdcJXbFAb{Qr6 zfk%psuZ;Rly;4uZp`*N7h818YRC|1+N4H|Bp)?metz0bZMbTzuLtdtAo!@H5k-c)Q zFQN1}@*{^dDXUPts=!yfxwO>R3b=}-rYIgwSld?z9~mlz6RTVVfPaI(5h%R}q?>1c zWrp53=n6CqT)(1_5N?`ren>TAMq_AZRSm?r${K%qpr55l(fNI8jsb(C-=7 zD3hL7_gK3iU^j^k&_BQ)i`p>U z+t0a;VySG6AX>KO0jW#qyX7cP%o|cMHFV@qga9csl47&T0$QUhZcSh?Sj)Wa<<{ti)O zE8hd@Vl7z-%LSDZB`B36OK6~6^~bsPGh6PZ&$<9OXthQ0vh5kBA}lsVs3==7E4Y-b zc--jBl$J>!EB2eAqtQ8W-=|i$(>{owd+>*x7%BZA1Bb z#7bI1e{4i(uV~u+j^#Rdz04^UQFV#>qT8GcVXh8Z7l~H~lSptlJTPk9EP1Yc`|6x3 zWymWpae9>4^z#ZFvrPxJS({Hi^#pvWEN@+f|9~ATESa8W{06zF?x140Qd1Y=?!Tvc zNVBo$zbpxWql5zidBsTUefgi0N@)meJP5!Xb#D)}CG<~OH@41I86ge95HcnICn^dK1)T)-Xv>77HZC?by$pa!GtZA3kRkdoG z5eb+0d{p0iJ_-oaKQ41tHzfjZS@nA|xF2)9FFAXir>{1AZm4TJbBV0v{hGg8hn5}iJ#w~&K zu3{wSX6WGBkx>)paLqERJ6A5Pv{ag#GN&#iro+E!6%NGH5?J@&G|0(*{KAHwz_4~k z_t-aEyHs)z(i4nwjmW6)P^x%Jtc?b)sH_=gL~tPTO>=UXYe$TPh_zHLN!%SYn5vo2 z*E?eq@S2tnH5Du)H$AQRwQP9Rl%Pux#iNz$<4ZB=xF4|DVY{Y`O^D|4!ZU1cv)OV z=0fu+J8L1j6bbT1WZth_4AL3ZtI(Q98fx-c8Fo=+Ltv(oMeB=M_Z=V^Q;IF5-_N;8)A zmAGQNs8z`}%Bbd*({K*}hL&iI&?PBE(Bbm0$c0UY24rICONe9z$D-r(gr|rk7mU}| zgLA{5ozw8E(fU)3MzKVTF!)CqK3$takcM5yhq?`E$e!X^|H(GAd9k70q{fI5h-;#L zS628r$!3(M(5rnyF!Bc~FD_G01{%VeRfmMCl*iVj;mAj_c_7XX2%jV2$L9kvIBrrs zowl5^5D5_yn!g;T#txg@fa7xw%aRq=Y(}tb*(Ou0Lyb#(;7G~h@l{WCe=)+&N!sV-_wt&Sw^;AYcQ!;6)Thqo=l~pwHiW! zP3k)QGOhBR7TYrg=zrw}&%VQ@MhT&`dD+15>P2E?s$FBFmRh_${yjlfVdq*G{&2R~ z|KPNku3DSbN5A}5bJdeB>#xuRBFjeLnFvp1$S*Va)}pYvnbK9kF!RIRdgvXwjl)3< zE+rjn*7NW1+K%kqr!6PNtv&6UD%ah?X8lf%TXC%Sdz5ftK;aDltt9@EZG32FiEnaOJ|N2Sv_a6X7QWw z{0Dt%D4^RbP7n~)qAOTeMP7YsuVsxq z*UU9SkqIwB!6}DkEgKmiTK?$;XM;~#2BRzg2U#r~5S!q`T+7__{fQP#ehBq!IH0Z` z?T<@P4u@dE?-LU8k7;@@-yDf2cGfpxB!Rvfx7_2=-ZnkVNG4%!Te)BBrr(Vj;-g||~G ziC4&%K%W#^@qx0`eGZy_Dn3uw)bb5_LKUBzaO5N1PjT)AP)jlmEv(O9{nesU)=nMVgVoX*YqDgBb2j%LFAz-a`zLsAmRWR6b4LF78e%Sk~KuX8QXA3K$L9>ZZg{oynp>XU7tJB)21)OZd2qPJH8AN2bJx#LGa*Lx`m; z0ye!gUHivB2;LA60h5324uK|o8`d=ENEp=m?ov1rvm;cAVxS)KYA6}?n&1ImfFFVc zdGMkFX_Luv;UV5*poOk9&jMko^T&!;+mHFTPE7{s5`AF@MunU+xUQ>E#Kl*NVMZh> zkz1+Cq6KR&1st8`BD2QeG|PLh z1BWKs&#LA-5H#^}g`sHBkD<@eeBc{(ix3<;)2`$UlYgaeK6yo%d;P$GhP5ta4pe8W zXwK3^j`fta`XRQoCFz4tYWbBVu70A6&Zp&S%VXO6&b- z7RC~E=TFs*Q1@sBf97aBHkRgB>x&nOkxMZ3#Xjp&MrRa;muU! zgjDc))Fbw;l+zR~t<`{SIGRZpl$^V2h1QF+r0U=9YG|E^!*~7!e#!y&cFl!0Lc}xr z+<&zOo%mpI*XLEmH4uJ=Rsqr};!wvZ!?%^wgUaTj&Fisd8iw*nv3cUE9FSU_F1~m5 zabHJedrB7esE8fsM|pucEk0uX%#n#kbWjnBVZ202W=Kk=HY!wlC0X_F;xW#(5hzK{ zkihjj6SF}Xe=7o%FVX^Wr%>P2(;dQ)-Eb7HNx`KdJvcDYZ#-N`+;5~VhMG-yT$8H5 z>R%x9%&jRj3qIT$2wzJgx7M$PWqC6SHIVW1zQKt<@Zy~ak1!Wu&WKx4GTvr63i&CF z30_NK6V6dHHIqkq&(VtOtOk`~PB^N>bPjdd)7DNQE_v@72bKe6l7!Imru5-*-SSB6O16dg3zd)w(w-Xq$^Iuk zRm$`7Xacq6T2(SoF(~{U+e1@qW3U_BYH`EOf&Ld?bF}3%@kf0Xb=0&91>>Q{?Fedaf-B9#^QYDWGRspgmzFp^a}%H;qIbh;Ly9B&kC>- zq(gVzB)JIKgd}Nr6b*UTp|rJ%c|^qqvk`26%w$PYrZHj8O1syd+Oi50zSe1j~I~F z+B`^Mz<<>)eJq9C`UVQsx?BNS0f6{g-A?Cg4gLw4i14rNt=)>-2Wbe3^wl1aP~m6T z`O&@$4+8R%7Tpi|1Gq`u;{3pJEN%RFS+D>I4&z$ zuf7x_|69cZ=jytC4tAlDr2V@4r17v01It-n$uqq7Z<(7|6?D-&KZ2w7NDpJ%6)6*{yy z$cP5L5MuHx=S(uCo=+!|=83lr(_IqPm|PboV(N`dxiVg-LR5Lk;<_~=Hs|(~L&PrT zoEiN?$F}+QQKNiw3k#g}Cy^?~NMq{nD*}9cPumv1ij2-kviE+iM~k0^)Q-E}Yjk*E z(8eL$0$cHtE9^|rIE)MFU_XZ@nh|hJ-3{}PDC@Sn@PO}lBgJYuVP9Uw+>uADYfwp5 zV!2;3w$+WUU$x-+t1|jLc;%3m4R_PHx~zxy{mnsPhnT>x;#KD3Y062jY2)lds)dxs zSZi`ZEt=1$W^-3X{XuA;4qn66x|{aAAN0$9|Bm)KBz{xx#?2+`kK|>+MZNswn-G8% z3}ajN2jWBAKen{nv3YMZ+0JPVe1C2EZDSSXXTh}p;xy#f``Ln(Lj5Q>2(~PLwP{$) zV!#wJ(1%@W%V1E)TK%KYA_zocAtuQk{DO%+977CZ6wVF(n@p01YpD;iSQRL)IdNR< zYDjqMfnQw4!OwGvYnp{t42{rF&^f(FmrZt~fFk!h1?WV0f&GK; z`xS;8ta6d*MYY^KFy=S@rn@$^Eb*z;LZ^O&pV&kz{f0iE1RHX$xF2=CZ{i(Hq{=Ve zo&DS>3)!rcM$=ZT`@>VMb z_$$$PXNb}tuu|GyZ9z8(IFAO2&>!A_ZD7Pxy*PJz_C&7kE_wREJ7^*Y+)Hvj#O}P9 zkq-LI^RLjmEAjKp#5K^@l%P)kuElWyC>(-TUR_3DE_sv)1*~EmovYhf%;X|DiP1{A5b{FBbxN0xKc-6G)gL#; z0Q@|e)%kveQ|5KSd<<2fyJDOWhjloAEw$IDBW#vP&HV&QZV+>=M??_ljo9ZbV)Vlh z-9BuX1!B1!^5dNMO~#7o=72}NAMnMJ@*U59fSeB5e{gZTpLt>2+=NawGSJ2JG{%l8 zT$T7mx+QoBf7`z**nLb(>S3`#x4l6!erOFtVX5p%Yfqz{EgO@{Sb`4~(vZUg53zc3 zHzg}uRHjH|jAjTEkr^5GnJ*gr=)zp+hJl2(@8(;O6O+_Jb`zZ2E-_ESs(`gw3&26r zIUC8GQQ!abS8LmTCB?8GU^JdYZrG7|Vn&#YWwsqUPF6aSgc8Z-KH-uQd0z{SvZIvq zD&b*wp)T!q1X2PRW6oHNk!2H0_8y&YI2BX^vY;WhChV3h@4 zYDB;!cx3`XlT)?Zk2iC6lJ>h%v0^8)0)v2%6%K(edO>@*wElBFqGU&J)gg(?d0*-PbwW(q}L z0(x9XGXo=4-f^`F2b&9$1K*WyT)B8Nj06~#WTsOhg(gO)B;oLVrZPC-leWn{7AvhE zr>A8@N)rLdf%@m_*DY%9ycX+l1KUDL;*absNyxL9I~_0{V1?|xa>O1oUiBU}p0ayw zR%`^}(6mRJw_j*kF5b%D;~U;qi{c)(@o}`ZibD{pgdzM`FQ;e!$!egS^#&?$~S5wu0|8NouNjZSQA4pnGRwm?14`Si8R6OQsi`t;;Q0tJO%yFZwx4@%5 zWaLk+WwIf-+OA>oT>dJ=*uN;IyHZ!uP)04CFcWg8OJOEjAZcAwd9P-I9DXA5fc$C# zq(*aG7Zm*czQSa|+s|Z^oJ}woq#VYu0lN{|)XfQp$xOkV2g8SePq#pl(5ybB920+4 z;=3MPGpii(gyim#yUaEF>}bdB2?h%2M~O}bv&CdcB|%YpcX0Hh(+8*D9Ckwr53nRV=bvDniV~&;`ie`N3ZID@50`I7ay-#Br(s>rc80vJMfOI| zhHZ_CwPTq};+ z-r9l_s3R0!vS!^0J_xy&cbw;;VC^3rgAW54i{v=s4UI0 zsp3B;6pHz$7^~$I{XX9vJyeFu%nsr~Ndj5Km8FKXL-&7@w`RoJ6ALC+3mBEwl{YJn zQ?q{uNp%_*Hj!X&aBt*ki}};j;QR_Y)AK8%S!K%xaz@jR4Rb`R1BMurC-PTc9f~Q> z-ye=mMix8K=7F0=vc8!nGE>5uzzy`vKO?nK?m-}0C>_K@J1LFo*uCB*fR1wab zgiuQ$Jny(~@>!1|b)q?V%z(>^Y=-dVOk(ncMw2Hn4bh+dfs+_DZN_iB0|MugKXfN0 zQNIm@{L<=j!>n7uiYYBQqfJQ>?f$gG13mX0%?YCuH}XDQ9#h6Id=?EVOKPV28=Ujk z7e~{xu~V7vLVpH;s8hVxiD9R{mreb8`0K}68IK8TRjZ22&} z!rT)I*yL+%ay4D?Qx%?0S7()~hx_&yi5uI-gs#U#T;>7h7{1+^%(Xx#L1v%-X>rk< zVO}ElML}dL8RKtkWblG@(P~hZ(LSHchP%t^sFU+04<0{Lfz>KpA7B6r<^3G!Pfn2d zs(J+xTs4A+pZ+G)sFp*%M6d|r`#3Kl!B336;N|O2Ovm~7t0ZHQO-9oTXb29V)>9Db zIS}&e5Y3J8Gl}V1wHd!Ez;}4((9AP5Y#A526$UdsGNX!{!}B1?j|hEYUpSqkR~G6oY@9yv9l0rCT_by~B&b3+(FO8zP19erW8z4h16M zPe4kd~Q!tLtxqY&kj84jKwQ=74h1@o1Vx>0!YYilJ`c-H=<=Pcw z9yOiP*TB4mB9U1Nw_YZG#F^&Rdfu1p5(I0U7XH8u0?xaQ~eZ$R+M3}oUlBS0z}tN_foJzR8Ytx!?b zZLSrt#`)}pRc2vEmT~_9yiLI@S-ZBoy_$9Pe8%T-Th}h?`!AGf-+O2bScM_h`ohdX z#O4|-;!`W-DrOU-UZtW*V4mLve17|{CqEF7VJ~AAkpFf-ezmP!?JSX8e*ABx$uFg} z_8CZ002Bzwe-QgKkW^^@llX7eAV_J%!T?l+|I1Wh7Lx3LnOe_6lK&&nytP@<(qBN`&md5P^h#kP9BUj`Z-9QuQnqW4@PVL9Aq8n*W;K^UO*&y}SZ0O_BpmpI_& zAKFL)ivP)x6#!-b=ypXwKl~SZ>jP?#zmVDyaQY=LL-hds6Qg+oX#aT>@B=XZJ0mB5 zz`q-k#u^AX{UQm20P+9W^&x=if9k+6fZ|tgX>-Ydi7zrI1t9TH`zRIg{zV?70sh^% zw1f=6?icBt2_XMRW@G{8zQ9lFYb4zd0SIaJQaq@wh1r0FFKH`FE};D%UgiPX0bf{N z28jQMoOOWQf38=40{s46sCqM?SopT>Gy8qALZ})KS$$Rd(=f-<8bDpya#fvU* zT0$I1%oWZXK&xHlSg|?n8s}e>u78u0hq&@KkO6Y97om|hj0-V{ywFk<*>gAdUki_?K{S2U4U)MR%db6*qCvtsHcOAmR zN!@dt$U;VrcGV)1gvj#It|lxbh*T{k3Q}jZ#ND;LkPuTB=$-Qz*JlE>HqA92y^5XX zngjaxEpk1>2dk~S0?+*{S5G<6ocFk1#U9_@>pBrV?7rW%HSOU+*Zl|w9C5ub0iinG zL8#{iS31`+0U?j`TsHwBAOGIml*eawUUH=aG3>JIViXl#b;W@r`H3Jp>$>X}L}GdX z((;yTJWjAx1+ELQ@g6i1>o6gofDRt%TI9M6TElw+tGL(|OA~Xo#FfsjK5t#Sqew_E zAl`lNx)Bg&>L=F*sP8aUbmLTqsp!_(bl0Xbqdhy^WTIQ@Asbd&x9zSAb+!8dw(2 zq?U;&9gy?0x#4KObR~Bda?Dzd0cPUYaEnkdZXI_PCe!)-+=r-e*Fm-`4UcepBYoR3 z?w<$;9_MyOHRmpK=cAf#SGgwu^}fcvg<|$MxHFM$_AM^X5~k=5_W;68`P_OaR#?IY zuy`n>%dEHD9nfZd$9;mbo*%dbc@KT!_DAURm3tiF^Y7d_C_6&J!{`#BoChY!WjXI4 zw9ZlR0&$)m)bNTy3z;wyG-oS#N^)SN)txES@#>+Ha09O)=EDeo-a|kW0(cM5W^51- z)0ep%!s|lW!g&V~7DVuVMVpNq@Mbz-{@{GJUaV`FNF-@|H_|;b}2Z zR!!#>0?5q(746r7ElvkP5Xqmx>j$mk-@r=unLJwc!)NoBpoTMZd3_!DWs{}m;p4qX z*jKIxiQU9=X1dMiVMQPVCxW105pSpz8F4yZ(uKAlxu1CMWWrUh3(LKIJ=<&5Cf-R< zd~Xx)4GQm?1j5g^^7=!jk?)A~&w;T7jdIzG8%;8%J{J2x&>+RI8kHA0{*q{$1Vx&@%ikkEU7I zd>$?Ur@?Ph1N<_}`6;-k7=DtE8)1^T6o{iw^C#h6X%`}H7x=W^Y|i65 zgE+JEG9N1flW>(k14sDjZGL;iWA5=MApO__J_G$F_m_k4>~}P|n8zRZ=a6alXZ{l0 zx-@eY=x|1feg~#;S^*udx*h^{Ju?U}BN?MvunQpRz7m+8c?rZAh>tY{xNMM#wFJj7 z9yp|XUCz~5OSTlkaX%@+wy;HoZLPqmTR8wG~}ZMsSD8jVD65!^>@12;l` zp4lekGjZDl15r8$TA06g2%4ixpp`j}_Ya(S5kTuwz+?u7L=gf9YABwofTkeE#3lrk!FF2 z$+{r;70rIl6Ffk8@3P=`oDpN62;`WM^PUTy0vP#1fE%Z!Y=zy^=br#M7oXm0+<^tN1E6hi7s6a?l-z*lwG-E^(;cnEHD-|Zfp@r<& z2_Du~3)^EBX8H&x;SNF>C8RruPZl9f_1Q7Po~WxNPDnTZyXpyBg2{cq2x*ec?LaNI z?kIeWs%v%@(slEEg0L@2MRpU?%JCslxU6Ey$ta$cLh_Sf$$6C^^yOh(ClS;8nx$&$^&`+#2FBBTSb zXPc0gq((c13CNbcTgV_x*eh&}hPIy+Vxc4V55YjUJT1h1dDvmlD>}m}C^{>=f&n^l zUP$+c&GUpb4>tZI+>L^lj)35)dO|TX;fl~5R1(8c5a0fUxOV+T(L+mSpMN%k&qwq`~hY<)w^d!1ZD57c6R3gGXGP6@AI*c$yDVl(R zc;zFac`?>sgvFyyfCw{?sSaP7i?Ca$=mtPC?=SGSRW(s7Y+O}cB$uNLxE~Ss{Buek z$+{-ek?z$*PR!j#qQ^Kf=9~aukK7VTX)`HmCOQJG`PkZf8MNk{vbvD8lWehr6}`C# z$CkO-Qbec1q1K`_v=!J+)C|*ncps4*%lga#q5=T-PJusjl0{17$xapBMwl^BM3eZ% zV38ln$ft@VPUt7@CgX*B+HWMrOxR7rcXhasRmmb}5y}+gYN1aNJ|M zE)iixVB(jF{1J{?F7gHaB<(C1-T%7?Q%5BB;xxS}qOyM_?5(b!Y zv=>LC!a9!P1axVooA?e!Ym7>aiyXtzis{)1J~ZdXjPVd-Ul?B_e47Qp0pVg=06#{E zG3^dl7h^dh=~o~EwLidq?3kO_nOv}lMMT^Rayma%1Obf{)73hct`;e*5`EJ;g8i0RGE)KTK!fNSwB@W?n; z9DpUM*;Mg;04t%51V+J8J#>ax!gQS`-hp8f%@E@fL1J!$$hKMH7(keUIbym-ubU^X zi#|VFBc^3)-W|{}ZJl@v2;7IinWpQ-?Cx-*m{x!3W-&diUAzm#!&}9zQJZLom=^Pk zyTr3lTf(1Wyb&OUCuq=jofOl}U;f|Xzma{@S!#@lxF9Y-+;~Zxi+TLup;&`6`r8vR zZvHMl0AEUH`;JFPF@NslY67m%PKPz3B$YcWj^LDS;e?qqeVPIZl;}&p=zpJZYJ>I7GX< zM7xl%rE(`SZkNP`2@jGKbIK1|G0oBnNtaBCH%WVLHIew`GADA^AQ3RXRhO)ylLX7( zDcXSInrD506DVyOLkW{5fE|aY<&`XMONm- z#r_gEQhZhlY^jnaX!yk-Nj)?iGFtKq#XZJLiV=z?N}eJ8!&J$yNN<}dDTQ{jvKXRc zSy>*eTg6rbfewk79SbF$P>{QXddRF@A*qFex3k%(-`XhIkMxCGCCgB8=q^b^qz^kF zxsL6vjz~5k{FKAe7oLEZz2L_9_wy2DUwKJ#5XtbFhTo}QH{e@k(VzFw_ihg)xLT4@ zMEoC1vLJM%>jyycUrL^%*qI_$eDWK~3J@omAAwlwqof+>*gHT<^P<0#^Z@dDxk{;r z#{|;m=%GL<#f6J`t&+Y4NV5`Ob@bz^N!kmUECEt`#G^x4B_G12)Xznc(k4j1 z7sDF4TUWXdl`U$>8VPGAwIV&GElW@Cz{>mlDqTsJD9k0l=#=bFBx9T0$P{;znvh@C zS9%Sd>1H48wl7&a8+Fb}lg>vUOhcrlnDC}C(vujLR}Rr`%=dB96_`=WCrfEYEuAi< ziKI%{t7ZOTMadC_j9VKd0D zowucG%vCNkEnnIU1@nN$ym%zlp$lPrKs*Yiba*)>Y}9h!N^y9ZuAiihfN8^edl{k| zoMm)m6S*=vvR{QV_-=1kCdy5Q0b=acGJJYLL}Ji+*dRNCZjbVmtwyNxmC-V?Jy7-n zqq;Og_66G))s$hkNOx(u$2m1+)MHn&>57EUtg*=Ip!G+wvbt!^x3TOs3Tm3mN>H#^ z27=e#N>wCN4tC??W$TgsLpxb}^u1*l>U*n18679rBpF7JS=~=o1GQZnDEo-o@`lPP z=le3}qrcJl?nPR;MZ;F&icH4jkCfF$NzFJJo$6hu$S~hYtUK7}{tcndUjrp<_7s_h zt)ktJ$b`&+8L}>@-+c}nv)%J$Ex_)+By_n<$=1&; zCu9O9dxPvFhVAzqvQwChi5lqZ=-smA81SQr1Rsz+aV#&cFZn55NN9Tr{BATl^+gly z&Q?uusvr7uCWnnf&l9pb7>9M|WtfFzmku1DYeL?6nU0l#<)A&vcwLcYVg&ZyfMp9H zb6-Y_-TKFLB$y@7Wwh93ypnZyE%)R~Va5?G^JSjwFhrY3)-$oE%!hrE^+m_xjL^p&-(|m{2h|+qjnRYAd^z2wrJ2C%!;c|i5>DSSZ$d#omHZM2l9guACNarBVZV6Za+*=!{Ny{3X^| z`B!9G8YO>&en`B*lyX(MsaD?69;CUyT*y3&k;kKS?Rr$2ku{golIt1IYIkcV--7hx z9p$G`d#W#J&q|cj$-O*Dj_U=9@hcBMbVNsr_euFAPcG*&Py5MRqI6`c{FZBZ)ST3x z>=`mIQtm@`_(!W3Kala5-oxdGQB%4V_q}r^YWco zU(Z}-4b2S$C3mmM*Py!@w^$)fzMM|p_-ArD1&!6v{Y-FV!ZUf<9tQHJ3G<{tZbU&& zF)Nt$md%^zUun4a-c;aH#9VYz(2{$YtLTo-ddL)XRcKuUjPuFA^IL*z0dQ+*V^6_mYIDbaokEh+U;$SSm0{S{hS(uk^$ z?x}WxpTa^1+ogk?ttA-o6WOf-3bz-@{ zA{?I}B2x7UqK>Q<7!9Npcp-~cT3~EF0u`X&`HF3*=-nd4E}S>|2Ecq|nW8Vw1E19j zdfMByR?!tbmuyl@K#%8bQ7k~Z_ioxfromptT=e+L0YwKfz~Ix0&d5LQg5o0bb-bb| zLfH7W;$Lj{c%Xa_nreO5wGld6wkO>HBG+!lI+%#GYmu-p#D6;OgVii(j zngQ#t9~8$?ZXO`y)Ig<_oTSZP6!=V=Dfz0ng>IfNRm?)Xv%RuA;N*~_@?StRos>Cf zy|t^79$UZil#9^Be2J2-vF8*@_Mt{gFg3|1Ng!DrjU|!osvV&b%+))Q#vT|c9#k3 ztV}@PXLnWZL4~V3g2Il8%6HJXxD&|raaF2FdZLoYOzF+~`k^n|5w`)#3>2~Nj4?`4 z;%g4fu7S!m(72)95G4lvmyt?LQpRzN(jKAnIOS245hZ}k%?ZleI8w|cHoje^(%=(A z7a*!dI3+fB4u+_w4_AYPXo|F;6@k2IubX$4O+w7xpT4fC9_guCJO26HsRZ63n-NVmTb zDr>Xwrp0Xm#ws=1O^WRkV%@GH>`>QD29Sgd02e0IcWZ);XBxUKwgOC?=r$FNJ)h!s z4hLf2Ot(RRlS|3K96Q%-D+H1lQo!F15;qB1nHud%G)vqz0_&ek+>*f_$ptFYaGBc# zMD$>txwG7DQwDHM^>FWl*W^V3?st{|JpZS=GkTJm229USxPL`=pPX{Xh%+nCxckA7 zlk{}daM67eBF7N<{fhfS^s(9v_b2G1`mXzBF2KU4?ldq{U$|F8`oTi?XiS=quidjy z^U}BOcc7e+^ueHH_80dzAiOt2H4)n+L!eE|QEkEgbwlKiScUIPksXM9k*Ed(W${q> zd$L+J538cPQRRg7U+1d|MK_uUs_0(rW2mYMX%HD4ufbpH&A&n;##VM7|ht= zpeEmAC> z{2tyy^&BM=Td7tfbZpJSb!}AH*n@$i!Cd!ts=cVzt%E8KgVUk23U30LeF?0&f4iy* z5kH=&qW5{~UMjj22KQD8vE9;F)du05{wliqEl+0Q##9y77pBWVR?q0cs`ZGQhpBd= zGna>}zF{;jja7xBUHc3b-Cda{sXim0$5fRZvpRgfsu1}MOH?N@nom}+{;pW1nu6_~ z>r`|Pcw?iAUWT9B!uoq~yGn|B&+SsBp`3M}Y7oL52URA7myW7lqMakTsx1in{l)qx zIi*?)I5{>If^+7ysxcTLl4*eWUsR1k&$IJXc{p0R&`4(ftAZEuU`15X;#GJ}bros( z(?Q_oEtMC#P!m|zP^*--6qP~*)U6XmR?SN$_MYWe`{Ay>T|`Ns>@kMKpQUK;fm)YILd zrjJP?O=@3E(5`;!!9XYB^TF9?)zuOZ+VH8S8f8C6savD&=Fw^+!jxF`D})Jk)h-Bo z)>lgq4r!AnD#{pzjMSg(m;F*L&pS=OA`TKx{S zbZDm@h+0l}RMW_u=&b&L?MJ$(gAu;$rlxaYP!Dw|;%j=TuV4>GB&#oC5;Pv9eu>Fa zeVp1G+dEHI2jIqK@g6brnWr8|eYvWpr7I>2LIOlHaShQ3Ny#ns2H;wHTYVhuCEQbY zMJKwg2CjA=)Ox1qfqEhKancL5BNnA;uhpdhCaeJ)E8nPBp*Jxf)H_i6)hBg4;<4Y< zO4KqiJKAmU0L@<*>o8|cH-wp7jVHoALQMwx6)D$fp=y%V>p@{(orYG0O$N;e(42GwST8}- z0mGcQ8KkepYQ+p3OB^s#Inct~=%K-bIP3%pqKHA7 zwj#i%PS9jwyWeC@9kkYess`V7Vt$>jp>LqInW>@kb?$c1U3;!3t~L-y{mm+AdPdU~ zg*RQ$48|qMp9r?Gs z(VPLe_z$o(>w|`F<~3h5SlY;TXe2c!LwCD>*L*;$9UZmZ(bWS^+94<%;;O|5)l6N! z)(Tol=3WfY0F9E&5^Le52B8*{ZbPD2izC%Tp{3bvaMM-;ZRGww95IbH45z|UZ!LX7 zKbI*tn&qpd3+eCv+8bzmc!)L($8=+LZB2yvQQCZL&yCgg!(}tDiIy%rKFzc|NHiwA zt@avrmD^t1ADtf3K}$o^r;~O(y7RQFmR^H$`)KjBK;|9>cSa>^IXJU^8>YRBLWv`^ zIHqLI5g;}nrNtcw(`}6QBErz|+Ors_xkth0j(n|n0~|h)V4AGOi#HN?45XS)*WycD zOz&A*4)!#aXxE}@*-|Zi#udL@`z!Wi>nbhXf^J`}osW2(_1b$>*(U95bZN&Q+OsG( zdarga;`|)#5H-LFH?-@qZ2CUY;=Y~9c&4Q*MaoOgZ;k8=#|=eqFHcJW9lz1*Yj?x|N8mLL??acMv02 ziio(D?mmi_MCtZpW9~U<%!$?EqYuWfo~}8DV`zQdE@bj;q}z({-zK^+YVI($LqU(Vcw=UJ~L`liNkPdI3>Ey(C1@`3$|H9sK?eDrR zz|v}^ZUq`ISgjk6Tl?g_I{Id_4?_b*?5~3U^aHwIFd@4la{H(bPvPV!AmkUhPC_Oe z(5acPIXWyBM1KuNcU8sc&=#5!U^p+m8GtjvgKe#R88t*w5WUj|JF?hEk83 zI4q{y;KM?t$9fPV;{hRnc#%`LVIiI5;ZX#-Nj~z-FnZKR|LWWUVDT7& zP}kbyhza1$K^}bkc*2v}9vL10`)=_FL&c%@qTM#^*zJK;Z|5CnR0L)wM^)E4= zqKANNchp-jATONtiP+KRT>U`Qa8#m?#=w1b(_^6|#gBkFOQXk^(MhYvfNa+3>mp~N zr=D&{Vtw?5V1o$`)W1ee#!!8G?9}k)`dirEwzd8cS`xR@(~J9k9rYUY@lqH4Q&d{| z43u8%roV(rSNGBH1xSuPhsKmNeK`7HdI9mSS*#JW*Hhz$>LtvLL3;X1RlA}3Ua0-R zaQzyzmM}(-=M<*LIQ>2JrtWWgHCjuUs-K51PrV{~F3{m&AQ%uW(?7!SbO(fRSLrXp z-#1q2KLFpQEIr-3{Yt&e79bkTbb@x_8R? zOMeZSmi(h1jZBJv_3r@XUD4C6jQXbj4xk@y>W84K$#?X0yQ|9A(^JOO2YP%w&%A!5 zPe$17seUlR%g^=nGG$zWo>m&aB0W|)GVmREf3>ehyzjL>0~vpNtH%mPLf?aL%|GZD zAuQ^Ct*WNG_VSgus6&!{qK?ZN=H90RfJOXsL#4s9z8KN*; zhv(0P<72?9xraUmTJ=l)4FxDw5NN22Y@wkBTE)MF8Ac#Jq`Kh@hQat1T=*r@upVX_ z`5I$5541C}h7fd5{0(U0x`utoc(R`1C4ght_IG{50wXXcBpH6i%t-$ZjTe#)botH! zgiL8|R50pvLlW8=KiEKT8D&X-vMJ1GYmq^leEc(Xyo5E%|I`i z+Rrq=tsWCO%Ww{wNmqv$m|d9$9;{|$Cn8bH4Q;WLb5ow zy{%hocnmBvvkfxTy>f#g7K_T=9R^>>K{DPM^yu~)rlOwgeTLmw9y^?&vdq`B2D)Ts zoi}X37_{WXK!x35(2;Q$4SB%WD$h_2$LP0z3_MgY`?8@nGUmF1);s?iW}>NncMSWH zXufB-fUqRr5RYEQ@qnr0L&JXbOZwD6kFBB44Rqt&;Dx~xm^bu$X+VWi0a%_;V1PGb zn6yGe9c@8(IPvuK-8BHt5i?#cvJukoV?0!*>8hBH%50Zy19- z$sY{@%KOQHHkhTK4U-V+zZy=U?4$1ntZK|gN8=HkuD!WN>@2w`1z&dXjqq9yGe=-_ zM)wzrjd&6vr81yp3ycAzg*?W@6v~Xfkle=2xD;UloRSd2kBHJYqweXAN73X;qwxqp z60ZbtXAh$fv&3w~n!v2_GSc(zpT0&tNRl)+=)uPTV|DZ_IMnzC3xO%hn1PXATi2+= z_MC>sKan|B1S|<=&!eaD z8T#gu#Cjs>W5g=9SO+?T`WYXhfkZ@nQjFIyKHmo!N1)*F;l>Ozz)wq!Bd~qYD&u}M z7`Vpx1P#g78=oTnd86@-qd#26ClCH3s_JFD6Cgd)QmTBW5IKn3Hlq*T-T%n0A{j@G z+A0qvDEG*1#+vvvfFjH`V-&didYkbZ7%tv!9E}bn?=sp$NhEnDhwWOtdDB0TrdXVGc@%J@5(J|phMGx^~77y`f*W-Ihe z5*PzpRH11eZiVG)(;f7yL}Q}oBvQBW>V&u8lg^8C?IF&nxwcLytBl#9ea=mjl_36^gs|6BV*33Fs;E3 zrmZn?QD$5ikms&7!Eut*%Qh7NDQumIgOas2nixQfH=5|~aP?LbuAxh}nefRP$*Ts& zVs@G4q33yfOdAo7+;94Z_55B05NGF@0@23<$4$6}O#0Kb8F?h9On-w1B&-JT6rD2- zKsP1;QWodP7$=fbBZkM23#RILJ~(&NG!erQ|IkzfXxt+cp0SzsPfaHf>Ry^!!-$g9 z64P-2;jc}Ppw;b-X(?KnR|^!M{Ahwg$Nc_@N^I!)&4gj@Rce}s3JM*}^p5+nlNs}h zWJZBwt{k&F5{*1Ft#QG8a~I6fnKH8;E5>G>8EY|VWd-KBdNZAv!_4LYbY-=l`3Qh+ z{^k;3Z5?2yN8HZA<`N8j^9b`wgfDBESE7j-Rx{1tbus2iXdky7>X7+;`K{Dz9@klE(ee&|5tyzN3bgK)E`d`f9SViJHnd1=7>q3dlnyzMF zFh}(Dfhj-HETga3;OvfCE+2l7h{u{kNPa7`6A9~Wh6i^Hw~x6$no4f~0y74f>0^l5 zX=W2TybTb!bWERUZipV`O*QvK znd&pljc|;@o5C2mdw6=1{9R`F#7D~*DVftWhC`aQGmA+6T=OkOhsMmzeRDQl|R~vjRgj^mj9Tr?Ip-s0E4IB%wu&lu6Dq)0>0$Yt3)b`pfm^ zff$eUmcX{5i@DOegxRv$`~^468+Mt0MPA1}X51GrbM~2=BD}HR{1i>*9x>zDle~&& zO?i>X)-iIXagLdOMAhTC*?{cPf0_3nJA7LKAK~|N<{3Cgr(ZQ+M)6i{LA+0ZSw-@? zn)%G)8)kYnx*eLC_}gZ>%4FVSP2ak2K0r;j1LBrP=5RD{=ZSeLHs=2VjiaBNA7hXb z3(YvinE|iNO^_Jg0f=Xd&1ZLBf!6k&c_7jfI|A+22Q%J=Fsjey{&0;%T6KcP zr2G9eoYFnRjpT5|%C&Sl6cL}r*g>C^dgL`&vg3ai+KI_o-{?(A3f=9>E2JC zxI<+Uzj)I7SEuiu&k=uB>S;m#00%F6cW}eeD-m&vix=LVFzK#dcM-PYd(me+#R4x8 z;-w<5h5*UD{@_G{)ax2HR#$kf#>SWd&{*K+Mem$7YOh*IY^(K3L5sOMui*emRx&WP z(tFWQdpH=qXrfm)d(nf>(G(!&dwF3rm=Qi+xX>`!eqJ*Xh6Q@ji%C)~GX*l;Pr>l4bIt>M)U-LW4C$|7rd(a(XG*7n+B0pgaSUZDX1Z>{ngPTgDM^$y)y zJqVb)WqZ+yJ$^maL|P7p#G%mP3HJs=4=W*bI;?MEdse zUV#&=1QG2h@9*f1W{mepgbT-d)9bZ=#(N(IeCpf`Z)R?W&ta6_F$@X?45^Hyjq&A? zqG2)4%)}KwP2k{0zE`E`Nc`{^7ves`cM*`f4+k;mAQjUwB_n))fqW#n*xY4|FCN(f z$NC<_wwDtSC3i=FXBRVk@x}_SQho6dJ!d4ec}(%0h`GFay6>wY05iY)#&!dk*Ts)s zT5Rp+cNx8&km$!uNc7)`HzfYU{d?j|^0R9M;NFL+S2JKX!b`ORe#iLdjR8NN)(S8& z>n#C$kn(GEKwYF{j4gM3`xsacj*gAdFi&Fx=ymhmIstW%r_MNVE3$q-SKL|7Lu5;f z00-PSZ0{8C5H}9 zksTSJEA>?XF6~6}8z2K-2OKqnM1V5zl{+-%Vq<7mP60A9;Z=Y$X*My2 z%dDv%$j2$Uy+dFY?i*Y63>*(0F&75}-b6*4=LAZzQXJkASQpR3rDp?`xMfs63FKhA zhhxxZs34?xG8hWc1?|9-Yn>^8y!8#5gMFKa$kMQ&8$rN6xJ%GP*l98!rUX5~dy%^& zC_xHLm!^ViZ%L4fbek5#VbV7Rb@Br8ne#!pXl2&Rppl*cUBtmI=#?-$coB-PtrPqe zpYCL}4wlpdxNBkXVSGlNdMQ|m8|B!Yl*D@@`2lWoE8A4E>FgoOKXeV># zKpzY0g?Qn!4Qcz37pUxOQV1V!Gt%b*QJ)?n#052IWJn=u)JzUZ$6eaYxgm?e5?PfA zOtpRw>5jyu8$;%z2LJsb?=VdaCqkU@`G)u9kVwQ6?uAT6_Z}65yg>eoA47IyH-7sT zG9TShI)(lPAZG#SEOrV#1YIRtIiaV4WteLyE{dc*KlE=v4T4bm#Q(i0lzu#|g)a18 z46HdgG)n>S&pM%x04K*5gQ}x#LX&Zh>j~zODa@T@ZVcs+?gV!215-lR;{zSlq|n{y z;@QklZ*;k2WhlMToV+IV4o0l!#?U?(!LvI;uV6e@9|*lnBbpof32-uD8R&k}iPbHH z4>yE%#mVe?F_eCOz512VRjBsghoN0CLe<`cuELoA;~aJkC-P=(m?I9p2i>pBRV{(`f znO#PN1!C$|TNY-**R0y@3roQEUAbXm>~QY+Fq)#4lCUipxu?$ICS2pXt^p+mb@*+( z3IAF%`~nD*B4{KFehpVKpW21v+Xc+nU&HA)oz(0ZUK_;+4iCSB^Rm^U@&I_N8 zu#kj5#5i91J$wtqjkI0|>KCjFcl3uwav^*IF6XMJ;bV|_QDL|b_(;aD2XplV)ig}O z$M85*8!V{S3B8}{UM&>E&*-b+`;_F^2H>1j4LG&F)!HCOYPD*iK$*HSsv5I0Dk7*F zw4F_kSTqY@;-?6Dw_*Geu>kASp05!(^+8MCCU9zIo$Bsh(6~0KI$ghG`&6IY8SoRi z)oaAU!;&vG_BID}SBo068UT!0P=l_cB@1iNXL`AdS$xQn8pC1DA{V!UPKO&cZdV89 zd&0=USdH3-L|%iql3m-t?b%t8ViMmXat#pPw1}k7k^YR2#3wV%>DG~>>08W$I>cL? z{r#trjI2m4nXn^9L3UBfkRrZbf7U)?fG)Dx}7)j{FCyA-h4+FFVo?XGQCSk$CE6obw_-1p+Mn z64?xw?q)tUY2fbp*31qCd{T#+{yrcaw+Dp(8e5ZYT-Hvg>4I}$^Te91Kr{1sa?Mf- zr_~hFxiPC|C&YWqscFKwFe$6%a-1W^9W@6A1Dt=mrrsOiF8f-Spw==I<+ZwD+CMbb zlC%W)HnrBIdH_f6t~EXe;HTnRb^XAJ*aHyMQ}(qaWbgr);X~B5Ga>WHTxe#rhT2!q z;gXiM{{~*>;PBdaux>1wR$G8RtXo$5I%dzf^|k4I)$grMH)Cy?+VrZr$-&y6v0~Lf zR=X$yVD&e(Q-a{;=?LiFZ-^R+5p0D>V{?=T=6i47sCt-9o0~=LMgv{CM&Ti!DM*Zx zVFH)-jk=COxIQ3i1e5G(&{)cE$?q>fHv+=u$;%h z?wD`cfr0L}AHMDZn8L4E&H?)UiiO@*g!~2Z)ZMUb0a6D1&D_0Vx$octhl*C|Y2?Ih zOARS}=&@ybT5M8}CFi=qVWgiO;oq@@*8~>)HQ}mf6~_uhVpmw_Fkn3s;ml(1pm5~D z&ks`pvNge~b+}_kGVfTFcz8i2Wa}M^#^ek?7!3j`J^S=aP6p7!M%I3NDmha-TI&#Q zzd-7&CLVV!T=cW={Qe~9u0`w6+kQd#Nu#)`J)~{R2ih8YVk)wj7sRh|B8NZ_4FfTj zoV{z&I6R`%z@Q&QZTjY8)3-neu;urYE$T;_1Nyh1xP3ub)~NPaX& zk{^RJ1yr2ed28YSByW6d(b@-*dG{Tf7^sTg!yiU+DBt2ojE5Y!|8K~GDugf{ zlAXxFKOI6y^n*&TNQ(#1<9$vLFf+L)fp2ff1ghtM*t8A*CzMd@lhgXN9g;X?a9jB8 z_y6b%M#~}E8Cl&@(uVZw{bO%qNfz24;Y;4+77#&7qoWK5aUCO2>lxTLo9r4 z`dH{pT0gW@D-SxE@etHkbD8rnmHu4AQS0G&(U6j5^a-Uc<)-vfP zsY8BNfT_AYL42DF3R2-d-`0Gzkor3PiNy_lMdt6ErNs@L?d&X2p!{rnohC-+1>bp6 zQt$+m($((5fRxymSqZ~_akI0#7z`7^MnJAa0d=GFLI3R+Y5EkhVSOO=Vf@odA9glU z4p{(SO+QF&XXk07@>&RGzVy_h#4o)Ehe^RxNP;a^>RWOx5ea*SBgN8Up8-u2LunUl zyOUW!iq8p$9mjtp8D6shb|k7H*i6WiHU z{i4V0j6L1aLhyfRLJL1@!yzTjo)oeO)G8vbS z1s4((BQBHLt^9wER6|vZfRQ<6E>Vlv> z%Aee9`61+Z0tBTP`q<3I&9vhv8A!UmvcPA6%Ho^(3UpRK#Zs=lviO#JKjZa3@5X}n znULRcHhLDGL+)nfj7jmGy0iqwN5EL$hB55|G6s=DMHbI;?vrbqnl1y^7?7|{977(l zFSgXL@T_?;c-G_+gtjr+SzIMZIY^j!83;9qw8WyQkO(RP!m%qr2qnWyDyxX_=)?_& zASC_ZSNN)a#wa`EpX#s2WjF+-!5>)YmcrRh3;( z*$VgzjGzD|s;n036Tez^gV zipOEr!9rB=FFRvhy$U%#ae5CYgmO>x@4*vI!wRxH8SuVJ!i`3+(iWFM0M!D1>` zHnfwpRwB%tJm{dcqHDMV&?m6m#!s;KjsIxTmWxDe#NLqEwv%mCuL?0KN%&Yf z)V}Ro?E4TxlQ5VNfeN${g$I8-2F#<+`z zC&!9^4N*RV0Jo6K^@jIvk4+fJ3+iM}!Yb+tfb1&GwM@hNN$D>CvodjsuX z#el5-3a6LTi@(FEjxEmi|l=Dms{Zm8g)wR(#ZYk;uB}-<53mC8RZ1J3R zK(`0_7X%|%^<nzgm5VVK}p% z)v)TjMOLn1X@2;YDPlW2NAr(SQf@Q(QxR_ap1m6< zrng8;r^8nfDnluxOY2jtTFp*-IkB{?tQ(G8`Kx^}N7A8qMcM=_{cjY}Eizi+J%ZB8 z$;exjF5VBE637Xtj}`y2Ge(^&mr$2QU{Nd_?MIz2Z+0Tl`=DEM@>u(mMy15QTb;SY z9_;Uc0fVKd;$L=#=wH&>$!cD7&sq_-w+Z3R2f`4Az$6Q+V8uGn{u*T_s$=#No4HwS zY)Y(toTY|4c)J|_vn}V!yR6u?6A%{dnaWL!{&Md&C6Ew2$ji!8D_Ra9g$R@(LFCKL6{F#YzxU32MF0DjFqF0%SgjxBHa1F1O-o5?JTxUS^*^`tWR4SbATfy5ovCwh zCSlHSO1we5Z?2U?Y4F1PCZ+>pEp|% z1>0{l0*^<5$F`N#)ToMVZH^TV`0Oxu;y?(FwPL9O)f@yr4cz{MuCaW~a~SU$HZzLT zh826wvC>T<4)f~E-;^~Wi#I|R+s=xlb}V;ZB=q=q@0iM{40MI4Jn2?Jb|HIRt@3gY zFSvq-1AA7X&TimP^_q~>gP;P8@CXz7~BC3k=*@6fDOEXzYE54OS7V#>RY%>oWYq+2a z^$pMJUfw$iAE@gVRZwXlF_o^G8_%8KVP`k6?vI7+3t_qMjC7FLlo!13xrF*o??nzz za&57LkmvynuD(5hWcXewfwf}WozVY4dNxET7*<%@F5ftW+)?EY-xq*^Gr6pW zn`>Y!Lxom)n*d&u7$H!XpF^sdgq(43C&#uqxR7XwaJhm_$fCPhg(c4RQjm^ktnsQWWAeAJOV@xHF1xm3@PMl_2#3X$UXC(x;bnO{yUp%DPEzJIDG^)i zmj}usfk3VMify6f*c}Jn&-=X95*W@5F3C{Aw|?Q>Dqe_IoKe=Ntom(>1DtALTgg@QV%z1k)X9PAbT-Jc^*G&%jGX@A)N&{XZeHMyN=aJ(rbCSK^+ z9`-ULsHVC<`t;v=xQ`YwUJ&D4xFN6&j(07phh)jDT(|!gCY_~_pR;2t7{z3z)G8@o zV7YC0y@b%)O^^k)J{HueBJv4DDr&7n1{&+vuR@;v)qz8P1t%(a1|W~PQ3a2o++b7w z(oG{Ev^kK;wrLmNq=H*SxN>$yr`LG~IVh91swAsA^!y*a9fhp@TC=PY1$+VICa$9! zIV-Q?+b!hT)3t)fU}hKM*q4z7T^wPz(;WhByW5yeCaA0$LKU8Oca)L=36;B0Q@%XDXk&pBjGXYK=&?? zR`ta{T6#9V(h~U;;s~#Ums!DsnieRr>t5h55|hrkWUq9OI+e;A{lP;+G;w#QF{>5&W>8)s9Os_jB){iZpHm*yOzXqjbwg`s_iLRWPa%FsQB5eBp=S3OICyi9$sX!pe^H?SalW^?iQHZ! zUu~_twEUumz#Mpn6i*VRu~ri^|1k1K(pN54jH_=!tu6Y>u4~rnUOuxZqlNvE=I?+aY$(Aa>1dVz*FK zDUqg!Zv(%hWhWPk)fqm(4nv*+VhwD>wn~2x>!4%ZN3p@{s%4~rSO*YmXd|{%@q<|L z5l1<>qhm)IW&hqb3+N}@6yOd%*`Wh zcxrN5Vy~Z*l9uc+J&fK~xFoFNYDBq}58K3_Izs23nSPvADV=R3)Z|#9qsvd7JGbW3 zyb@UDPxt+?bDnS|SH+@|M1Oaz`mo#8E_G*W?9tk9q#vNZj`I644F)bRmzR>_NGCV4 z=_~t0C$V2jY}Zu9mOr7i-iHHxOPe$}yWK|2+s0$>-%?^5k6QTTDD2x7!$G>Sjp*n7 zm7-xL*g}49>}1l0`mSCW0c69F=7vnLjhW+Ch=a^Sad-1k0in&J@*bx>2hJD`NCw&*;c~<)8PDEl^TE z!v4^9Y&u-v95v@6us)X$b>b(?<8KdKKIX^2LkYG@%HQdPCn@H)YAq zy{+_41u4_NT1TITzTr8-cDoYRmgU~!%@%s(o>NqF+YDHbe}RFuy?Lw)J17k6sC*}Q ztEcRWg_;ib0n^4FEV-{wWj}sDzA3CH^g|0PkZnJ%z)lI3UG%Zir*Wv~VgF8R10hB` zz`X4(`0V5gZUt%VYn9=74Ma$HUpTKx(yCB*0=0ZpKMtLC!~hD{{G@Q+z$zkl-Z*h> z4)v%x%nvlX4q@5i-aE<3ct7?)jnp|vl?`R7XZ$LIJ@I|QO9hyF2jdKLzv5qZ#?r|w zx4%Ejt>~W?%NX;ng~HHmg=ucP0s0Cr8De~<`LhQ?jL(KnvB$UVW)-M z5B%9JCvq=_ndA@!`=|hG0BIOdsU~>;*SDEaTmFHHXuE5CwG_D<;Vup}kc+6|`dU_n zGO$v`vN};Wp24Q|B+Pu<-2VcbDl|AMke%?Tr@UJ-{=il(_m%T46L^Ge&$}5JFh1AjhA%j4vK`1u{isH@+)uyY3q0Y z^Q_2~h|0=W7y&W3e~TrY?cyS#69IKKO-+jp&K>ji8uVWeSHiZsTHa$tR#vZ!e*Dhr z)3vZ7r^DXCwrj&)BDWv;Qr)U5ues_PVENHkB>0jpHLUJlU^K6Da_oTG5vw~xpJ#v$ zf15#kX~$V`$yG&mf+%uh{@l2+{!5ZQyrlpYt&R=%EL80@tXf%bMtoCdp~BuhwM88T?ohLp*9)b&iXhbBGoGz_!u9 zk}lX5{40lyscF@qzsDQ5iw(;UJ#Zgp@Mn0K3KgN^Uv|cV1P(a}A{Lkq|Mi={EGxcD zn%$p6e*qZ3MY!_yV*0DU(ppf3k>~IJ91?;EXrq*4kTVR(8g!WDZ#!1qX?g zC@X&227boa4MvUqmz|N6O3CmE4|29#M3YY2RHRcgs~CqH`=2-Nl4T5(&6%)LLxxuT z%g#s~${~q2UEE2$#fl$o>1-)8EgcPSo94UdiM6GbTdwEq%I0Jy%)Xs4`{2@{;$L>g z{0X4PYK1Suwop&uhm_eabo#A~5M;ID2fE-#m8*7P{S->Zj}^?YmWiZHD-%(-qq^XC zm#c~lnNddW1LShgE(E)Os0ChqftqjYS%IOK3U+D-WBX4bteb2wyPv=!|i zEN@HAER%Q_P3^-^I#;z{V}2QV)i0IyoyIjAItwC`{ZnKb+E_I&UpO?=^kqhFYoTEv;|N5f~h&MuKHCB z12T?cIAoL#G~A7)8lIJF@HVR?s0JAe*ouhJryh;%Qzi?gE+K>yRu#UAlu7jA=#%iC=`+^>Ps^x2= z>-J@2gdeb~Yo#w4BlpJR{Vyy5u4C|@$%eb}RkF0MHK;ta$#3Ed7XbslZ~^x`75}m` z4A(d$qX`G@8|zx(GiLY`+w9;JTq9xitT+wfhqm2FdOfQPoxYxT%LHoG;|R&BdNj71 zK#Eew-7llwuSW~|r*gvF2b7>CHS5zU)1-cxM8+dZFp=48cEHbeS4~Wxmt_hs*0;Kq zcPBC_@6=?d8EG&p3>BThh+inEWOt7EdAY@?NhZ~)n+A*G?_NJfE%P&6&35CMNnHEN z`ViZKzi=&N)l4vGyGw|HpXZAi$O#}98_+1+Xi(NCKRJB&QUjWofep*r{1xy~oQ78T zoDG>!-u8>ymHaY}6GSdHWEGSXrfNY!Lu(i?2RABH(9{EJWh0uH@T25aV^m#FDGK-; zr|paH1b(%=D&q+w{8D)%D}1Po!0(t>r7ScVQ&d$Xzo9mee5ztv~7pfNWa2q8*-$deqgErClU`RN(Rj@ZvxF@46 zWBpX@^~Wf2+h}~xL_dqJH@&$|IdQsebTw+$k4Mh0vA1WBUN6v`KOx?E8JcxL-EzXa zU9?G+Tz#ZM>s62#14*MlXV=4pldJ6l^j!|WyDq(RW_^4x-j1vAf{L|m$XI}w{)?@h zUJF0RuFq`g#w_R0V-U(Rw7p{VaI_`zK$9+RlmH_x!jG^RDJFs|7;s1@Dr*Qevtc_beeh=x`7dr0fc*g13h*;LC zEN*t-KK-PFlGXCv@LgZW=wK0fz|Kz8?95JwpOM#>^ImtBX`%MDo$U+p`7bux5Ie&R zS!>gqC4W}+-PS{o8!50;XpQ+cUdpQAh+9|rKnr_s{ z|Fs{1;#EhL7ze+}1ImfXXY5R(dUXp8@A!Rt*}P#x&_sXOdGPPti%Ok2(BNn8^~sbO zW|c8P*>c4|cOA{Nw-|Z_(XH_nGbM1VvywpTB!%sF)y_eL#B%*5UPE|{iB-bmoG(}e zztwQf&Q^?)A`@d31HHy6Y|UuvFnfG*gv~xw*(+3;F^X54poo|4Vims|GxEf4h?hp? z7{!ApDdOF9;%Otwc`bzaj~`ffnj*#BKZuj!=v_mtT%M{JF1l-H_rdtLb5Gmt8Au`L zgA~?JXT30ew@SEgFg<>zVmj>;o=L3#M5(Gta}=UL62&Spd7eVl>dG2Tx+)qE7bwIu zNu;R6=Zo;sY}aUfgDP&S*!d+a>@Sr0Sha47l_jYPK{GLEOjL;;=?bCExl~tm-}=e( z1*m`nsDKbd7YX@N(b*ibQpsy4H4;}f ze#*%_QppXcb>xwny&CXEc4Iyj>+v{j4J&_Kc>UZQo1LoQ#>tq|7!4Fbf8WM zo|pz`e>Nr;-6QE>;nc1iE-L87Wy|ka&RZPqrA#jG7fOkTy?9Vd?pjViq04wiEX^x( zdl`r;FU8E>JT&g=t>kRoJCWC$TjtpSq~Ov=2_mIGvghTSi0l)MuXRQ2;BxvHHV|ic zXmuy1^^tR;kP-81wD@jCRu7b;JN_Dn#Qvr2MgJk?3`640K8o8Efh_66edGOTzv9Da9xEaU(`INxt zu?*tksqx(E{l7ODAJaqAaS=u&)kw4-&c4AQYCQ2<45%-YzUB!1CfT zs%4l5X!#6a$MFo(G%xE3TEJ1D1@_Lj7dfA?mlu#wn?)?-E#iu-xsCc9jLTws(PaQ~ z8$7|;PUPN^F^+2Km-a3#ViDi!VTx2i>wq+gf4oG|2`X{0|I32K(6j; z1HpW;Rbl2k+iMLG>SC|&^YylgwF9{ypM;9d(`_PR5CYD-ft-NzAYgWT2&;-r^rFXq za_G@dxcS)QdS}_XIYdm|ON6g@JcxCZD;N^WI3g`?uYf|1p3J%II2dXZj*4SLq65UD z!93|%Ls*Yv5_6cs*ki(O{V1_MQ@Gh%3<37>Nr`o+Zttn)C5wp1r-`6p^9encT&G7* zu`!k1w9!yt3vNnm^w4O!7-u12ZElGjL%B9C(_@nYQ9KL|u=WCXHP==++Sq7Vw1Z8> zyOM}$WiMM92F4D9#(_U1F)iO&Ys!m6m>v*8Em>_i9t#TTF;J|z$aOW5Fw4J$d5PV_ zIg_UdyZBaO?}u|HeMX>J)85&Ow9o7-u}i|>Py!E^Fc>U~wsT%b4Yt?E-Ptw{q!TRS z?{Wn283`SSvIVkAEgp$Z@)C1IhzE(jxtzZfB)-_6L2vPXBxlfj6u=EZ z3fOrR=WqytOM)H5lG*kqwMio9!0_2oFx=AQAfiU2;L{d6V3LQh;+QWn z)huf#*+t^(ah@4WMb&WZ2I&0oBXkCu9YkmnNA;LZ0Q)AP<-5n=W~gWnF(oP5mx2?8 zgScVE4>pLI*P^{_e(x&jk%#S_G7i8wlp^LFgn_qY;AD5JgmB)ES%^~B24A4P`}9=U zFL)zrIbp|p>oo2hwf4HCjQIZkuZF6^&6qzuF`%wH< zDi)%m&DbPKoF2oTaDNQaZS;l2LdHeAi1;h)R;8{(qw`p3c#qYw_z`KX981SScf)uc zn?II)uIQE_yJaS6BGgaYkAvEgDH5AGjxB-Jua>4Pm9Q|9UvMh=Pf1 zG{w^azY#qVt?>13sT({=zFQ=B^muyr+P9d2-}+z%p0`g*yH1X_6Qd?^Y@Im?iR9vE zB+09pI5dfi6lFpp#gmXotAiSgYg9z|WGd2663eRU;H8H1$wVZbU?O8mw4+F=hCxuN z_9itP^y5yqQ>qw^;ek`Aq-RvfQS_a{E)x}nJS?3;c{pz+J65;$fy<;>Do-UH#LXe{ zq!<0-?-v=i5gkwqlQB+*8OGdxcUVItw!~cqG-gs?p}AsoUNe&@H6MND;qOx9O>1k^ z`DrRr+x3SejhOp4?9 z4SB0}++7YQxuZc(C;Ez`o_>FnbDlE>^lhR~;^*$I+R7N?pNvee3{{vt^H90UWF~8& zA?JH5vJCAo9)phShDwe+v6oZ#ZX$UhudrNa!Crj0qljPZ5UzS*)KY_~R#libi6--` z1NlBuEU4xv3Q`>+c~~IlpvJ6!==cE9WkR%f={7YL#eVlVQYFoXj$kHVBKV+jxh9&jxR1!*Y%Qzj92lzELSZ`1;ifCthMJo;2Q~K+%%V9wHsp|G zVQWWGxYHp-zf5>f#LzyDB6KdhNaS%tnOpW_CP<{t<&eH^F2t|JE8v~E-0?zAA;sb8 zNU`o`3PZ~TRQcUAU`EXY^S=Si1d6qpJW1Fx4~80!XV6O&%wt!4Md0a)3K%h;%hP5) zz$KF$h5G`Wq$#fBszr&oJBGZDnWD((2bgE1?L3_snozZ0fai%b70Ft1=-5tR^lSz( z>*?l#9W6OmeM8Lo<;?hCkHksq#2puc`3%bhWXnfPdTWiu>%iDlSt*V!24Q&zf}$F| z`5?FG-Qi3(+A($epovSq(o}F!Zg-SZ8D>V6a`YCx7op$JUZWGKF@bA;-6ACSTc(c4 zigVH?+wX~8wq9fH#BN(hTXn$cv>5E}ZzMY(`9%$d`Xeq#*D+r zF9n$8qQq+4WNav5`S}u?dXvMETLVY^h*Rko$;LEr)Yb~7RH)s)LIlkhN7AF;pY-S@ zYIbncB2Zcxi2!lM)ym+myd7MgODYi5hzx14VQa_rALXnmRcN z)3-3DbjgrI_;;g~NV0AZC((K(?=&T@gk;a&P9o_ly!^l&Zl>=D4DP3Z>rOiUU#}Nw ztSWJFJG~Bl`A@twi8OfBk_4-Ekm@`MU%~F=0anIdtXRc;Ifodpp$b!Vh&<^+Nvs<* zYR+*a@gtH5HzaX%xFX(Y6^DyGzo2|0SD}0-Mk!3ns%V;kZdwKFNl6S=7DcPLj$RY^ zc7h@qxtg8LQebG*u9Fqo?Utjx7+rva_>wdyvE*;W zL<)b%M%6U{|6S%JI;`P+yJ_@%YK4Lj_H5Aa=%E>tdx7O$ zos5@~)eqElHlQl1ypn`6Jf{#g`!!)S|9C`?(QoN7P*@hT!!-CBrATvj7C9SGw~1d{ zvswA?qYmQIaAY&YO`(r`&Dp$0I==(__iLWug>J;NaCa6hHuB4c!SuY%$62@~BggAD z^1wH9x|4oTUE!zDk2mt1+kO*t+yWU46oJ@wQ}-hqA8qkC6Yh8fe~o*XmBXDy<;6}F z)n(oD#fBtoD?5wz%bfhholP8Fo>LOPR#CuOn;C4f8DP)q3OMa1_eMeB`kD;ZR6XH2 zf$bw1^b<9=aP^tD0Ib~9S@hq+VSEuiZ)ol;c5UHJyuHVU{~g1H2qYNvq8nCrI72#v$(g7Gv>M-&v!m|#?Jyeg{ei1BXHdSXOX;}C*-^7`P?99 zk$w@mmm78&JAkDRWlZZOeF$_P;Vj%QImwj>GE?viGIMbUGV^j2iwEN8_MB|t6&#i9 zc0y-aGBaMH$xa?>(jP)+Bl6cBoXOZ&ADOk;L^>4Y1kALH1FvYvN;YVeBi1$x2kKcd}LN1HadS)wZ<{w!>K8(;SCUZmI2@9sU_ zghGaB|j98(VAV9(gb<(j1R8;bXGkjjSZk~m_qZb(0mG34C#CW%la z%V~~`?D+;6IeD9~K;>mmMt9)v3HK@{+zS&C_N&w$Ab4K9@h?e?`j*|QOHUAMzlFx2 z7oy`{tn!o%#OvQ8Qyu=6n0}b3z8A#ruZWua~#q`gjU8y2y zkWDULGO}xiK%d{@;vzcjjm8RfU$hU=w5PMJmF2=5(S`WN?h1c&ALsMeeQ>8z<<4?MRwo&^$8{V#6>&TTe~puj zNs%sM(hBEbb#XWHdl-t0auI3YM^`{u_R@3D`nWSQ1D$-t8fQmkc9U@cM4N^R(KplC zN9{8`i8+ z=$ye?nZYh1A>TPrgq&AiAVTpRDc%^OFuOV^uL;2f90rpxoEdF+97o{ck%~Ai{~&pZ zz|o@_3=pnIc+4~%0k|ufK~M4N5iY-#dyAZgwOc79mR9-e14Yi zj?^{@iLSr6DcU3qZBh$?~+Ck zSZf1=_&u?u)>U?-gNwJQ!LNxnDRdtP7j5$CdJNq^VzY=k#&d!`^jNqVzY#~#V6kclQ7tSsjC9f#bkJ%j~`OfPI5<=*45 z^;tMHXa;Ii`Z%%PBKk+`Fw;0=>wLL&yzD2*F8PszOw(#mqgP5cpPmq>ex$em;^~h_ zqwQ&t@DpOAMef8k`3czAvxLzOO*%bxx*$cANq$xx7yaP0H;>q0bwY1G0gcnY6Xqjs zw?-^f95aQ8>klMR)ZSXBcZfLtClMYZ_Z04QNIZ%4y?#rs&Pj+AJv55YLVp1f`A>`l zdd32t>^&ehrpUxR2jRs&A~flF~Jx@3$C zrYfhS-PLW1k+Y{A5uNfer#^re-As?_?y9}hNlrrjuRe`n|Zk z4Z{_g*u~arcU^)UJv)OORjlkP-kf2Fu5=dVv#;VR($B)POiST+%2Iz6F=J{oL!17w zt6cQac3@paXUZxU8Ew^0+;H~wayRiR=pE)>#!j4y-(VT zJkNu*F_TU4ulvH})c^Bx(;laWv&y;hG44Pyd!>aM=aQ zq|!@$pWYJvi4NSam{0;*{x8VSl9k1wBuo zbDjd0xx&pAbOmL)lqc3-MQkmqgFv8E`e-7)yCezij1g_)(=77Z#47&581XXES#Se_ z_Ge@ACzqwl=!UM^ySb37uyy#VBx;W1#mNl9lCKlSA9lpL>VviSVl>}$7XT9%w>PqBBS4B@rH zb?XS|wWn)G5i8Klu1*TQ>pGX@ND@SEUPs!$fO&X5x{4a2+BJWD{1G+-C*aF12yJ##hpFscLSbRH|!)8tq;Z zc(4Y80b<8_>$|@ziT>@|!=uK2Ft2@QW^qq3=@$EO`YqU)AESV)H@RxFp1Z`fZR#f4 zZg$0i8hN~{=52t@n!Abhw=qSD->URR zIw4b`ox;TIcGX@r<>i20OZ2@COb2Fsh`Ym%kaH3im)?QJlVD=*qK7Ik6YiV_bBCC}KUWy#EK|^3q;q5tgS0lBOwVnG zs_OFaQphE!7u|(=-4V+Q*9?bhc zT|?A{4*Ub)f!S^%{SS;m`e;6im}PUB;hz80&Tdm^FnNt?QwJq5or&3uAB4#v25&ijqnx454jtb@_^bCQuEH1 zY@~-$RcYD_uvu+#q%#LRMLFPpVT zBwbt_O+U#MQyD4EEJRA(k4qx<9dCN(6~fl@lZ3^P%Te^Uv`>60Vu}$&>*o;>AQB() z@U`k8gzNvJVSY+MG9CecdR4=H#LR!VfCGq_|C>g5sYN(R#OXgIv7^14n^@;!rjIBq zJqD5fKqI{Q!##Ce>2bt1ek;b55*$Z1esCZCRH__(%wusOnQ8P~!+g{#@9+df@k@>H zQnQ*(#G=<4;h|>LxQ9X7e7VC?kj5)2^Ree zxgBqifg!khQEvdPA*;yz3~UKDH1(|3)S1LK_m*r-5&DXfu3Zs`kNq`*!qOaJG$~QxFdU==JtZj7(9=iQ4JoH=KN26sQA|^Fkm;Xk>qedH9t<@U+53CbzjnV6B zt(Hk_+%z3)c@?d7=MrM8%rvs}T}Iq15C!uz0zXXmmLt_kCKl<4J+C=Z)%XV*t5P%~ zRE?8qMAXM!GD`mZ3^nF8WNq+f)R+rutg+UX!=q(XW7jy&_ORb5cnS&Pn4`0n}@c8eQ65B6nDxvB}*98{jhemDwWmErz+|zpN6n_$z9daXR&XR#;#7D&~GK|`k+<$QE<=WWcPsHSH&$jduF9Z{pSnR;o5(jfMP z=!n2&?jBr=swX5&k24KG|Ehy%Y3vi(75}TJxR!4jSRGvDzpT=tQ;029)5!X%u5^>w z=(>`P&2&d~U?5hV#q@sFna{*sUpPynhMG(THO_4>3+2C}H3C6L4#Faa^KUn+C4I)l=)DcuTwNeJQZC)S{DAMq`tZ|-lxQk9jDe+c2*xLT7Mri%U zsT>IBc!eMjnqhDDQoHUrV#|I0UskE)o5UU&^j}sa}nMYS5eN4&efRjhYI zHLe*WW%k@e*_F}X!2!yvCTK*cTEC-+h@GMl_<<~0zxhNwoS_j2lyWK?=?Eflfh3;1 zL>fx>Sjof^9ijA$V=oQvd^J@Q(FTOL*9MU>%QX?%u4(|dT-T?A$n`LL+x)ObioA0& z(?qJeGjerkorVRf(Ko@qyuQzk->8ZB8|GRk{<)d_6cHjB5u&zX&~e|Usragu(9H$O zb}WS7#HoHb_EE!nozXcTBQM4| zU+NK^isCX?$yU^H9iezjcJ=b!qN5w4(T^uJmbTtA+@O(tQDV9E%WE$3mWb^?YlP+^ z{oO(Izp4?^Mb!M>N}JK;1@bx+_4cu$-d^9(RHXB$D$B~%_+vGS=!EgTkwGQ;52>=$ z!|bQ_nMLK1d;~V>DJC|l6A5Fx1brmZLyjtSL?ES*S+>M7%9o5ocSVWo{e;@5=)}}VEC+ABeV#zj)>2jX@ra$ zYF%BM9#wIYxbz5h^-Vh|llBd2Ls`Nm_(OSeM~%>CBS(m+(M_{jM!fMilRs4q08tg+ zD{1WmLR+#fAhw{7k=53%kBFVv-^gl<*LHzm4-S!R{)O@t)mdf^5i3S$g!E6fgdMYE z%UPs1G#stTXbw3k2+C*i)e`6Tdih{A8r>k`{8WV?Uz`~XKc;a*b^TJcfApdLYC?ZC zUNQH>Orsu6mA41O;I27FLUWAdXNK;Uu}~+s=UMqc&XyYOuRKjYEO#|trR(H&o6Pjh z)D067dbPqxsQVitCIoCYzDd#=2x{5_X(O?%GmWe^|FR7Qo3@$Qy6Om;NZ5w$gtb88 z(>*-krJ?ZBFY$>|sJXT}ZZ-~socUYAqSVnMlQ7pqgawIORXtD}az<%i0fZ@6N06JV z_1=2Mrd_=dat>j0*jVr7@%@ov3)>5F@98?(I(}Lbw`+RHszg>T6+x`NB#F+Is1juA zTZzcNq9c?}=2i&=&dzWe(4b>F;|c3@Tf?-UafAg z3~ISYl-g1b)nRUXbp=#fX*7We5e(K>z2Gu|Z)!6bq2@beJ)E^e4KsF!aUL_hrZw~H zuxKh>J4OMO%$KVP@M&`eR5Cw`z{PDCj6v<^?lTnTN-QTCAlzzk=WJXH;`!Yauy~(` z)-jU~BQsbhIokA8*gLh@!|WoUV(MpA(Oj&{S*Xm6K>h~}ROq%B@G3-!-e*a%)(|Tr zcOa3;Dv@9cMk&mNNcg@oAxk=RcPhT8?>s zH0XqDMq9f7Fazfa>znbT zy10T^Q~Bwb=vm(!s7X($k82lS9P`9WFh3u$yS_9;c@t^4IK!s$aq$~ONS?u&bWLNo z)2#VDsAE|Ho`wq^*)U(S@euUgfI#E@kJfYp ze)JSke*}8D>h8GYGE*sRI{Ya5H#CQ8S=I706Kd{0!8nG~P*+dq8loiMg1Q(V?3>X@ zGF(xdl|nE{Q-lA1S@Hh~WxN$^EC}a?tF8S{Vs|55wXw;G&`BC-^3vTz^r0JAUxQK` zZdX*qQQX6(yK|H4gnq2O^zV>ZUp^#Af5bGiF}gzF333ik5mC+4UF>OQrmq%?h;Tef zL|r_#j>cn$Gg2t1%2~4fK zuMw_ddozAxb()AqPbKlLiJ4yPjp}Z&H@jG4acTK7Ph0gmyg4z@HUBNy$iAqa_}-p= zqVqPcqbI}$ztUK2@yW(%+7yJ>dr4IO+%r(Mcl&cgx-S%wCWAdah2H?rfDEbc&+4%!*IO$03TrAW^QRnfAql zTLL^(*Gn{Q$-X{;o*U!z@`zR#|3|F%bWjgi<$P_((MgLUQ@a%#a@z*?if@IXs8S1N z+Q54H4uIzf+}w)6in#ZX%jeV@=``x-B_dk$0=pkQ_m0IV+FH)Js4eQ&dih5w{N}(H zxCPQUwSCl$CBuZ%K~G;{YQrv5wGC`E8^BDc7}GMsA5X()f^E%obIy4@ z+a%2tT|Pyx$}5CRX5Ghqkgo20fn7v9u!m=BY?v4!i8Laf&({bS@#$Lmb03tQzgk^+ z-W^W(3FgGc%Ww`Zl`7}|LQ2Xdc9!0m8qplGw-&=86>LC^J`H*Q*+YUhy2BX8a^Aag8R8yT(d+;pBs? zqz7axKa*I0_YZ#TOr(|Z($6_IWwD@0>}aN0dzW$scF@<7oq;=##qEyV2!$PC-+i+r za@~+{q@$NR|7(Gr&g?A&38xs4iiLvNYi1fK7$%EavE#g}k%#FoQ_EN6XTEL3}yK!NU zwlauZzAHtFTX`9OAA;KG!Q<93K4_!(4+f5p?@6^zZM|GXes{KfqdPK`hF|sl)Etbf zMZFyL6&3L*$cKeQ(#Tya7L~N-Q&dv($D&jZDD;mt)@%VOYP&@g&FZ{p_=T$RTqJxt&iVT$IG z`CjN~vIm*^f$ADTCbXIE_-590xi z6=p7;pcA_ZI)_MSV2r{{--I~#RO-ZTA(Q=yZl=;(;QeprvB~!RVX|#YZ*iEk(f=fK7kE2N&wTQA2Z$}F9Fw4O-qoy`zPdJCxFsp zN=HH*7fMC}sxzRKPgf$2V#xIVF#wg;Gks=aH`5ZaeGM+)|zYpj+ zauz>(iQob3pA82996Fal50N;)Om`|RC2-_I1)TZJtGrlKk=wk0n0AY-j3Z9;@eWGs zfnatmQJCE47*dxG_A+WUnjEEg>4zB&d{GuE$<}2OHY(ck<#GnZ( z7zXUv^QLWUh;1%37dOjEKBi4;zN4P}KmTI`xCDl7rxXj{wnnogtQnkh1QRu>bMsj0~Bq@z}Ro*`OYzL15d9$7*dynFL4kK*M_Y&JYihJ`>!pfW^j8^16iOA6J zalWEaBKOu&^jz5ASEMJJBWYzs8x*I0DL6A8jlUj^U(wL`Z$9%CZ%+AC(&r@eEJA;^ z(df+I4)hf_^L((bJnQ2jYW&RIY4m8MaB~n zYXyBozc0AbMiP^^fEjwfa*&>#Q+&m(FU)iv*k=r$vr~OV_Di1{>Y`yZflD$N3>IFi z+0rq9HW@fVHP-C0VymxVYUnl=oIifc6r8AGhvVZ)O~ey%b2||fuy@nrzdOXGvE)w~ zGpYo2y&T7MPoJw25YWzT$hnPN&8(9#B*VK%iAIDMePr?%La(V*f0>^W0wipl0 z`JBW|k-iT4s_$IaS0671sVdiY-E(iW~bn3;U+Vdb-rip#I>Nh?-=sDD0d28YbjLEev91 z?ntr9Eqx6$HrGkW+b6#hNliJN9+U3L9HqAPHN=6Ri0uDBlGEE8(+ijka!DbP!J^M( zZis}=hCGaaM69P+*x47a|2_+kPmgG0a+ zq;&A9Brbg7ic)n_zj{2kWheucdDPY3A~nGx?U&`(E{Fuzn3^11P(YppLkRuv#TriY9S;8fT>t zMCoRw{6y>!-*6E%oo50qrvr>F&0wV7bjmPaeenLeEQ?eSh0}SS`i}JW*x~E48D^T2 z_L_m`!}f~Mol)@7oijMu{lt9Xq%d!1nE7WnsL+W|x=;QLg|>XKx=q7pYiVPAtBAOn zY-7kw*r@N$HvGk=nY?5_O<-Yp1uUD)bGl2@4B37F4?%JLGp74Ginr1uN+laayaSO| zGHHHpI6Jn6EX z1MpdM1|vk!HeYrHCZ55T^xn@rqrKr|ZhTF|B!GVWio5y&f z=xzsy=lIqUYYW-<^0~0lrHh~FvfuY(-j_g~>^^8H=}+A`36rQhk89`B5d;08mqIU_ zCs$tcVDjg_3ixCmhvBmG0Zxxsz_ma5me-fW?Ucb{rOy<(P4mq(xjstDX9h4BoUwpI zjN1ZWRfciRdWiG+zOJHOp0Bgs>If1!Fx)EQrEVh~17ou&Q8dj!pStrD?Pa^rOq;Ae z3qgz=ts_#e_+pb$cJwL4+KiFxo`viccL=*aUc-Ear2u(}Uc_^klG}zr=sDRahaF!z z>p!>%R)VH$tdFo=%*FIx3?h1_BsiyW_tEcCgmnX{e8|qqdk{qF7fp?OdJSgj^m_m4 z>m=H*)UXvkLWIZ|%)61;Yc1IItGi0Wv(1gYoF(^z`-_7!0l7ig9ni9`4{N)4Zf zlFPasMMlQrrBz$Q{HXj=O{JRX^cQYypr3?}O zf9v?UYlAQai?oJ^&K^OCPDO~WWN0!M+}uYh-!GGE1v2A$l`vo7`XysEz66%=D`BA` zQDRF7n{`8Cd0%qOctlvzEs51!&f61hmjjzvAh9LOdCIhtu!VOeR7%t!t0E8r`Z)smC7AaVp2g>=8`Q>Tp39Dt<%}Bym%@JQwE5p zPl%wgZZbWND3WsTd-^$w(;3{OZxC_&g+^$W8we0L{+7fP!L_rJu%G{t*l&VM{fe-U z-bgH@KeDED%I+&c%zsBji0Cg8WBg$X&$ndN9!GdG|+xu0^;)i#<@S9i0m61<xw*lEycJ?O{*g3{ubl&6_p)Ln!Zb!d*v>M~^ikAN3)oOEfb%gumYz};=TPrKg0UtD3Kj{N;2*++-uD@T%E;74ZW*u zA1Tr)6I*D(xo{nN(F0!@v=#;e`f4mLR(~U3xzHT+*A;J)#=nX}FF(o9`DOZf0?Q9+0k2lEzY2EbD+B!?;KG zV%Fm}g5g`)-d99qPtXyUZ^HZDu47ZS>%k^r7*y2gCt2bN%b!MAh$#Bq&sFSM&%y_Z zz;8;cguN)cfoG=v8$hhZfU?ybCYpS0b{B~oc>JEpM2be3#H|;optY|2(#-P8(4bwH zX>zm#diI(s;%ihy=@lALMPz)#J+a9s%&QS4K{;l zpRFU7Z02fOPsHHwh`=wDnEcC#M^fWoB935?Q3zMzwt;KB=N1qPj!EME7PFJswuRF; zK*Xt^G=jEkO=vHh-%k_P7>~KN4CT0ThKTB-q?W&7cg7TD5UYbbW)(4fc)E>8!(Q89 zxEC&(-D<8TI>WbMeIKyW>396;@QdNc5uLY#==q8X+@#dk-*Dqs0)uK&ghL*dsGY!Lv7rL(TwcJNGB4o26KMp7C;L6J# zf^#=1JK!Q$O6x>^*ujN5HXQ6DVz1h2EX{5!??h>FQ8OFuzmtc{DZom#5jp$Nxd7RV z!#g?C7f5zZfF@f;IP7ADO1nUy`>00B2)Asg^`Gl+*omA zB8dV#U@z=jK#P}Y5CZ5ce_PS%TiK_HjmXegXOa0W=Omkm*snAKbDV?LqXM6J_`Y{T zrxHYp$&BHA#uFnKv!wKzy*wqVRSxgC^7f*_ntv^c zk&XeLVr0313S!C`xsae?v;O>m?~0N1MHn?+3esbP1sGCpGe%#GtgAR>f{hcZv^hrj z_^+XE_%2M4%`qVHu35k1L{X ze1JU<@anX3>X!+xR>A8!eGv79yHy7Zki%>(VIr!p(_zd{$K%jDQVbPS53}E;97a0r zy%?m|Uhz`{^jmFX@g{(eX$BnOXb~|3hB_R9p%#d9bV^f+MqMAoPID~Y({?2Nm=HE93TDNv*zH-C&a_~|(ElNQU*p(3Jh zpe_G6Dj44%!G!c-##eOxkt=W}y^Wv#BP~A0D$K6y0fvJBhe$6vnKR%iVjl#!i#Yt; zDl8@!8gh{_J5WUZBoCp{Nb`N^wpE&AL12sP&y2gwv*xj6q*|P-KOqUF?v6`ni-Z$|G#6JBg zM3`>bl1Mwn9o5YnL@p7P@G&D4YQsetL4&Tgy>lbH#C==VY&Fq$dBnP@-r46 zHJ-r>=-9KopE=|#G=fh^Z1-7ifYXE>#4Uo9n#(yJDJuaaC9QL}NNVT<(Gd1&Dz5|fS>IBJAnAj7vMHu?hB z_fo{kz007rFX-UIg~=9$}S4)S~sFlwZ+#nysi06_>{WK6K zmnE^12xq)MIfG@2zFR&e;wvw){w5B{?wTCvWq7ATvjnGsRlAmfTmFC-Je>@KYlKOv zjs6+$C@qsSAZqQYM9smb?kwnz(-peb*3nggZvuj zF^xa5YKv!Acq6scRhUierXxCD<=`Gi#BaTH#P+NFmg)!*KljlQZ*l_-Z()P4L84ro zPGZ)3drV^Go zL1N$jYBs4ew5r2G(k5Ys))AlV8(&;TO_$OR*LnE9U1-SKi`kO3dl+a^Cpt^6Bay%6 zX@rlsah=`h5fN)ri0~Do-XNsazF`hlv+(qB_h$Vtgp9=%$8`*@-y=g)G5pBE^U^`M zPw)o1tZkN*cy@!|J-Ips>BoSITZpaje>np6xw=zZw26Te48l~uo+05=xCQ`<;-Pbp zmpJqr``pRjP>4P|iKr;1y9XK0B!=Du`Q;uW!^KeaayaA+!eYNC%!0=|^tkW`X?RCS zP?2cB@O-wFe8+(qTin9tZXE@Q@O1rp! z93NS0;+O%k+Q%~@?QD>KRNgdDCoXE-;l5(NgRJ+MDK7nvSL@U6aExy9CsN41gGTH& zOC#*XHo1;|P8z=RCGpwU9I4yf1?I3&Vhiqa1?2s0Xz_b#lD&18$A-8!NW=Meq%kH# z5@~OOJk&)_x8GrJ)JlzT6j{HszwRbt#~LE&xRA>qc-)mqk2OT!Ke$9A2`gDgSaosm z4^}%%Si(kWuh=cvPRz9ruAr_DEcd|vf>R$<2Vk8=rw8m)y5Jr z^tQ+Jz-~u8l77@`3`EOH*$8#^}F-XiWaB=&w*+VtF@xIgBlQ8@mhK-@8N04H8>a5`Ms-aJxko7Py^Mk9Y_uoDf z!TOF@y0%F~J;fZM=n402m#5HiuFD|J0Q%E&xkmi#FV;TgF144yX8`FdHSy#r9{|3z z&S3aKV-tU+CZ2vB>>|pohl5TnmLG=DaO&y%Sz9w+UgY7^U*rDbb&FLIzyAOoWaHE` zq}Zpe!X#z~2eCCUm5RW$>tJP^#MxqQ&xyqB?4>ZdM}iIeu=&JB_fgo`TomNOb51L~ z7)(K5h3Qw!r^Ir9g4?VqhTB|;SD3@aJmb7U;=4vNi0k_=2iuFl=iItApM$BIXl1ZH zDnC}6M~rE-!W@0hbIof6)*8oPu<-qhZB+dWpwoB;D~qpAF*t|7C39LDV zL6hikC)i!wIw>ohhOP473>$+NZy$|>{}^ABubHMKdGPmOTuFI~D`D*mShJg{Ft=au zv_Jk&RD$zMRKnO<3e(^vFQ2=;gm~c`1+;w>?5k#L`V;6}Ae}GgEA&4v&3>v&H)xd{ zGyvIh!)(GhtuilC#QPNCbM41+nUnN4(kWh|FfMWA>^3ia z#iL0k{%!MO4PkT~_isFwo{^~CH zeX11{p0=}jbxb#|VY*@5hdi~zs$&vUVtFU9mM6~44||R3ZoXTgm%QdC+WH#l1Rhhs zJFj^-eFM-8A);YDjj+W{Y!*K0zs8W;uj}hXVoF%6?YN0zBc+F)Uh)iEcT-JEDj+4_F$(^G_vkuxV?oAxlJXu{^b9%7bN@qj-g1l zQ~xU$;lNg69AKr#bR$dm1G@idFc3b=DB||vv8afL28|YTbQ-H2QLOfk+}wQ}k!0_= zMwVW3>?dOSJRPy-iOEqslvh1@^b z&9)GmvRudRcy01jS6XG=z-F(Itl5n_aqwG1HPsf9ZS&TI8xG{Ka*)(se?^2zym7No z<@mV+JDe%8-%5v+SHEW-PsEgslK9F##4w@BBQkq4k+kBhRUVIDZ=uH;;-ZsLb~%yT zwu?{?OI>lxHN>#BR6^wXT||BgLlZslI2hj-c~}~VOz#le4>lf0l-R`GX{y zcrg*{1p`TkCALapI|%#kC}EX9>`rC8!Tx$&qPEiSLOR`IjUX^$771uKuiXv^R`K_`~Ssza`tt-$DmhCgXoLlW}`D72%-2 zg}ze1M%c&iB^DEC!5PWVL-3g)I~&(KE_D^X>Z?KwXD6PL)%lGSwq~$}zT@f@3@`?7 zHrL~(6VY#jQDrZ&m77{wd+{!q9pBjm#X4=&El`?h*Af`ruHX9`xo8cvOsEx;x=LB z40qmeMiN3`^rRw^9~WlWm)scwXRvF_DT?D11pA!UVs7`s`GFtAg4 z|Cb#fW}%OrQ;298 zBlcE++9RA8B%-*2C6soasa$py>s`x6gF0dB!dNakBS_MbSJ4Y&Eu4B{MVKj%6MqWf zB)+T2bJ7b$tem1G`~X=oRpN*jSqa3dsXF4!-H-q=Bb@DRA@(qi%5#26?a3b@?kejU z4t7hjWMjiQ<}M`c_-swXOB5F(uH2FK9ub?qP-C6Mib`DNEh>X3SuBZ}l`V7RL&BQkBQ+k%U>YB(|-p#aC?=+d+N8Z(u~a@xh4HaFdjN4^<>* zss;=Fw-UyORH#XR8Cv7UQp`R}V8FP@uCkW|!$e!ZP{YBx=l-EM&Q=XIVY^>S_*Q4P zsaqZ9mwqp??4VEwG5i?U&Qc&(`44=PqHBy(AV?lMOeW5~?4=KJ}Jt;*?OsxbHs0AQtW~#hT0r zEidwGa<+?-QPAR=C}^1=C5c&~hFM6hT1bCOB}wKkGNv${$XVeUSw>_n4mF&~%Oi;q zRi%Vu1ow^52w-QcNvvN47jrCOf7O=Qo(P_8Akwo04P)`wX zR{kI`xzyO#nb)xb$;Q2>9h*y;BT?+u#aj)PUbcl~T|Q>h>vzJm`D2*g-&zuRJ40Pm zCz|pxd^lTliLnHT@1#OLARBWBDf@nJsIBOmV|1GQy@mwuch+PLJEDtgQ?+!JvH8?CgScmji4oZ>>sGI#`RET1E))3`XBi4I;%3L z5p&OwjqWp~$dP(nz^nCOZ}=>U&G{2*GwNH1u=jS)YCB=$ZM54*|2ftdHBYJ}*5?>D z>N&EIRUcWHy+k9*inH}O(@}pJve0U&6p06{#?D3!kdNvqlDPd(D1Hz11)5Tc9qC|2 zqxP0S0m`sb^n~=jSuXX?y$&^uPujBmt6O(V>1gTzAY!VG5uLL9>Mai{JkP0Ud=Y|KsDwlVaZZ6+c> z6g0MA_--us0;t;*|2*!utC+Ig!)smRyOVXhG!+VyZ^+D1ywXz4;1x_@n5}+yqjNMQ zQVx(rq&iA?H2Wpx9!7{l%u9?%dKVuiL4R=@|AG1SRdCIStC_mtkCM1!w%{zCnadDg z)lkf_KWQRPqJ9M)#k*94frDgV&VYtRqS++@hXMKxAE$L~Il8w<8EUcwHyFrwLoRKtz+fI--B8 zFoTyx#z5lQADTpYF+GMIB%vKjnHhspj=wLGn^|g!-1cF(1uur{V0wE)HGPR6olry! zN7$2_!up{{8d+XUX>D;;tEyIn-I{#}$le&AE!qs1e~PDwR2qs-%{aCAW-t>}OxPzB z6?!3SkD4KC178x>K-6l^wVB?}kgLuA5L;Cg#NkKUZubkb6YH9D*|s-_z3*OYtV#X4 zHn;_dg0~vs%K1f-3y3IJN+k8N;CJylhm{eLgTinMQ`&n;Y_N@ybrYRi^0Z_${%5n$ z*2v<=Fo%cPiu{(Gp&KN!!9gc-HZjamM7CmfH(iVq+Y06V$yq1zrQ^fG)inKEBhAjZJd%n8qMVpADa==87ZdB}t0b2bR#tSL zj_9E@zx#CL$hi%26z*pf@e_U9aHid7Kx8V5VD7=y##Uy88Fsb{+8~_;!A1!$5!03j zn*8}NGO{hwxgVw%p{xWgKn)#k%ZZ(83oC6gQL&~oJ~D<1nu=itJtkZ_+QZ#s&|)RUK__lFq8y0S`7ZeMF z9TXcPcBLb>SR?9-@!9i#n%H7bqOtI1=iBe{?e(4W=bU>y&TnRRc6N8Re!E{4IJH)X z*4Kn7_2{c81lr=bM4xPSl++3JWgQmU z>XsN4%lX5+W+R_#G%9$CV;8-G@zK=v)>NW9*F~c{uMSY5b(I*Uc(Z!SWLw}Ehsg%U zNOnp~NiY69_dG_Oez^T6Znyj4=!bEyjiCT~BnCC~PLOFIW5~~Ty^&@zOs099ak5sD z`* zP^o8MRJwJ#3=Hl|Gq!nsk@@)ynRWsHgUnAEtq{jSEDJy0bHaC3VEPq?BK6$_Z zuhlXz@NcK)&^(n^ccWqv$lcC>u1cAEyh)gtisgL*TU3+~9m{qyMmM-)=Io&usIzod z4|j5Kh{9P+VD>=CShfSviY`ei)#Q!=1;IgCut8qmi9+Yxp<_UD&!rkGc)S z<%^@khpQiK)8L?s|FP90%L-@Bh_Oj2Nh6a|goP?YF+9Po5(}*W-$cnuwHl|PFEN_ak4a$+lN=a`&DNJMKFmg|H{Rjx;+3K>7oIE!wDTQ^u(390e&0^LPM z)L(mjs1kO?OZXj&MpKJ`;u!X?nCbsi!ozsU&gK7QY+25heO=v28opVzGN(T~P1sAHFg=4dxL%HI_ILIHM$fVCIN}jkM^$|)&^Zl5 zLvA|TLF5_dMymE^s&UEc82Z!f75BbuiErkt*JvCBxtJ*=CtR&%F`Vb_$|9b-#Fx%Z zU{HvQ86*xT-;GvSdMvGHaZnt1PC?r(t4Etavk}yZbsWLf$7ZY1Q6pFjz)vmndxcxx zDECgluJ$4R#P8X?SCULis2m~DwK`XO21@Sz`hPwgSzqB#oIagh#o2GYbrx1PlHLg} zpPa+_JU?RKO&cj$GE)NfUmuU<3Gs!Ve>jb@@t&@3Hw|2oR z{L|!k&eZ0o&#X*EwPvW+Nl+WP0is9Yk&>~=5?xNqxL?JWtZI*(?DJb!f&1x3nY$uc zqI;f>#0E(BCza27in`e~7NR?%^BH$LNH%uC6?!xWD_@$ai@MXAyM?i^{lloH0x)cw zENj=?#T05sQ8T|ginrKCJM^H1QVZ(L1bM4MJxf!VF`DM3*`v8U9*HKy&X1=3&qoUV zn>~;?2B+a}V`!`+jp6){_c1!#LR_Pev|rZ!9;XS1AMS;qF+f+3yv4n{qw989spH z(pZrkG>%JtQ>o`LfpJdt#Zjej+^J?9bSYB`7g}1wNFAZuILVR?3>KushF|;hMMXFk zbR<>{ajm-aIOBGP8y)Ml`*;YM`|1>9!7aj68ge>%324x1J=zFHr&1QVa^T@$26u9K?f)mm-$(SPYuRn6vr=>}LN=Z%dompstdAW!`@#M$d z+In_e>IZoAu3dUId1Zfko?^rXQ#-h7WmU_FREkHL} zvI>a)qO14lq{&J3TXAv_2x;I`>WfA>qLalc^gN6tA!CAM2Eh{~c5P1g30!S*XFbS< z&G`yFK2lFN#@hqQv9q^fp3EmoZ0=T|Th&$O&`zY;L$8TkV-~*Li-v;NBndxj-r*uI z39`CS#2Wtd>ewB4Y;uxM1A{BX&t$6P9!#WeIM3Qu-S7_`(z7sQdrjg=Zv2hWTS59r z*JkEJ$0kjjSnnOkQ@BPpfJ>Z9SX_UhFj%*e8N8>v;td%SsHt`Tj;En*qX+SZ^Y7%^ z%|cnxgOKa5vE5dp^F4*Yx3jHY!~{;vbk)bR4~o*?tQiO1!amzo-!mml<|!7~GO#`5 zO(tLLK|0mTq~H#awBF4LGBU`-j|%XAmL0)yio`y`NSwl%kF_M44_x0XHAkRE0pi*a zpbhvRR|&K#N(gi6Zd=%e~KXEm+heJo$H%;mQc{);& zHI1vVbtHftWX)gLK|M-2?9vfx5;|Ie-E@lO%RdV6)-@a+HJxVOwSzmVrSHEX*c$4P z4z<(C+1ID@RBQew)K_Wb8A-_0E1k!V)$h6L)O5L@{xC2=YTI|uLXRqqCg!rK#s>4J z{1xaVOLy7O(sXL|=hAs9n`=36IMBTvC8Y$SU(&mp{u8U+8T^U;?SRW%CT<2b+e;zt zc#M0764kD_%j3-A84^4ERXc+xHRozQU=F8oe1T}knH){K#?knhG)vPdz=7-aVkS^E zQ({+Z-|p(J_T;OZYM%D9DB-TNxaz<=9PRd{yU;MRk%K}_;RCp&!;n(h)7=6*Jg6Xw zW8KxE;rMGkOEVwC_}IAUOi;|wn*&y($II~Nrg$FzEzoWMz(nmJdw*vu7N+KvRs^m2 z`PN=`$G<72FM2oQqs+a2sCyH*C?8Ud8i5{uHk*5Rn2`r0J#lvfQ#{+pitD?$04cVr z$a*ezeiPpr88nSeJvn=N2G#3D8Qd>pbp&|BjSO1Ld!hgXGC9y$M?;*RIxdkkpMXI1 z!q*-J9Ji1_ADB9oB0-Dqxzy;T1lZzTinLuaN&zDG)&ot=M5jWH!sUG$pOOF4!|s#7 z+ai-Epu0mH!T>%VK`PthFCimSVvRRysE4{lhkay#8+^zlb1#nZP*xHeD(>n|al&;tWj0|(uXpTi2PFV8i5xcW6 z>kLZ@lx)4HPdV+V(cGvtoUPgvl8F5$!DRQ~0O^ zAvrH@A+P2oHyNE>)R7nCrLN|Op1`Sv5__Zag~EGE3pLB7mT+yAmvB91$|XTEbF9p^ z>zY?n$h+hv6vvW9JmEDH^<3sqyGZWD6!5on88lx^Yw7Nbxljk4Oix%WZ=gQ$!i%;R z^MT*i`7&6%SU!hfwlO(8d+zHcb8vfR74A}sv*EUj^xRE8@#V!hKgspa;{#rqX*B(@ zxrn{uzq4e~$FIDEGa%0!c&k0wXrs)wVu?H`yOLWH(W!RbAXnU}+||yoT+4RJ+&=c+ zO??f}^an{#d9UIgzslVWDPXp<=_+{}t%++$s6iuw*$6TQ)7{b_IuUpyvi}+!R<& zzJH{k#b0pLIh*G0;n`eF>cfFa*|f#g{-n1$xCJC?p*UM&k3QW{M8Ze{ZE0B39x7iY6zo`6@Pk%MxV*NS^3%SV zR&eKbk&oWaV5+D1ZJ5>)3;Yef^Cvc`riFTuW>A7!S=E}>eQ@~S`Iir@z{s48eSkP{ z8*)T1!p`tn=kS~bpO)$Hw5zr$#OIKO;}rDT&3aT=U#&(C8o*Tk4!`8kYLq>2QWv~V zW+Sd)E#ILvfkdED_zVT~!tdEQ*c?LE%f|+SW_OTo{xSLcJDA%Ok%pym7spR}5!+m{ z@Wn>0ItUu$B%iaG!O_iFMn0RgjbLW3yq;%Uy*|9lJ(uW}jWil-JgDM@g{Axz=+@#Q zfL^#ZSE5&i9wgi%jY6^q2n5=-rua@`M=v;584?HK=Mlk zyASu(vo?pswUQ--t)cw2UCWg%iW525hesI-IA??k+<8tb945M@aM_Jkak;Le$zJF> zuK$}f8BBPE{+fnVHV44Z|0RbBcTQNEYcyT(42QSkEc~}X_i&adVg@N7HD_|H);=gN z_%IN|=P(?KV1U#A73jXs(#u(Zw4R*SZ9TW+w>%lFYvj`c@B1Whg926`)=M@8k4;*9 z;08+;JyGoFqpk+WD^z@)H&8H)-oR6+JgEZjy88&u+Up}II(bgyH3pA$au(CNxkMyo z1l6Ts7kSvqaLwcIe=)gE5WSH~F<~Q5``}$h4?>Ge6w;ojjMNblH{qs?zP2JqZLDC* zeLM`q`DjUT-}edO54}%}Scct{3Hn(q8(aJ$U=w~uadZ$6V@VX4kWusLB9^HN3G$GTs99CbW}_|Y6_nDbi8n1jgLCSor+WQ zw9cjFh?)zS*4}(RZM>T73zhk_mUc6rn-#sp7ouPI2z$?Vo4HHh=RxFV;*VEI_6H$j zv*gBt;hMzE(1!oYECn1^^x5VSDm!OQ{-d&9B5 zItfl_;O_^&Zt%4Shg{!A&@+~H)-G)jO!+m0c%0NzotUk>cvf`rgQ~5V!6jQMgXxWwqhv+aYe23vNBa@lht)D~_fM z7ZAIxVN{*Hjhl37r5{{+<11XId|m-R-%g+nG}%tWj<(x*@vh$wS=(_Pe(-k6$5@4a zzLe3~b$HbZNplQ-*-rD76hA+8=*hgm09)w3gLH2U^J@($J9y>`Z7=V_I=u78Mf{yS z;lP{#7`&5)YKwMqADw{!*u9gMv1*Y+xbHB)uTdg zS&Q|7(84It6>R0(;r8_mRHtz20l^M1dl$(`D+RcZcM$B|MGg1Gsz6+k+Qpq<>=Oj< zchMq?ZxK)GsxQ&n!Lyve7GRbl59ruV-mzHeMwc!c&89rVyf5?*0;7MU=A%VaPcACT zj3yCa4b65_uXLw*klOCoJA$A~&mbExHw!X`CF|t0x+Mj7UOFgi?K&LGTW>b3>B(ng z8FO!ODggU-^Bi<}Kpc1u68FTJ)9)u2h5Q_86R@~}Ml%IldZ%{A!T2R>)qI6lq` zYNU*wd!kyZT~03;=*P5Zh6H!k4jo?9*FmGn{oF7d{QU|N_M$C!_mC~mQ2`FW3Uo{G z;q}D5xOM2gmpq@8D>!(vKf$z1bvC9bb>v)Q==4`2NTc=9QMgE z_t4m)kXOZPUBALjfH#;6F%UHgsb@EVhv-sP0&n)khEF`Zf7AbV(US0b8DmJz4{9uU zD>6R_>+C*WXZuDNFgK@J2Zkc={oLdZk--qNpXz%|8Oko-&t*q^O8`CtQ5Mt?%J0V}ilDHb{6H-24Df$qItO?*J{|3{Yr^HE5t)uoI-r zkn4PhV0AcreGJYVklb03Tkifc{QaU+y`JKP#G<{C!Q6GtOUC#~MDKEtr+vI_2vlbV zw}teBR3{cA04;`gZunV=4M%D-f}23?K{DgIB3Erss53YmBEbC+S7`1S0_O9A?O_Z8 ztkl|I2!1xnmXbfdO6Q< z?QczZ2rN7-u}QGQo?vyH3G5yMUPmNHc!!D*ZgPZYdHWOs*g=+kMj`5AI<+2V9U)ys z$V`njw-|mrLOsSiMgC2B2*k7t#ka>&Lz+TF32Fal2^VPG1d>W|djdh+8Z04iuT{XT zo-$aA1Wf*Sg>JS)Pd78{%_B>><`}$Gvy`k`rjV@5U|%VnG=0%4RGky!M+~q5hp5no zU|S{^tzXgokYSU1KG}d%;v!s0#VLvYuRwRvG7K(G33Y*SoL$rBc94ntE8PVwr~3m9 zi*>g%vA2iPGP$|?qV~oCYY*n)u*Rm9q4u&sVs8hZmD6x{*0xam9JZXd(Un~}TC^>+ z3B;F^*X$1=%PfV(KROH+mrJbF%F203CiliqQp-YZ;cYn;f@uW@;=d)p0sdJbXZ=E- z0IB>pQ{O_|b%{+g4A-#(!{EmX$pzL|ke*$No^P@U@Wo5ID|sm#DTDEqc!Z!!C9y^+ z^sn&)j7nPZ*`Scpk1>)JWOWD=wz7U#7^;8OGc*P7D(Z(bfYq!4u!2d8yH~(YFj1_TJ%FEsT3t4Vhb}X!+p} znf|;+qTRybsO7qS`;T+6YnQNqM9Xfx*K>bjSky7@nzfB0Ap3n-OV~X+T-Yo5B_mv2 zq<`Af^WlT#$Efn`SrOhk=QwXvC%hsc<^)biT#r*K*ZQHhOblL7Qx?cDF#y#i0^(!JWGID0D%-ADy z&HXz63H&qS9T;9w1{4ei=pc94yi(kVo{`A}4~aO(trjVXE8;UnHO0 z7Ub*QfH!AIDBrTV|^^3*~@0Q((X$_n4eBHSbSof4KBsWGvB@Gs)R%7jm zTuj-g+~SI4QUiB7!@AP0s_*JxM9a1l5W7@+jty^f(1}pCp((6~uSTU#5bfGVLvo5T zP9?ZrSl`U{Q}GKS3;1_a`90C+g!rbXz1o?LUzmS66UO(i$R|7}@Gp%2bZiIV{;yNw8wl4w zavwNFs$e27#Q%)hpG5IzOv)4=UPQS6E|>qrXayDi7b*K+F))!Lkq34NF16{s+{o97Q596lF>+7|j1zZT_U^ z0YdDbj)IVae`Ei3?gghzWh?`SK=?Niz{P(u_y$7p*ADwXQT`?w7>es}PZSH#L_sJP zO=XCytvANvy5$N#) zbqx1KgbmIX6lpO(ZM-FT>)+N7hUqB6MKBVYC>g+;^ z`U0+1xC!CQ<(pl@kQmJ(;}7b$_^q9b`^>iI5@Jd3fPIrtiP03Y=U-e_>B7iz=X zQk-n1U9S=0BId=5#NhVyGrRb~qUj|jWwgZlhT^}g1PW;Q&p!JNnSLt<4g|ylliJS% zjF-wE#EaZGgD4A*_Ge*OW>5-Hvu;`-WjueL?4)mzf@&vlo#Y(-oPGW}xh>|%^$iF= ze}T@{l@-&=2W<4z@a;*0$ywL|G&nmbFrb(1&*LtNn2xUCuMn5Th3EO2b-151^rXn` zex;wujXaf8eg5J4!s2cb)T9RI=*f4GDrn_5xKEmxsR z%r}J|wdM#VnX3sthOU2=?U()Gkctiqr!9G7xR75yXOy9pB1AE^h>9WA9!6EVJ`U+Z z!yFIL0hGWff~%dWa;<|}&*WKG1O%El@zj%KUGl}D;0YU-3r!+P9mb^1^EIIHJN$?^ zg3uFwy!~#R$CB^PI8AM1JyDjw$xEq3hG8~gFB97F)ExX~hxna)aQ^Lxcqkd`QBx%K zEZA}~Jx@>wh9lC+Ltt|CC-a$NPZg?Jr9ETMBGuNsA-$Fypi zomArXS(;pfg8O<6D0SvYl^wJgyvX&p!6?x6@nFM3K?ulGDIxu)DouYf8jzpN zriBcM68)$)ZjlwbF7)UZVk7SFg!hVLM7qleyT29Wf_XaMO}&9e43UVRdg37>Yx_arE4WYgRzUlP0(eoNZJ;#N zFJ&lwQBWZgtQ}_pMD&=?t)kxsShk9$#PtBXK$`Zq9uj`ws4Q;=t8B7sA&BrG2C8_q z(bOCU`%S0IHBs{=M?_#KmG(hVWE6}M>|Iyy*~3zBlk$HJ<`K*z?v^?ldwZ?mrT(mP zETG%^7OR=ob$E5;o*V`+2Fxj(S=6a%&}N7l60JmLZ{m21+_C=}0cP1SGlmsrfx(}= z#L#3}Oo1RC9niBHE6+2)dBAzACQZPB6iyiWz5uc?akH0cy?!SE@yYOsT*{R-Fc$q7 z1Trmv^8k4G7M^e#7o3>xmIHF`p7V(lbN1kYTOQF~V!5zoG6L@<0$6*06S)gsCx!IR zvf3G9mT8Tt_cWkP=SLRYtW$C8Z&uWuxr~Op%%{e6Rr;lagx)0e^muXk1I`Ag?dPap z(X}3Bz3!f0M9^<`C4MVnkgh#He>}K^%A#Ikc7Nf=%APQ2+!QgTXQ&y5heaQ@)p@ z3F~pM77sPIH;w$jF$*K<0x37PL6r{wKK)I%+Zu)4&MB6LQ8g7#pOCq0ga$ZdtBk073EU3)>w?V9(--V_?G~zpYJSP-`36;Dy)M@Y*G6Gz z+`v`<1QJ)>e2y5@%d{ub(>)s-p| zI4spDuxDbJSlJ~c&DCgNKb$Y`Xi;`1H$t1+D;Da!gZ1JcP|awvzPS-36FkFVR;J`W zG$^%1=>vcT8UG$ybqDlws#WT+$7X_W(0qxg^b4t1C#B0m*~<9Jz-*d;{Y74F13z8~ zODO!behJ*lOUv*^J(ZPa#;pfO!kIhR4oi}o@lqej#dLJc4qzKoUrYtE+;!3!L|z%K%_EF<5d!Sy~6y%qN>SBnu#rG?P1^ z94nSb$BuNvOc(UZ{y1tA45k+hGA0-NQ}}y{kOT6oBf+!LP+YBb(|v9)A*1j}Y56WL zzb-3kFL0BnOMO#tptb7Ql(a*&7)>T0`XH;fai#{mwho*Pu;({uMCuYB?_c7QWMD4h zV{KtG)%c-9}sj4<)WkN0&H{CIt?zGyF!3YziX&!d#biB^uXm6n;=zI>6oi!k|v|H z444|+f@7kNj>ozYAv%Zc^1ED4^UA7m==`W8MH)d5GTjBB$(q1Cc*jZWu-l9Bm=-F6 zs(hRJ!QvHr4dN3*ZudDiO^N}F#0$BJu)~*J3!zK*iu&PRoXz16nyi^~P9glO=x9KA z5_${5P18_T(}*uQ-2<&zD12TAvii^HTblx}j)}c}^rlUBC%TZND2)n+RZR502H1Hq za6?#k=rQTwGxQ90;cXQY4;)Ip3nkD}*~`(xTRH zjaA`~nnP_EL!vL6;#LOljF0e2sx*Kdz17IRC=SaIZT)E^ys~FBYoWLn?{ItjxgqzE z47;vPLtff$Jh^^6^{*MCj>=v82O52{Y)GPGq-~q^HX#}=AufeQ-;fO?K2Aw3_ve@P zruJ`ro1&eRsWo8;H==7hf=7&wh_XCD?G7PiVy(?7OIF+Vjc$oMQeK!DC(3|m{Co)^ z4yZ-G&`0dDtCG?BnL=GxCaZ%bA^q&FDQ+^-XsGNFC#H!arElW%>BWPV3c8pm@^T@; zzM44SVI|j+W4F9`1f$X9h7QxI6xnvekx1mOSbc{|>d`9AW^@km^W$=|8{XMR?HPP= zkL%n4QyW+Ofr8`0mbNrrIsAZmq`C9%phLdPqdbn&tKl!P($O!>hnCg%%?sa0FU8f4 zpq`z)-quqo+QrczuenmUQb-@nIe8U*eyLI(z3kl=(beN}L{)vo^HLlA11cb|IVd@w<_wVf z)n{il=2>8ZF@SYL=N7Gq7z&LZe9F>N7NNZS?wx6TQ*wr}`jr4+Ua7Gpq>Y+K?y#@o z$Snt)MCC#$2jTr)AacK>NhFzuSEAY25b{lSJWR3^0Dm{G#lJ zK>Ct~H9W1X4q^rLPNY|TEraY4y&WU48GN3<#8r}mEC}zYBtdG-0w*OAPaQ83E^Lm* zM+iu#E>hrXbtL@G)|beHMEhj-g)ErPmj=7@AhM;(dg^wKNTAX3>XBzSd%z}a0>k#n z^)n<{_Ua^_q{q%D$RXV3lgwaG@S*GT+5pyEf5@fw*Qfx%^d~pVpMIqMo-FZzUxOW) zsq&Bf^8L|3L!)}(l$$R8^c|9XwkUTKeNpx;B*UOw-355;H#7LmmjAGF=8pay3>G?g zD&l;tJk(WW;7|B@0$qhNei*Wwu*E`vb&XH|uQng`jtj>)WId&iY+6475sfudc&q+3)8)f$Xx z3GPWNWg)p3Y=4qQW(a#{aRQsn7zlX9pYX-1UZnDU%II6FYLBD!hZ`oChFo|ZwOu&& zxDgZvgE2{%ABeyg9K0I$`x%rpM z$(}M8{qU~x{Zso~YrVFUlbwVu;YV9=<}%hW5kg7cgB5*q--B(4F1#cB=3ZCUo}Xfh zHuuD@I}agmYrnLgOj9zK+l9I%HCQZ|Dsn&}$5^&M!$aGThpAYj$SvtDYf*&Rlh3{Z zlGWF>KQZ=Izk>Fg99@>7RuPo@uGMnXmTjQl6FOzeMVfiepqFzB49cETUy{{VrUmVP zxGhX@TbX4yHOOmE2I(B7ejAiI8uLU~ok(s4q*V{sx{j9;!M8}pixD$6>kVpxY|sEWilo!(cW4SKEL*c>|VX* z16AzQR8Gor6PL7{j1TmcLd>KCHhaKFg^E4BS1&n1sP8TSHmC6eKyQXbkvITY=6feu8h=%`-o!mBtzNoH(@JMPtZR*VI5NA= zn4`*48ULA(@mUe?Ss8Q#9=8NzN2afAPp(nZoTfnOfnJ+HfDD)dLZ&aN3OeO$k=avUx85gme#Zjd*n zgBbeQX5|cf)JSqc{lEgTX+K&@1tbx+2Fic9YyWV+mDgF25E5};)o9`H)Gu2-jBPKQQ_)^kW*X|O zsG?|xX$OpYMqRva&Ym%g}f!g?K$A|p-7JYm5g@|SafiC^a-kIa>Xk(v?AU&BY<6sk`*by8l7qN`)xzwb5{n@PPEZx;_GWqm6feIgC|z zsFmaAI491@d?XeL5CCR9x~*zuD9+IqONdHXRZ z4^lm@=hg^2p8d4k<`%>2C|R0esak{DAis;MOoLjlTlp+jgIX)oujEG@Px5!;r^zJy z%$Xiq^mD34J}6Y}o6NTUn_Na+&{&SUMW9Fpk_~=P2Z5kp0G8lz+!AX1{HWK)ri|QT zIbLbVSFSf80fo`JYo^~>%)|6DdYfpc>xi2C+%*;7bh*!hdj@J|cZz8`S@kmJk za=f*ruf#mnity{|-0gFRs0Wnmsr!c(Te22gf3^!XKR|})Dk{ud+6(LBfq+0>2l%GgyUG^_U1_PWz&YD&)K zdh7o8xY~yk#ltnc6>jL9teP1shF01^*g8SOi)@;#dZ5Rns%P&nU!ed01$YaRX8z5e zkAWTFe?NRQZqk5)BK^7g9``{Npn+H`DS-z9ib=etrw6ENWdEM&x}G|O2&EE{k~|eu zxlfC4(!$oJl#~RG&Xy%jjF(qBGd(6xXHJLy1*Cn2f$p}gr{k7etv#fDhioV9AwrvU z&flcI*>Ta+dOfJ;)>>0}-STc3@RIG~Kn_YOd6MdV(|&RX@cX*S$~)D}*)2oqnW#7?ZV;jB)s)Y)$5X^$gwV4Gd! z*#g*H9!0rLlE}B9pe+xMqgbuGW2sL;WJu1ckm+I&ab5$497m9DC7j1a#kod3Di_@{ z@26pTxLvoP2;9&{7os2B5@^hm1FGd?=Z#D}s1l^U-JpAVEFSF{$c8(2cQbD}m|Ook zitKv$#K2o_OU7qqyMgzv$I@ZlBA@eF4g|Q_4(njf>hdSgkTX*s7{Tj-H2;EmmsvOV z+B)URVkM6}(^Us0`hHamVaHI+LPc^Tj(B0hn2Db|hIt^~Vp$X@?Qok{&b)Ry?0%V4 z|5MDA0cqfv&v``0Z7To@H6<;Lv4M)b!?{9B1$Jnd*`yIiNj-Jpk)6uwnGEFEl@35H zSQCAQ*+(c-kj4TDp29(w7G<|~Htf7rP7>P*RuS2LT`$^0ldG_0Q&PGa(+XP!Wr2vo zTfPg7Dk4No?I3=by)fYm;wHDPaZfFT$qtcuyy5^o(Ja-@PPQ6a+vF~~`*z&23$%wX zdlW6)Qja-x==>g({@_QF29;65)&XD=PsIr$Y5(vnI34C2vs`1SGu3x?DBvtH!PO!* z=XFYRT}DIT2s48sH#J8b%jw?!!Pk{i&MKaA2;y^u%Y`&a0WR!NK)6(~342KEEmYH7 zYPy*q9XBT>gyC|?Om)1Kz=m)~w9sX#>Q{lMlIB|V|qF+|h;rV&5W?7$EcjrpBqxz?P;u9paifj>Ho zl~)pqj#pt+0!_IXVLX@_onY}!r(sbD{Xlu(=JZAH1K)(gV!N{9lYafHE>?mRl{+zA zw?E$Uckt9c1-QC;1~+5MEFR!TfJuZk=Lo#fOp2pHIvyA=bpg-94|}^1qks%Crt!R@ z#^b5vC~*HL7lC~^IumAImGM?VQ{bblDypX7^iiFR8W2TT3wIQQm0Pla=3ZU@SPIkV z)M)JC%@|?kT=megTY>Te(i~%6nBu)aF@k^)YE`FKg7dTyZ@MJOU)ccZ2*JT+PoW8^ zZyh6lj!MP4<#kZCJ7m?|g$Gzxj8xP=cuWsFzt0fb!;A#{NRY#B$b+*+mx3-4WpCDt znH{G=Urc}Ub3Ym3;PCLiqj>2E70Bj6RE<7=fygmoZy4D#*36;9ec-(u`4t>QoHw9Z zlOzS+I3h$sfU82nlVc9D+@#2lga9}kO^{#3}sfU}G{grn>aI|w? zFrd_UkxNF{GmDa9+8#d!NG9g-lwtYG>0-*^8MAAplsf+D`hf)aqIbLid7?Ai0ok|u z2}MJfftlKzl!#z8-b1Q^q;l!*U?cLk53F%lpIVFVZ zvcnOIBFIq@zwI3$PM`lFHj9y2jLy_vUWQqtD_bh2hD_JHlFz7Fh-w zx;RyBVY0|gpVd?RDJS&pK^YIgx@9;8h-$$K`eHIw8Iwp7lP_5%^nj%SK3h0p5yTMTo z*gtI@f1w5tK-;d4xp3|Nl74=@K3G%Uc_kWZMRYFyxLTayJa9KyHQd^}z>ntf<$*k) z|Jk>za{FG9(|_N#-Ajz&@uD%ZFAuPzy}n-^-p>HI(Oy46nmFZ7_NI7D|Lp7B?2Yw^ z>y7ZJ?In8xplRo-iZ|c&h^FhrXt(T0fo?8CShv9n!{R5Qf;yJE){c6lXY+b z_XfiIcsa6gPY<{FUKal>U|M?9l1|I$ADLriZ}tuNWv>{SC)*}J9GdE|xq_;BqBH1y z%i1!mVB)<6c&EDDZK&(@!1i4{6$s&*RaU;pBF$?D2(M`CR~{a1uAFfOi2uZXZPV1g zAX@;5eN!!E+4T{{UT2QUm#e{EU1yn*%jjP81-Zl4=<5zp;RTMniBT{y(OT1oQ3+b~ z4EwT&f%7b3y5v|>#)zA_D4Ped_wHi}a_w_^9)*bO5bKj88?_)1m`+-;ERD}prXRbF zEpw~_5c*qV^=Y%_;}caKYa($VTjDtKxtrj{?O!Msy@$;QdgP-c8uIN-7U3RLjn4PW zy_?_LQ1k@Gcw)5fatZ&8rK?JfDaf(FPVa#vLlYyprySF4P!p93O{@;GT`yaY6_IVv zRzA`0Y?nx@9z1tO?m|q`8TIU9(TQ8k@pd8r1UUX^*b#HkZ6c5IXe{1?Lutz`wAX%8 z|De{vmj>>xSP7Ir;aZAlrcd?BA~DhPHyA|zeOIm1B_IC%7Jov+5Vf*=d%U@unWgl$ zM{*J__Nm1Wl>Vz$f$5+Z%T1D$Hg(#{hZS3U_7D!w4a)uFiKJ2B_~2O~kDEHN^EVnl z;5RMHnn3Zn2UagM!I6El7T+|Q+nBU?TA@;kB7uYli`D0lGhB@9lvfLfF`@?C9$g!1 zXPrifcdBuSzK?WB=Fd>hScSz9YlvqipU^k=dH6=PLF(^Wzb?y2N(3fvIVmTDP+xH0 z@fF8Baz>ruH`#n%x;`X^ocA(|X){Z_07R_3UI_TH&g&<4;FT3TRJs_^@^yfDCV2H@ z0$Dxft@6nI6(<;$iniXsQaY?SrxwyG`3cVHjEJ??EJ2Yn`@t12gMG}J>SwYzBdus0D-)HK>;?`L=NG8!;q)G)||0*zZQ)lz)rMb zE4~{yVXfXtjIO7mj#5M!{Qdht7-f0uydl05ZP3qI=!JASx((LfYo@VCQ$KNGtDDox z$5+|TE^;2OvL4`NrA1$QsiIy`3U5R}1GXf0TXw^#yK$VFh;1`(>3lL3tWqE>f?@Xh*UV`hj-OA?Yx zh(=`m+Q=1bDeCKQEE{9n=?_F6ggDhtm3is;#r=k8(#+-#PBc-x9KS6xq}GwU{*${3$90KQg#VaR|OOjw|wkL zG!s0jLUgp;tZ=CvxDaH8!Ev0)_jknt<(o3Rr@5o?Bkup-MLAhUj6r#?~#7hU2C;iA17Tl0@_o}rQZeQRAv?D zSy*pq`#<4`K9^xuJ0@$kL%$kyH#Oa>3oo^o)!NBbe*g4bcD_=p0mO^7e6i}2gNte$Vv@TzoK>))EBERn}zSdUfoRGD7 z2E}>!anLy1EDyWt1GQ37#y|Vsy5<)^-z8b&8KHCPF?Pb>9Q3;eW>9@mbfcDC;ZlQj z_y&g5Nw*Cv6sIm|89+u;6VZ61f$Im`Zmw$=Np7@GPgu0aMr3ac_L6KQwo7(#=?1v^ zmVEn;HWWZ#EGbTKC}l`d3))rCg>Z+xt9Hm{$%ne~QN#vV%E)a#{#IQ{ntRAI?sdq| zZ0OqHo>L~L*%b}fyYKwMfA$Kgdk@3$mg{KpWhDl_Q@a2k1u)TdAT((#yRk4k^viOJ zf0(l-(g;*n+FelewfTeyQTky>;}Ml>fS{BJFenvsP(0z1=AG?v;l@OusPJvQ@{;k^O859U;`VSkAFfjla8=&pw^?$6W7`2}oIAWOH zBJ8q1?CIc1lOh)?s`oT7gaq>yH^*wz1i^=do4KJAP0!3Z?Agn336f9vpU1ylxV$5k zWgXnD3rxICLktTWXSy^rvi#)mxb`@ixy%K8KJoY~T{<^0>CV6wW-(->h{?^A9m;i{ zY7VSzY#spSFuC1_Y8YvazQrVO#ZGy{U2({6KA2;HMEr(_pgXdigaU!OfGWt|6CPptDoQj8-`VT`L+9i>~=#=U=9Vuqp7;1UTqO&23R?Z3cHHHa@jUYw8Oz~UX3m#G$YPcv@8Jxw`%)kcM2@Dq z+My^SS8H)zq_Qrm7tWYtia@lqsa%k0SN3F9bIxuM=~+V2iOHhETWk}WNE<~UKXpzN%N_hh5RD(`iUp@dv0vUw z{GFf+<(H4N1yjOrfz+x!mRL7w8zHA{TA2zL zkyq`?lq`p2LJv1v<0yG4Db>`^jfewvOoLti(Nu<_GCAfe3^cFb!5?P!bG7N#SgSTp#2|sOaAhCasn?DQY zGg5~y-y+TE1+<*%cj3r>KfeCxmVz-e=3;?Nvc##%3)2;n6?iV$i)URdy7mD8snYuC z1THRzfN!`vCBh9#O|P%kexR0AI2R00;nAGfsFLxjIP-M@-2i(&ja7!Xh~Olp0| z|HVu-jNpZB?8-g``zKaq0V(u7 zPt5lIII)8MBW{spwV1`q!CX74W`!`mhJV-z5u=fux#F@Pql zr|MCwV?QGHdd6~k#giDlvw=LvGVUst z%tqU*)h_DB-gPIV(^v|YzrQQ)JEyyu1MNJ9OB`$&`e7Ug_XQ|EMIz63UIF=wi3jxuOR*D~5sVd;u9LnkKHo(4q|3MMiW< z=Q5s-N{!M*_qtdNIu2M&Zc;Q!TK-YxjeT55LR39F3^MW(z!-~$J(v*Ff&{T8R@(Lj zxlWOqk|+6nFwI|zdfYS;puTe&Pu*xlWNB1kcztF_9L?gGg2Ja0uX8<@Wls`Z7fWrc zS`c|XQ}zQzG{argL(Il&@oolNq<|_%ngPZH!kTsp4Gq50)&#>mQK+dq8*Bm(CVO5~ zVtS*RhjOzZQZ8_y#FKbsYqOaxrHitoIdKeoacLXlY70qjT zfvwk%FEziFonsP4&B_6%W)Gnp5o4zp_lGv6$(~zS$u{M;8y1I6g{qJs4vCSA>=3k| zoln@Q5v6Z-!lI`*GmXwtsb695uew*!`rzDF9id|{SmO={DDqu2B=l^H!+nFYCFzD7 z7i2D>_7lb8N9Vj}e$fZ0Wg1oeGWYRXnHh5Tli}WIagl2pmE)E{56<;B9ww*bhQ`?@ zbgWXP-L~~YH+Zdxgg*~1ib1o4t|Up($B-M&nozARbzg6T!HyWemd~zPMMZUNjuGfD z%|bLg)Z|99XD-ifyx=}D7j-X_gMv$79_L*+c8AP1 z;=R6DF&_l4wP-E?uftCWgYRmayxSLhCB`tE+A3Ns&YNnJTtu<|#x*Fo#G^j4J?CZK zI@-h7GT&40=PI4&U4MbGFQwYhTsXL@FZ#Bj@!=!_cz@AtlkyI%-6$LWV0_wRpxYWB zcp9Jh5mUQI&&^#tE`)?<)sAW9X0_D!J2d8#v(IzB?`dnc&Y-yRd96(&QC(02XVa}> zcI1=$NP!CE^tpyI-_A{J(QdPu>PMTshfrqYcXSU%6({|>>}b0K>j}chT3pSS7DKf9 zMt4gd0RLCg>xC)&>;587x)g2-d8ByE+{JBL%+0=&Io|}w82j)05pfE|uaS{N&&Z62 z2X3McX`7ZpLObN>#JWXAbd?HQp0(4NM1!+B*jUyLGhUkmE8fJgle=e(o>}?Zp=oyg zKE#J7v`smx5pyVnYwEMewiy$WUgVp{(4LsL$PaOr8f+rhmu`oZ* zfX#~ntbhrnsorV>MRKg$`U}HH7Sx4$a63n=r^ht62x)e-SSLog9Kh%RzH8j;+|;%e zz>hc#(yF3q|3%Pcw^uy{Zbr$5(>qjq>P}*2vEL>WDN|q^1aiO1zSHl}ma1`Q9>j;i z$jzsaU87_=aLV^FGw{B|BV#7V+Pyu(ap%pEg3Xo^yY>R~C1G7tipE=sshG(M!{6it z2TE=hKmBk&Vni=Ln7?DgutI(iw3A^1(6F+p#+ouEUKg^zOqr%PD{UK(8LXbJLj5 zi=f$*m0@sn^ShQqSE3jlrC8tu`o(Iko`HrsfgEYaqS?l13K z-dvWIOiZXO!FpnoSY|v%JI?^3vacQbp+jUD3*~T zhuC6Of6zPM6%j|MSvqZNk)P!;HF#Q|@hAsP4uzLQ8QI_DTe0;hzpnN_>ts$ey zvdq$=qMoE*y=cd^aj+ynPH1o0wxlu*vcJl`gpLs#wO|^$P)l*8p$GxOR+0X2%J6H> zj%Rx|=q(^rtKWx8mf}%m4BA<7cL^GAC{Ts$X%7c#di5k{Md`VqE?{jyOK4p-vpV9M zell1&-Ni`7j-il3SjNK*0N3gco{zl{r(!0naFzrboId4qW^Y%ftkdnx2e%7e$YMBy zf%)c^NryK88`w;jO&gOA9K)0?O#uRM52EEACMq!2j#JP9J4#cKST# z;0ds@B6lNoB|?6gGD1O{^S)4Ytq(3LyF4#gY^kHq$cPB%FTxTv{z&ID3AYOo-oz-b zk8IhVw+jiXrVMmrY6%MyY-*(->a4+K<}k=(#O(pr0$=MJjFY+iR%XG2Paela{%os0? zdO3wUmHIA715E*u20q0fHRalx(YU_y*N}F#u@n{6+iTCY6YIR`v<-D-#e02A^P5tW z;siV-V{Y8^^tE$#?a&ENI1RN$(#w{e-E6+1N9xOq3s64*F)R`#{9u;;fTQY7e=DMa)zgoBL@Uf(TE8x8UM zHzyZ8eg!mvFs8RM>Tv*^!OSq;8tUE80K=i3u2yZ5?-1Vv;GOOD&mC#Yg$^bv5c<8v*nEEDHfDCXWX|>WI6^;dRH0Z)Qt5DLllWD zwsiquq4dT+;(1CK>%NQP>Ld}^t3ez>>VEgUC~3mr<%Igve%Ri4Fj}I{^Ov$JKD>kiaKm(4r_neb2NHP{z^n6Un zR8u`s#lLML72*S3f_(5cn8!o?es0D&)}C;K4jb;4=y;>J23B9 zFVaybDxwp8oYz*#x9{YmI(Oi_vOTX$M)*A=?I1yK(%_W|_W2ZgK@`~qJ6!91iII=f zm~L>z#L8(NXl5Arukm&vaPj6n;7%a^mAXiGXN}2H9hOr{w1*{(!W6c~KDa`Jo?r)n zK+|_h1TUpUhlZcZiGFFX=$VLK-{}p>Zx}IJvf*n&zQLtC8Mot4@->9XK4lE5$sI@7 zGMbajpNJLAie)Mg^E06MqLTCsa`FNkMKM^+bHiUkcb7RZYx&P`JoktYi(M#5>T=23 z!|u0a06#~Cof*tgpEG1d>>P`p?sbTR$YXmf?maQD=rf4v zLw!aNo-^l-Q>eru0E#^dp6=3ZI~odcfnOrg9(Y%U0M0MgoV&}H))79Z8S0pMdAYaF zl8VvWth{FGiwY1GFMKjlL}s+N7;J=t`5J7Yz^AYm$RFB_rgr(QPjL|74Etn&xX;PZ zPaIXz#;bGbw>t?NRsvo`z%?lDmIuYig$Q1k*teH19hZ>UO4nin0sESzR;-Wq&GgrA z2JDHljMF2FAHJc10y2}nxgfv&*-pa5Bc#KcPokD<7(jfu6T1B$A4^MCXfYcA`K`VE zo4tdZwdm%hR`$-Z1@ZOD6LMbwdC&0qR|I?2DEZ3EQ9#w%V4Jnc{Y`%JnNgRto59T} zz~veM{tmG-COl&WufPAx4CH`p;eDRaK-Og$Qv`_5c1iO;^GN}Qn%n`}PMnM212pNQ+W@Ia1lnI} z7O?d@gWSs5*S|5^C=V?V!XY3C<9iNpS;NQjMTL?4{sd)s+B@Fb@Wp)A=%W zszWj+a1^Lr3#W*XM3|wITuc6&sL9k-m|W-(_9g12 zw{SL-GU{c$93<0+^(tJ>z`1P~g zOt^M294Ql`GwV~E{ZJh@PA8%8Pna_Kbd4}5UfcIxlf_}@!WQj zmUIGk#EaU3$oPs=!T;Z zL)QU>UT}IR0VCGgly|b?DLtji(4*YfJl@x;mkWoOqI4E+QoMDx$t-Hq;&{@%gycwv zaMdtXcZj}$5vNbw7~W8Vdxgsb0GbH~zspdtE7Fi`!Z|7GVAQWvg_2nfDo*@|B#qWW5pYj}MV$u^wmUv&h6{^3<{b9i5-uGYo1nKTp>+ zp12|Ju~C9kRmT%`dO8qXBb_vxVtfpmur)_(tjobU&@9cBqi&%duV&PA%r}D|n$~sYC20>pV>$JuGsj>N^52o9b zXP_x!fMsHMv;;PY>N?b>D;x4y)yDkfX!A_;0iD7yTMWLj?T_>aC<%>K9{6){0;Xn2 zMfZZL6w0xaZ*H}SH2rW|c=jdS$0Mw}QrIh~#AKaK zt#cUplxuzRSFHoC*&ZOK_V378yb9+Qw&S)mj zavAp>$-JYgv?;a1(WK=B+f4O7nKuiwbjRw)?=#?*i!Y%65tv#j*iEo^gE0Rk=x#&1 z85F)ZigUyLhaqfwf42wwFYDIk1PpZlAK~O5a;?o83J4qNAF<@05#n`-X$0htA1CnN z&;BEpB<%vjruAY2kpQ$^kR(MBBeP)|(1_XWw_nNf83|#+H6hgfHmP zZ_cZ3&WFqQaYy0g2kTyOBpGxrW%^mfpk{;Wz6C3v7Uge~qeMUkeXNO6Zq!!rTZ0_g4UP3|ZX01G!bX#C2Jhr3k z1{Sl78qJRzg@H)1LXsd5KM~b%3!kQ-_0~TwU1?DAAzIz&AV+;bGQqq$c&+fwxPYc|! ztQwGgMy;8J(^O?urkvZl7)1O~eFaJAJw2fgc5d0?IPP4E`uuopmTMHZ!)A7+Q9pUq z8JR*=X7`&mb$TJO#BgG}I#6=LwVJ9-Px{+^HC@oOn+hn@b8TJJ7CAr^Ii=*V#gd+L~4^RuQv_zHf8kr~pH`b&pAA=ok5LdJUc^Xix zr`2e#**+gGSHp58i8-7EYhpE3j|_Z*|CsemfUxHZ$8t zlr*p6Ej1_iBP7$@qLm*}=y0p)^i97iwS4;ic2_YQ;^il6H;~Ji2TTfy&)(M?cjGz- ztY$lJSJW?FUWbl~0kxOaI5!()=H(&m{TNkKv_A_}j5~|Qq1-1N$Y9KS>OVtoBm+UY z_5v)ZD;=Zm*mw14(_XbvWB1OyvZz-XuY=iCk|71gP%#k|D#Lke=it%|t9FF5bXvifqS3KvzmaXv0Kg4XrnfTi@ZVCWi=$8WZwFn-v%&#EBMED#y;1CC?h(4dPEJKQb zDAMly6p4JD!(B-(M^{sr`P%RZ4fSQNhg&=)A5137S3u63ab2D4Z)Gel8@CgJXr0(~ zmuBSY*#%u8mLV-ySLWY#DCr(ht`(2`9m9(jVSkhnNm{TiEUA(IOt3+2TL+ob!|F~rT4#J$HRCUSsfk#!RzQ~Z4!H1kz z{kSrhj`F5gyx{89&UH>ItB&9_PvG&%>TQxVs5*N>cB`n{%wIvAv!^{@S9X5pRrYy* z<0Q!VDa(kRF80Mjs#2KmLSR((-isZ9>&t=?4bEjkXU*!#wp=*z5(%i{r*eeyQi3qh z)!99$#2jlso-wv#P3_X%wf1!ACDY78PV;0x+F8X=I~eFtvD|eqIS?(RpjlH_(rpE#wgsf=#I|a`Md+K-pRa!^d)L4IKv>%X-_yT zi{Fadf!6tuw1<4K%2G?-$X!mXbWb_2UYf1i{OKOUnMc8$v^e_dKUfJj1=KU`+dHA7 z_jv03Y+pFZuQ#OB6LD2Y(;I?RC&Pad9A55hpR!WkS)Z|7iVmQwrL!exOIh&Cn70}} z&}Jp&NFplN%+G_NE*2G0`dEJNL#HcGZuU!9(A!V;o20FszhJ9P;i4}$D8lO7^M0W_ z$Tm6olrLl@Y_qEkRsOzaMWhtpB*84(RQ5|2@5v*=#WxV9Qc7IR$POo={U#&K6k)fWv}(;x}6 zxXVeWFUOFA%Fyb@^%BblA3lrDR~5n|aWn8luanqz29R;>IH-7{-_Ttyk-rdsCg5DL z^yUDJBLT-aj~Kl=s}TnIQla&11mu&Rp-S;(px$ z?^hAUEr(;Qx6poHwBIL)GWU~boG71cT$ZT=_Mf-*YAL)Dh&z$@@U}v7o82r6$4clC zhELQ5@^~AxBY|vz5ESzFyk~;{L7#tM%XNZv|Ma&)I76Klpag>V4`^v~BLO=24x{?N zxTej39Z2ba2&jz*Xy9Mr@te{HqWf3cgg&7hp#I)rFbIi~#q6ps@EEf7d80?={!KB$I^TMSWDFdMZhbyMJ zD+3V&eD{Q+l;g=>kF`I+zR5-5O$%mrE1oDb0`u8*pU0Qkwz`KCfxd2FN9@>O{Ltdb zCAXGR9B{f7SCy3+?V;T>HhUg~#fN>pW(4NTt}%l9=7AY)Zj07)*n8jBX5SBa zDxK~`9b-&}cZcq?*}W0rYxC6p4dY%#O|5YN22Z53J5?SXeHtb>^z{dw1jiyww-jOz zS&zAMnVA*r$dv(02>g1Y2~xrtUwszQZLivlI$h7%DO?g!FHW4S^GHg_!I7;InZ!G} z{nf;mq&Sn04^9jNo6J$Gy6YJ%s;df3>$%#D z_OY4>%HI{mjy-R|#USB&%t||CopJ<>IdS#DqGGo&kpe`GB+HN=6SKM z&IL=h8}UYLIi=N9rc;eX8^X<;)}e=l1md;|JBf-2-pXOKyE^IiR6^I}WinY0509?R zskJMu#n65~?;g5$$O#?R=FYcJf79236t(7O|T)<^`w!K6|P#9_svDS*qSm(B^7AP<06Gbf|3!vQb9a$N z`H`CWj&&5v;zqqA&-o(zVgTgH z&Wq0W|8?{IQ>t|59Y5>Lk>xF5f)dgz)*DTU@q6hQ`=)X1) zNmEV?w{O!4Y}&0J5K|kjAy7QffBRO5d6E(L@2ai!U2lc{>07^30^!p7(|AGC`b~l0 z(jrs8%Q&bpkl+8Rez++RJjB22Lyip)>c3^Yt=%4I8tNa<;dcaITcjsYKjeQuq|`Jc zDEaLYa{o@{kb|;(_ond6Evdn|x1rb~>a0UoUqk z{h+C6qM+BAp}l76v>T-oQakK5)*}sYep`)4g%o;2F1maUYGz8kdXVd=!=Ql16DfEL zsNtzb)hftvwt!KqrPj(+4iZE;3w2QACV4G(q20hpPHa$mmm^`jD$*?t@rhL68m}?d zOSGXDPXbJwQjm00?mnY#4%dxx^*@Ct$a%WvWC4`gs=9+_qxLEbv7~54zxj77U}Mh} z7b56^Mrv6IN26@}3iqkK%ZmXtF^$Ec+!rsYMFJHybut>M2CuoGKgWomLqV78SptNbNg02#a%3P`N}UY(8wB#hHhBbN68MPJ z`ztbxa!P)y(?Nm$%rwxWVBUk{U}HLt)+_h#u~cn*Qx%ET+c+0F{U3mL0Dd*D+!jAO zMl>Dv;z*%IdX~&+tAHbz2Uw;;Y|Lzuo9ndlnQRVVrpcBscuTdJT5YJRBe*gLi`DN{ za9Yur1fiSIgE!!DsM`@X>Pc~ggtt^=wt?VXrDya}3(dt7(a>=Y8eMX~h>AAs4~i&l zhQLq?kCtagGm%^zGz_3yk!c1DS*+|hGcn{K$xC~SNghL!bJcY)!lE+JzY?#c4(e=@ zfwpGfGFTWx=O--R3r3v3*ggy$vEo^7KE-?_W~S3Z8#8E6p3rX#Qr|TyJ5Pd{%;!*b zx>qsNulQsVl<`^P zFNYH2lX?=(TScKbzA||RqBTB2#7y|Q^N$jRlc0^i@jLrWj#2Eb{~n>J8zR*7#V@Nr z#Ygs-vyInhVRV@puSzU4M!)2*17-fuK{k~^E~>&Dfs0o1qi*AZqk-ig zYfm$Ep{Z2KDlIF;AZ0cYi(ST~D!o}OZ>#DnWpeOKUfY7M=15MRMoha*iqbAXH~@9B zMi3Ztkj~f08obbS9PwGyf*WKAHt)GV&hV5YH1hBj-*X9gT>n6J0`)QAJF3nlT`I*TRfTe0-1=AJOtpz+25g`>N> z;YuucUcU)WfMyLDA~r5&IzApICJ#3~VI#2J2U*!Ub}Wz4+Cym?JamBIfUC5a3L$@U zDAW|uA=i*R9Q6cjfHT^XX%AX|oexR&qU5MKY!XtGYLRkoF|q z=5Q7k$83ZIt_OIpi;tXY4gUq{p_`_jqSxO|<9Wmurq~T&>X;uud?uSamZd7b*sCy> zbV`_KHyKh-5az}k;@?_i#|@?tighy$?vyO|TE9uKdav=Pb zox!7`(9#ds`#OQ_DB4{dERLnhHk_FhFR{j_QKQS)XDghs_SE0~?BdAHY`g5%f-=r* zn8H;Eb3_g`eVoRYJU5)YU_?TkzkGEfZGq-KHf&%O@37@Ib&}xqanQm+XHhk3ppq1SH>E*s%U**BY!tL^Vx}y>hUX32eDbXW~c*2DXyH8xTSC2+I zFT>4Gf`wEIVg@5$Vf=`r*7%kJhs#WJ2)AeEt8RYm0(@}6VcE)Lwf%Qy4k%#N)fdRX zl*!aZSMg8o;`f%wD;nsr6TDUGOo_RZY3Hqm)jPDaHL?J^swP(X$!Uyy& ztrAs$yQ@|fRc?zc&m5|Q)!^+>QHg&qgfkrcn_AFlh_b^tI!wHL#60wFBGXquWcl*gdpwgPUexk?D$H$DH2*)!gM z(gB;n_?D!fBgO&Wd51y(j_~Wbp}#E8$8K%df@eowQRLiCp45*6QQou8M5ge9yRc9F z{OJLU2(&&Ze*gvn_ZMp6#(ZztU5_pIB>KhSE6_vxO*#ZD+@Il zf(qqgPdqgyJ%}*?nfZPYF1ygZHgp|Zz6@l-F`uGlq*<~dbkWT6 zJ1_9XJ7~hOo?;8RqEw_Ib(|MYu9Rcz+V0`;e6^mVm8$ZoSjVI$0iz?yKCT7e<3o&L z{rtyF)cC3mBY*6-AD`9e(qkrlp?CAu`J#dS-qFN$Im3AzxJiKKDY$+LWK{IigGQ=0 z6}Kx)okAm~!Do=B>PqacUw6RUEdyI`7@(>_KN}h)THIP^L8gL2Gah#5HGY%`_t+u+ z>47o!_MRuw3btPi9pjFb-svv@r`vRAkaByJX>*vxGY#X!fGM=k>59M;{%e?&?z(8? zq#+nX6tPTop^t)wTsWpsAXZ@C#xu!{H+pCHqVhE88Ev7&0!6c*LyDN7rZ2?7>9`D` zZhs>gWTy4 z`^Mv8RiKX_bUtj3jTI_;7CXYY)leHf?M0N!CKbBXjP$;gOr;j!3cKo#jOAtGIzWkb zsFlIfw3KzzVGELn>4Ba3WuhqZdRopxO; zLsrZJ)VIi8K6D{E9$o+#xW=l9l%i_H>r58sAr;k9VMA*ulL=SlY8AonI#8?NQ+%=& zEv#5jkx3p~OkoZ{u5oHLz@kprsMi*5m0EiL66p#i8mS|J-67R6ZRbGxDw{qwN zDKw_3L38>9b?#sFi{3hZYtnMdf!#8+|v2Mc*wiCMj$ zvH+H$SJ?y^{hk-N{gWU^2|JNn{y;9%fIU01+fqYzwOIzPFny76T2#Q#snuGW>kO1> zFugKbAHf6wSyU-EicL6EPR&_of{O>7spddRma=%qPS1EG-a#wF74VU;d`zspny!kB z!cE@O>2mdIhKb=MHN*q_h8=9ueR(==n8|n9!Dp_qUWrA*fRoNs8h}FBc@+wIz`U*Y zIR_0Z&_zH2YZ0+stgIs@ZKSv21b4F_(P~X@!($Do79Bmu>aINi!Hn)c??S7SIA%3H z`#`CeSxK}(Kk7KDrmcDpWkRU9oPxRBI=yARI7rRZQLPTxTHf2ra3^N_tC_hzk+nGK zROx2BKz=!P87dBGx(d^J;#H~QR%UJedXOTNl%XQwwgWart@^^S27Gsdsg|s+CGW6@ zaia}ju1(O(WSrc-{Z(JSClavs2nX;!m78IhtiE7-px|~dN?Mp$Q=_=>D+7gR&3u+X z10N#*`SI6=d}I!O2is%m_6{hp!E-Kv4HQ7Q70kF}RJ)tMFE|3P+`vuX+U&_GbSX-s zgt`){hwp)Sb(#;J$6Q}4^e(3deG})?P7noHsXwu}jQR-n0}L{#qbUsqINYWb{zh1t z_ro#pc5NPY9VP(p6=U1p)DSTy{-q+aV_IF-7W%2VIo34bZ$Nvk5zMf91Nd36{>Wm( zDf8mO)~zPu%~J&(V8Z|hK2x3q(%Ig~P>sA2gX zC@`d}VVUCI3wTpH#@Aj%U=+YH5V_`PI2WtOV?#aNdD`|S5b(BXIyu07yxp4FG8&~7 zuH`)VH8tQ58H24nG+o(_%Ertc>|Dy#KRp1{Z-J!l6C z!|D{7hSm)(rwXR_0<}l#!O(WuAb|#FgM^#HhPT#4B1K?^rHh*{iOm4OKM0rGMbG*- z>#J_*su?|FjmBvZXsGt{pypD@;s33W@O%9CjDr8m9g{?N;5N7B0fB-1ml1#|1R4eX z?_jkQNDTZR&i9`_rSFs+eeAmffgt?9eTqUhFB%|66UJ9Z!|RLS!l~})LIRa12Dwaa zpVw9^Hmg_;d(0_o)V*jlZrS~c%czLszNJPqRaZ(rsTMCfM58TsA*N;`ACVGsbP6sa zdoRJ=gro!yuiidc8S?_O9_?35103b@Tb_%VR_l?to&RjlN$ySdjc?r76Q}q2%A5gs zy&eG8k`j3hi5+KbkR-t}xvnVYU{BJP6rT+zj6{hhxe?i%I@j_N0io^0*TxZA*I;}! zq-zs+1A3j!o;*>#@GjmP$}*>5ds}hU4tP7(cY|CvC*QyeLo-F;iY}-FmVNXK;`ce+4EPh?6(7vR0sfk zHOuZ$3vksvE$qr)j3rxt5SrbU`hcqYi;Dqo+vI3=C{F7uJssF;(UMT^fSC|29f=Nz z7D#Dv8`7v6@JS^i#ciYrEj<(yi5k&7FIHfphE{$_Q+ zY?WsBa6jFeget{YQ39%5_FRX=J=3;=NW$_dvm{qF z5vk*ha-Bq>bVm=TWj;tw^c)KFZD-iom9F@fM4Z;%axU+0Z^s`(FPx^k@P7b`f-b(B zkxZJ{_fm-zi^;H&bM#inJY)pe0-C#zviYf_)jqZcQprq?5={i4OOw3Eq>F{tR#py^ zuml#x)IY2-50}GJCJ&)gtn4RfrdRS==K{{RLW;0u0|fWFV1t=RKX9;c@`nB#2TnIi zi0{@17>qfLCrOymiYH@h@bmzdWO2)cL<$spqe<5BIS@xMSwM?K^6$=RU8V_(CQ;OP zTDirHjDHPaalij%GMtmJks^U|8hOTfdHA4s6H^d3a1L_A5=56fI>jnbgIs>UDMBeV(!Ga2qtKEaW5Tfrk9;+e3VL*`ShV0(yhqw*@SwMQM&!ckX53=2$ zkdl2<{Qm9c8*x@QW~|n1Y&4bEe4QH)f~Vj-&>PE- zr?8Ow=K4;Du#N#Rvo^cI=3?q=WU4s8+CPisd2*Ay>$TpsQRda~k$B>_|MGbaDBzPX zdcMhJTp!F{yhCuc^C>Scxhpgm`T{@Anoj-P`3F+2V~&G%Vb_!Y~7fq zumJJ77mkLvzv!@p$i`Ms`*_{M{TboaTROVkq2bL#Nx%TWe#Tgj=tx&L)v3)%pMu1J znXu;e7lc_JZ#~(8edp0HbX1~YC*GiF?IFSwD2>@kPKMEEzs+T zxD$}|luG(|srV>Te{P493-EI&KGRhsK7#tP*tK}(YB^~!E7!{mB~r;Mi-J0$`vk7T zd?xc3Qz!?RvOtuYy$;1gGznWaL;s>BIk!825WoPI%-vFqAgfR#42IQ12P??tgjj=28si>7`g|~4vn%})LpNxL3 z5hc#Om2sGAPn>*hs~?SI2I0xS2z)Brr~A}iZ(s+!kPX;`r+DHF5VSa`+2)I(`B*ea z=XbIr&>nN!=~a`@US#qvp4q@QsH?IhhgKJ%kWBqy{>a>2ah~BVupmy2jMlQF82Z&7 zoFY+;;(s>qw_Ena`IMygCSfbeSea#2k*=8O{c_hLi7D^w@^c<958l;Q%PA;r8%9Q{XM3u9}Ddi8eOoj8aiKn_feuTjoyqebkTks}-j z)~V?7Ycn;Oq@p%gHK=!2(YtYqyQ0=3MBRlb6ShHOi|NRH9bZ!6hRqSPlXS;6Tqn4j zOZuKFr3U^Yux)U%mG^c*3}>sstjd7-UP2sTd>72Nd`R`p=5!jOx&LBAbHVG9ZwQeW zRYbvak&hh45!uqha0y9a8x=-|Q%gAXv)W^zo&7!3xMAQaHcKddzGxG!w@=<#!@-{b zsl%Mz;jLuokfH;||7|TD-_o=+P(eHZ>oY=|%+S!{4*$|~Y!X?>chQB-hs3WANO%C~ zL6^OUfZp#wd#?D9QLkjdv9IbC{i+ei8soo4*PP1e>Zl99=@jg7!CnCx6Vt^=A9x+G zTGkylhqr;~39wo=5Ebc{+Bb=mBgwO&M6J)8Lr>l{$6t)ZZoczgjlGc4GGiHKx79FS zo}dLSi0vbCd1tCT+3oVqI0)i({~QLWv4SE_a^1N*bCt_KZ1^^osaeP;kyxb@$2w%I z=v0^e#LQ90Kmn*mA;E}$@src!6 zM=U&j^mEp^lUaaKIfBA$Qry&&i9J;z@PCdD$b zIE$RW7*o`Kjmo6+;}S!LMFb)Nq*;X>$mU4_i`SWtRVB7=aVIfugr`p4%l=a&<^jSL#;D8-~sk|8cefSbClbSn4ca z^t+xqF$Y{OmCExBcT%5ptYa~iz|xCtO85MHJKb(W#CI4$?~ZwJ7?bma*m{>I{={oX zL74yR5D@W1siw`G+M-4!)(0HA`$-N4Tj7i6ALU|yf9GZL}nx zWDYXHHbrI+7K4jwC)~gXV1pp~>yh}2Hpfg~yIFs9YlEukDih>ARjYtFew4@R7sUC} za}UMse0LOY2w&y|c?yeXIzHzH7Hs@iUg_OH zz%DQR)N-c8_)(B-GQs2oe#*KEH5Y@zf>oN!GowO!Qv%-uQpsu;zzU{BLka2xYjR(_ zVhPQOk7Xh#d3PboQ*hQ(fIBD*yoZ1RUFOa z!DDXUbcwH$Qm>`TCwZE)m{P6eWLl5HiE?itr2$u5-$8EOiE^%5vW1=fSGtUsyi#rx z)!dxQJ^0Y9EMs>+z>pJ$x?YODncLO(O|8w_KV{;Lc<@BRxMN-n-fF(xPb@$7YtP-`*oApG3*Jh( zT1?YQS5%l`6Mi0H&Gr&TXKxKt`(nuZjTiH~#AJy#vfF3tZtKdSTjS~=d+ZtvQ zZBm;KR6O|`tebq;sK!Zk3wM9sBE2u``=K+e_y9<0RBjvH;dh|8(?vgvX!?g=4diA8 zgXS_u6@UW>4ZgBQ8QeR8g)qXJUFYd`vLDc{p>c2NW4P1-lK&Q-fTYcep6CXyf|kE+HOS>Hg5P*aUQ4sE1tN=(g5 zv81`NLcZd+w36@)For0Vn+{cbZ59E|Vxey2w9jUjgG{k2_F@SB=wCi_`Oymrs2EaDrVjegLWZU9LY~-3c-yK`S0x8VDzMwX+}$ zKM{MSun*!5MwV*9)xg`Z`3kRAx8MZsRUzzC<>p~2DU?zb^WX<3`8nqz)k>Dw&mVN9 zwjacR@eE|?H7aaX@fS$#5%pMUsw?yb%32EB+}XnuXX#EETa*o|fSj}~9rHV;QVPCBDq*$UN-7?PtVvZ0LXg zzKSy!<1!qw*WRSxJ66nO4G&s23i4iMv>0!zIWpIJ3xR>(6Nj2nr;2Ql_f#C}05>uO zR8f|=Ou4YenX?)|v^f_TGC82<6h;-1R5o|uqL~rN;o6%2K1B~>er+?2Eaa(o7YeG8 zCUQ8XSISU<-qL#OR4Zek*0!4BVeV!(^CrN6TWM;)LyC+sRVIC1evt3-kNo_C_@7+& z539)H*0eYIE$;Jw%k>;>wqrmr|I_sS$ALip)%(-Jr-5Gn+ewGb0L6m;S7>JwlA8Ve ze*EZLY^O?dS_L9alUoLY0;tH?qX;4T9Ra3E8{{r`5g(=?HeRBKp^Z?9MhW4Z*Va6D z@IA6arQ%jtVnHc(fpI~HC5#7v)|?R0b$=+~OBf?*sk5uqd7t-GR-AvnJiQ_OKrpmO zAhh)w1ba={ zAfJaBY(zO_MCLw+0T3^x%mID@K0a@A5%Gt3gUhTk z8*GDR)rTh){g)y+DImu^?bcFX0>(_+%ie5j5Ka}UgEGBEQnQMKo}(9ZP!1-m{7#eD z97Q^<39<^WLXr1h(0FgajJk&3>2-G>66J>Qx+xI@U&umR2(BrHRvd5RLN=q*~Asj_HPk8D2tZ`PVg zo`pHm8>g@G2iE^@rv1@nl6Cs_V$j2-&F=$|r#bi(|e zNLfJt@dl5{)aMP3TnU)hF8rb*Q4N zTsuqL=MBbdP}Mtyc@JS~sD*VxG@i1^0!D-F0erDrx_yY+8lxHK zne)6{IY`9t%X+K{q?ScXKm}=r5HMyj{neI&3Eu&KHo7hk(g;;$3RQ~4H!?ABP{=~M4crN%X(`o7&W1d zW<)VTJ0vQ*c}uAVtE!*1x#VHUvI^3Pz@1SlCwQ(;aA-Lq!CV|LO#x@e*S0QMT{|}} zE*c&?u1jazH`%OoR-LWa>#w{sFLQw2sW-i^8^HO|SRQD-=!|=jsF9E=!XJsZ{RmKk zo`3*=<>sXf`5(c}Y0L^cZ(_vF1lBD_%4~kDBW*snmIP%^db{F%@4^kqk^`wGn&rxT zdiC`6MAjv&t!dYb2%#Y?@8k8#rX(F7Ndm1^bZe+bQQ@5XCL+n#d01moUI}TAI`!M) zn3|waEH>2h4f6g491bZoOSZp~vF%a=Y50Zi193BtT8dI zCJD0^aycZ|U#y^7w8{J0NE`x zh9felhPcvO^Q0}fHhIh3LvXS^}8Wjm-{a6O-Nl1)^x4U+=*f%){B&{gmNV8=81 zA|oLhzsI|VKi;K?rE@*Ju}PP!LkDtbHmLNB4Ay2MO5U|=fz7ESdbMzmSy#T_HKk|4 z>S($HC2htN3r3tP1AyZgf`haEqz~5@N^^(p&4jF9R{y>t(k>lj52cNxPhlTn9WGTX zDJ@F6RV>eLWhtBiA)Dis#^w?Heb@(#5AlFZSD;ZY!rtR-?(y}pqRzv4Yd(ffykiiO zE^AG`TPqah|y+Qz~=9?Io#6$UvjkC(WtK}7O&5ccetG6R0U2y)v8bk%3q zz+mo=?Xo>iT!yMA8%?67pIrt!ND&4y4k;MkG#v1BIk{Dr5#upLfKk^>U47ojFwo5~ z)@R1B-Qv?~r3TqoDSPkuqA)EDdL1$0E?;W8mNn8-9&y9j*QPQuJ5${3oH|GzJv8L6 zDU=ELCqwdF$mWqHW!#bk&%6o?a243g+^?c@w53CXbyB#818n{Pg+_qR&&ButSVe)O z;JOXP$;S^XxV-Pfh8{z9f4k*etiKZL+2tU$o(kVcf_!F&&P9nUY(|rVn=p5}?!#dB zhBHhQC)l$g|H^I}2x6cSj18-^e#Y*#ck6Cw=?oo9$$#os<3{mA#jdvgBW-Sf&C4g0S))ECS9CSYDzrxJ;ApW z*#41u3Gwk}|lZz%WU&Qa?Lk@R5*JDdF zA3b-y1BZrtLR`j(2$MTB&lEi=!4gLTVLZiMn?#Ub)q4!i;sg#cwiTuT=A288LGL~{ z^)I=hojYD2fJ2gx!&_;fko#dhKEhNG;Ff%oQsN}a8L_WeQBBA64?ds00r>|p0zgsO zB~;?>v-){=YfFvf=~O(Jm89c13(|b8s-6z z7N!lBm=<`=%d`hKx{bgO<+l82%e$n3* zl6FT)ed;5#@MRX-Vg9J5+;;5KNU>qu`uJD72(M!Q>K0w!KQCCxV{-LB%Hcg&-MM+t!Y@O8EXtlKV?Km*lx&1R2er@=pS7@`e?yuX^1X)fN z=nf^fL0(-LI#5pdLEFQcHTAsR!TfxA`g%d;+O60)3xhIa*q5f8;zeyVH%WS)INSDs z7gLPw0FOnA2cu)(MR_SB8gCpY^ldXSg|fVznUtw}%)XSqU~0A#F;FT~j_cVFmvAiF zhvPJSIh8T{las260~6;0;}n~1YY1Lf;`$s z5ef(oXS4|Ws{{qRQg}PJYItu{WLLzebD@F4ADpt-PbTo+;sie%fzOLW&n52Nt{T~W zXd^P^;*-i5qgh<0Wt^w+c>ql!VDBF$&wO5=L!8Q@F9E{GHDW0M%s31qe${aRuV(TK zly9&qL5pQxb1*Jq-tGFi#k%jMeLKRQipbwjTK+DaGN+4*t#a#IzAK;3V$!HO)_O4i zp(j`L9sBY9Hxanvw}!sl3ybO}Utjr>mVUQo00^}c3HtpbR=-?zu#Q$hTf@G6+wIC) z=RBztKvX=u80e73?*DKg!@()?k7G*(|Lu@ye?i$ERkC$7)H>#Caek?WWPY zqWwyvT1V&CU*fzuv%eC1B_kV8{aH58e=I)Fh2Rq;r-NgS;S+l-mgoJ^ z*iT{~N+o@$VrrGlqd8^E#%|i2<%4d%M!B`S!B!N5~y{Rey>6w%%*W()7GAw)-gm^+0)KFZo%5jGl zRGMs~Pl&a?6I9xejDRD?+WD_^-Nc1AvR~{hegBUcr|a9ps&(eTfct%x_m6n`fABPU z+Rp-B(EslQ?;b5E-{qCNymi~hHZ6QaWMEKWP{{b8pON|GKZEOoQ%r-_L8CQv6UnMc zyNo=7eqz+rwElW4N2~f(Gzvl!7)%-Ylr-)^so z$6-5*!@+!8*WqFL>FJWNP~;gT$X1r?@23@D=5NeEDfv&cCGGQ|f>FmLfHos4jHvos zR%r^DFi_B?Y}i%YA__rrFt%^a6fLcFe2~@Y#ODejoWcVbu@FGTF$bWf@{@|Ka)B&B zk`)tv#q*fuPDvI-W~jh&njn(yj-v%U(WDL&;B$IbO(#1mE_v{m!^B3r!+iY^flVIp zFhY!2nm(b44y#dTH*g<_yQ8e&b~e~~p6GRCZ<%2-313e;mdHneZSBNhfL|VWlwA9x zND!APU}D@oJ8Xi91`~Spn33%;^=1wh?Q?umuz2m@?vl<3V1A#J~akhrc&Hy7?DHkV0gRnZZ_wLBEdne^%E5bCF)CX z42bP!0rksb{sytmL>!w1@HbLe&0=o3+K;Megxl9iH#`7`CRGN}bOyPsu9%*Sme%-d=D()^& z$)19LDqV5j!@of<=KB^$r3nBqLRt$fst{OJYY;@YthM&q140*4m@%tM8_Bf4WSzB0 zUe+UL^7Z>@4DnK0zYUi6v%?tE*Ud*ZQlW-#K}<1Xg7(utxfo07dcep3`E<Ry+!;>^B51-PagWw-Qq>hzC+K!$9y6*r*%N#q*={rzV~iv-1v&TZ!yVzwyxL4^To z+pusdi(kI|=2z7Gbr%4&E)9NRxR<~meO<){B2@cDNWgaq^gK(Q66qXPAm>6^RSh0# z1l<37?pm3$*ap^??X0H9xBU4Yy?K${1loQ0;9{eLrnR=DYXj2$pQR$@_Ovoy>ztd| z^MWtbiv>`tmvpb`$3DE2eRs0ZGfS~M^ly}im(|E3`VArT_A>yn*HB(qI>q((l7odO zQvVkfA4D;ZSb1R2qU5aau+tK1WDZ&>A;BN@(2SLg`MKolLZg;abUu;U0-#ln5RrFz zHD8a&;MMy`yW-8Fjh?(SFtp)jkV<`EX35HYDIS|yKNNbD zT3c8zW2Zgfan-xRppCp|rLQ+zHBrAN?VPJ-C%Sk{5eHqOt}aB7*rmc=sy9vUXW)7V zIT;xul442jHw83b>D+#O^~v`d6yC1x+n0SMtGV4bgjN8-I1ut}yD0Ud02+J=gCdgi zeawA$mOk}cX_77}o)Fq=X7HJ{*f)c-o09&3LCW86`T6Iih_sMaLd6E11C!AH%1Y_3 zo&}oO1FNilsYw2UZF%_-rhmpX-JGHD!}6BDAW3Lt?IXZ4K|A_QGz4lel1?xTi_j38 zu%^#OEF1veSksb>I0c;1i_^k7i-9Msd-me@>#;4P1u3ip*pXqk4k|gX`zpd1h2@_H zOJa!XlujiF@w_ku)X`otDs}vOgO%k)J^XV8gz_x!{Z7}u8IKmj#vXJ#xZZU+gb2{P z=a54h_B8BRh=1dC<@J|wBKzz9u{lxQ74)OV4S53~hPze_R78jljv^%a*zygWWmNL8 z73Kv+TCii<6LooWC}l`L^8ehSojL~;8G_|K?1glTYZ0EJ@w6jd-5>GR6-d#~oQ&VN z@oU}^^WsC-b0N)BcM|?!)T=cMOjG*HFcRNdhlg3ttUWnoF68cHsL#JG*8)~pG*9fC6|hNjk!KYSZF$I_8$(Hi6oo*E6w0l^22&xDtH$sHhKqX zx4QhXammw>q`ydHpiXdi^GSbS7%cnZY1aVcZ+NvYV4nyG&FrZ)eN0M}hho)J|AaH5 zNaRj6x@4LE4Ej{?{GLi6NnpSn$Mxnk%;nau*n2COoD=*T)JHH_9quHTFsKsW#Z|;P z^(#&o6T7LdF@6Q$AtePo8GcaqgL6>j-lg=YJXj9wl*Uw7Q&)YXq1|Hc7QfX>SN8x+ zjzyUnMzqQ@WL3Pd{vP|+quAQkHKJiYGv%JzS;?2IN6asBn=Z^-!(bGUr>yk7&j|4FSbOL?6{|JHJT@TlBYGqtNNRV@J&zDTDN zY_P#ki*}Gx^j4yMtHVMD(x~iMqv_MCl%Fi+Jgidp=zaeUcQ0yrj*bK{pkm+EJuDw| zwiGNh<@y<$IcFYZHpB5z`7D#GX+3G9?^1xBdn2pTr#x<=e+ylzAJbP-Gt>kAc1Yc3 zXVls|wwI!{+^BY5N=wUZM^(~R1{*{jbOswpx`XJ%wr|vUWU|+%6xLJ?9kQu)Y2Wg_ zaXROBEtD2_Y25m5+>xkr+`Xd<5ZzOI?G*S)-VUCucc&jI4aZudKAXi*q6w7ymSd{$ zR?m%FAbP6bItw3H{oXnj1gHW&0%Z}Z%4;ezced@nieAWnb+Zdg`Nj+vgS#>kR@h| zCkV;^Q3)RlU1c1PeJA^dfEGm%B>`hcVilo+-#zbRPXO%bINVEh&H!7Yu zl2NhDNFhl@CNzp6*o3nB~>N0AS0dc%ampKeXvdv!4(xbCE4R*+UE7!D?k z6alpF)4l?Uc|$Zn-XOKC#9X?x_1V6Gyz zuXxlMElsI?Szu3L;kuAo)!1l|MZ}Z;;q-fiw?F)SB~{>NV-QURA5jQ=q$MKOAD#IM zzCFc`KkAFrH~v63Y)u%?ek|}o?4?Ez6i1x!_v0?y9uSfMSK(w6kI)a&i{M3U46`|% zbgppa0~UyOQ~BA`XslJ}S1%OA^AQ_*fQ%(C6*@_-kogFx8SoIsDt=S;%Bo#qZVF~! zzn`B337wADqv)7YX*A$W4!IDSSVN=jn1tOpKcS^w?{Spo&2yu41rGsOR` zBHa6HP!=7r(~Qf?laYCs;0xOmEej#GEFR)gnM?EVsU3|qVVeBB+)hHe*p~SPWjdxt zH4ZpbkTGptvGdNy+No)tT#fkFS-ax3Z~+E676zLlhuV@ce3~rFpbv}Hfr{-^w(drH z8x#92<%QlYTsy*W&E|z)5lu{ZRls4;eJ;6agv!GiElkuClUWgJyHjAhcjvYhO7Y2) z$FaIIoUwL8)j+DQ#A+ZKw8LT%6)=Pn+X1NJD?N0K4EeirM!XC8V7P^NGITf}ukY`) ztd=vy*@e^NEZ6$(O2+DXmOz5=f_%m9iG7?WKGEPC{{HR^`Ppp5;~PW!E?~bP>kavd zuW@(0v{~09evg@nisNysV8qf9p$~>efaTmRA(HVfeV zYaOKhVO^+JW#+=5uB_4%@02j#O=8LM!mh;cMmaHhX!{W|Oj*d2D{S~+8))Z_E0CC! zJF_&y4#Xn}Dy=LWT0p>DN2;`n66i3&59V1Gt?@K&BgilXah17_6}29oqvdGjKFn`X z2zZd=Zh|~V#W>&6v(4GwK0%6+;01_RC?a;izKI7OciS#``O`JhrTq47!0XL!v~InB z*>IUzA4!DS;Nr+G-RsOQwL7$Y#%#G4DltU-#k@hKvJld8{)Yr|173oM&RK9+pck*eiO=TFa?qm|HZ~@kv(;%d3nyeCHbM}inXsX zKwp$_jlOuhW%@xw8`9UvI|jht+zshPJ4$l3N-NNzefCh|PyrFkO z+6Kig)!IEa+=1B+1m6Ew%#I9lcA15@+ohxkX?LZg^9({L3^HqBzLo}MEkDFR35-4h~K<(`3cL# z&u3zy3cKU=JmY!?1il_8ue+6c8^SfSv?m5@0oT!M7bN<_9`-?i)94Hk?XioQ@|asc z3-}w(luczjL(s*~an6FIlUU6kPbkDF)tp5~ZG)SdggHjkn||MFo{3a>Vjbx>^Y%3|+_f}j< z;|;qOO1D%t`zO@CR5bOsfKKvMtGh5ewlj3XKL<-fVc~IlQ`|E|9l9*Mu^bY&=%0T( z$4oh6PP)FZRqn>}#_W>)3xh+d{z1qSI=6F?Ehy;_goZ+yqF%2&!N7$KmQ+H#xzp|}vUb?#EdAF~evUzv9 zh9V9`TVCLbxsR_6Pog(q9}y8vaC_1A^8T zQ*4M!$CN`us!;)Siv#kQHf+7Px zadAeED5DuvAb(q@0y}Gtp*OJh53obAj||jc*mQ+_BBX5#+r<^L!gfkAni{r3rI;Gx zf=|}h@qjsNt(o;J)7exSgoe06oix;41x7d3;Ri;y)IkEkWm@Ybz;F!=V`14R269kr z>O%NH=^VH287br1^2rv8r}b}c|PfnmA89ux!jO0TQ$znjPrOqg`6Ejos6CgQX=h*= z4`gp(+V+%KNZtV4SChWvtqE(O)w|z5?0Z~3O!^QWT>6u%pgVo7_Y{3Ro;=q;UAehO z=0Lf}*&jH2mmk2_5Faex*}Y&NFbpYVJEo1B5E~lG#QMsu=JpveI@%xYUtqtqEN(Kh zE%cBA?I%|0waE031^!mHb_;~+1J+egdda!K(TnBiO%+EnZdd~#djy)ViQ zP~HaA0#fGFZ?;pJp-LN89OI0sa>H5n-$>HhAPZ5?Eg?Na*)Fp!k2|{^yl6=B2g8YL zW7Tvv^YMaQq&kAvRRR{?;i{MNMvws_HJCA`z;Lv92=wUjViAS@%pxszAkv`dV;qg= zlHy@TM>3%kL=V9LG7{4sRPz~0GmDZb-OD(W(MC4ZH=<;-bvexCGtBw`T!b2yr3E5R zy912D1<{MIv<_u^xUVMbGSUH;sJ5Vrpw90mC&v*-#Aj?6DXuW$L9b5|kwSpI3RuHn zU3yIdSlwjZBgei`U4_6V_KB~ut`~uaAJ*clb$RSyJ!qUQzG8pn65Jpr;-I9=h4it8MpacnBpdm` zCsCZzET$u!v1<7XT{U@~(>mY}%V*7RE$}q!-xQbB%nu;LW|ycQ_bloH3mOoq(X%;N z4?L=42PU}J=vBW3wk-$lB1zyG?57>8<*Z{i3H|Pme*Nn{E~7s{K``bMoX&CP{h2cK z6Yt7|z9x-izongC;jn44oUV7xN&9U?$`%&doMExvEeAHz8I8}|vXG7^Hg}Ku-_!G- z1LM7rQ=5$n1msJbN+b-4|DUlM#Q(olovj7$qq>|dKzV;_MiDEB0s@B=;S z2n+@ynFPkKuYPH6{mTz+xT3yVtBKjaO47Zv!FtuI(|Wtb zuG7laz4f0)XLWP6-1lh@2VuG_nVsL;j_+xX_rLbjkJDbEChopuZhpKkTGy|DHG0yL_(aQ(<$&$9 zh@}?ndIKV~EzC10p~$Fm(dl52{hST6Dpc;pwl4N2)oSR$P6a8kk_QO5GLWgHKD)bK zQlBj5unFSL#5TRXMF1IYxh8xV#CsL|5Sq@t72JCuKokI%F+D!oX!24rSyw}69@ZjB zXXvu;Lg5=-)L8v|ePZ*Zi2ll&G05r}%ts$H;!;t9V*t6~ z9?=W*=`#?Tr~_a{?0fVmi@9LS_=ziOICBBkg>oJ(%r^iznW5xBWYt*$>bINsikeN-h!mbb{NI z9A=$>Ew;PM8&4p^RA|;s;xfn{$cWfv+}RR?riWybLo94CFZ*OP61O&!gJgFib>mJi zagMhTdak=2?(OA;*))lDIe;%tIMeRfz>D7n;rgvrEP~M_&nI(dODvkWB+DOKREhH- z_j)E|g#p|Y!z|__NHx-p;IgI5R0|184h${FM7EcRbu=!IipOz6(#)c1vOO#?LriIl z5-yayCsfaIvP+b?wvx`^N0#r9ng1w1HfWMr4WYDg*Xb1cl|;1vrF{>|rzfQl@~M{h zwy52rbo#Eko{<8(j@B~nRgg6Dsb1eAzGb2Ug7!D0?DHWs3 z5R*TcW!vS0j>$!Yb466P(A0voE}XKy*<4V}aCOPIN`=z?WkSJltX;di=SGJn`6uc% z72ee)kOlRGUHR;SGY@8`hPfSGT<-c6xlZ4K>1c`X;wHwKP<9sc5Z1}-0y&Mwvs1d5 zDhjaXNxF2NPmS+BG1YZVpdy$%7xIGgq_Y~|w!l~1kGHf`^J9o%2l#={b1R^O@^l0; zas4IbMqkv##5W|t^hFmkIByy>xlGB_!pfbqB#|3MaOUxzW{qcPJyl4I zet^%;!2sRIk!N+6I^v^^xX{L&YuO#Q=W^paS?2pmle&^NxafV{7n_Cu}WEVTb$(1&bK!{xa*xi=Xq#FVbj&66&|DBsrZ;ON@;v}#`(mbN{I!n10?s*z5((X zgL*5S6lgE5@>X5m2)YqI_u(7`oe3(TcO$02f#MFtpF2KuZ;H9EKFz2WnIUjM+-#j{Ph zS=3>;y@7j)FDhWPrap4y1xlaer2weNw3w=Dwv&xg1R3f3D@EEsm_79Du-jZ}g%!+d zl9y?>BA7DTuqI;6pw$92p6d znjrd<32o2Aci2z(ikdn3hH`bPkBvN=ma!M;txeT&wM1*AfWX4}&lDafDS+yV@IiD5 zuHL*+%$}|&)>5%hpfqEIxRDzq26@uZSb^jjMHbQ$f77K!hQJkP^`S5(Nb-lT%EYY% zPt#hlJf^B$O` zFO^jc@yS`U*@&wkO$6+NC(ZCh=$YQiI9%PsW$)w@PFSSK+s_M-4&K;gS03RAUFB&0 zQWGV8cd5Zh0Asu`8{c6+Lc}cGb+YSZ*3)g&J?tRfEO1;ibtxB40a%xd)Hwvqpg$D& z@YBXV8x#wY0Do|~WwMJ66Dq)UZ?YH}km0_}DI8}L>~jR5SdvUj+JS;aJCx=#Z7hP5 zVyRceBt2R$O7V-`-YBc8diVrG)-JV6RY1V2Gu=Co)~C~BU7A>>5yXFM*@{R%=s|K z3*}CuU8Y>TRW!QdI2NI!TY)x360>hwF1yrdp!j|Wo(b5P57~WHXAa9$18$Ewl52yT zo~1LuOodzM)QQkK+^+wWJxQ?iw=u;+rna{#9cSlS{OoxR0T1VG@3ot^iS^4CtO-oQhvOA+J{ zxO9O>+w~7cD7i}-%fZ?Rum%8YX^Aeu~l;76D=NQ9a4o4c>NiK>^5zZ34`B4Iy!!EXr^V-O* zc)8Oq+6bs!JV~?qz>7F(_A120g)8AW-m3g9R?!jZvBb}TAIwxNze@~hX33A~n8<01 z;}EB1@?b^!O#{dpoxc&{k?EHKZM_Q64u%F1$4c9HfB`r--f-VxEyTlv`!<$mA)W9r zVz^of$IoXC-Vvy zO|x%`>}|4Qzu2!O)Ak)a1l;-(MIr)@0~dZTaa{DcAlAD%6Q@Mhwh+<0bB}^yidS6o z?x<>a0CaMoeb)i)ptJA{M~33?X~-0sFHDX~xz7A-p`}%FCQTB;V#JHj8Id=HJaX%M z%;KMv3slxl6T1W}?bDX`=0$jU?I2@+oNlf3!X5kP4H*oBnd`d_n@7LaRnuAlAy{1A z(z5#Q5%rUL@9j~`^4_`;?X;p}NIabrDrW+0z=!;_((7%c`u_Q#W5kJ2^ed+z-*gFV zSqyyXJnkb3hpe zhZ=2JFrDNfd~TVR7PS5}wRF?qdS~n+peNxn!NoVhPI{r=*roFtyyKHjEvtZMBc$|* z-a$1e|A-**lGAtJG?$V5JZd1vb&pv1S^=7Gi97hq!K+cjqQpL!n*99+sNI)5)`&6q zvYrlj(YNQrIV@)W5LET7v0Ky?%j}f|clGpl+Y_|EFMH;+HKbo-{`-ux|7Nhw+fcr&ad?obMQh(_L%dVzovQl2Z_z>sJ_ zBU?Q9={O9Fe&gN;JTJ#?|s zZRX<7F6!L@IqOcSSM0Ps2l6zPdt4&>9K^P2YT{GuQ^`Nam4MjQK&cPno3!f8zt%gt z^p!MPRbt!rA95vC-t+Un5VtDLY^-uic8#H~n|)sXRR@Lr2`Om}{t%mYnV07L9USjp z9B1=3%BoO^uLH4=`5-b0sB4~p5=A(Bc$sg4&yMt{+)&}buHyT>!D#J>+a{K4l(r}F z%P1j(tUH?29LzIm|9a%ky8%Hl%T1JN>X)6nK_;Mi>q}m%bNC|DqN+_dF15&gR#Ceq z=h^g}i@o}k;Y=_`gkVP3hw7SmzUJsR-n8zA!_M$`PDuH;qABT+=cx7xf+z!@t@Qi>LPZ9b$PkGQ$OfY zodDl68BZDk-bct4;2aOP>yNAx#P1{1iUND{{;|~1c*I*eMjFsPSuE&_(aB*wNjDbM zf~D8AA>{>u-;c8%YGKH>!FHQ73tF2fo65)7PcK(ad~0%I?RBnp38TD%|3Ifek3<=b zCzhT|d#DlPAnZD?z+w}JpnprzPi75to$7~x?1pgH?!S980?seT!fN+pzJY6EIhM

(E$(6<*Y@f1#|tGNgQ%=kSy7&Y6H2nf(h|@}G80edb7)K_2DBH8BF<}H|0teF2(Aom4olrR&X;tVP z8J@{Ky*hV20UiB@*8W#!UfHHkZ|a0qyoUus_C| z%oohE|L8{Sgg{*H`+aZ$-SKyQ{IKgfv7PDbAEAavOK7%kh9 z;!w@%O6jLy<_L?z=-N~TBUNiRE91`)T4wlU=*Oic0H-MpNmG9B)HSvOGxwhewcXp- z{M&mCY!;xZ|6EfoAN@*K(~sT;RnJ_<&IW!#veGY3==^*KkKC$mhv4dNjpq9&tDGnH zdH?M5U#{a$1%{??ypskA3zlAjxX-lkfTW8H4LfP@hoCpxg~WnT$;X6UkEApmAryGX ze(j5O0kFR8fGR%HTuYq8xmLrun5OU=zNNl8^|y)WPL$?dtSdC_7TefOtDwhLy2#L9 ze1MlwhPf4P7k+-zK1woj38^wV)pEn=%guo;1J^g6_y?Y8XXp#v@_h!&K-u={rY~i! z-uMfeoYT~Veu1heu;CvzM`1+VpFXbWjl<0gB4`_^rz-=8lXM!)R%{4ROZ#C zv@%NQp6b2WCv;vJ{jhEpRNWdBf>Nagz2p}XY_UOxDfR;w)Ee2^kc2Mh0^7+o&=KZP z0XqY#HFw?!YipNE(325!@rIRvDIncTeB;}y=Sw}laophkaptZMGJ7K{s)So~N>AJo z<6@YViIIOcK+=QOcy9sYH12aW z4C~3QJH%p%I$Myd3bJht3suwon4eP@+AiC*)RUk1wiuSHj>M5Q*so zJmA92aN-3a*dEX00eyW~?8CrtSo%T&el4=Vb?;oxJGOhYx!i1YYW{M6q2+kT@q+Nj zX6}}{2bGlP`~z$^Bp#GoV8p#Lr#+Z(-1!6VCsn^*Z04n;F;hs3@6$+1xV3x=WMR!~1Bv-ssdH!%ZdrZgL zj%I?#R8C<`p!(J;DnCbB+-z(sMMWT8?(pi|+D-LtCcnj-Oy)z`bmuQLb)%isvc(4# zFS+GkTi}up+UAV)AHp%HMbvc4_TRndYL=*a4g?ivS84&Uw-i}V^>pf4K!upHob;52 z^7iASw(|0Z_VS%|yw>Lntp)lmw{IG|&>1PUQ|GQDPPz3u(DXC+#HC!ia%AZc0Wc-~ z(q)*9KbpFpnE>-kM>DWjX(sb=l($z@y(=B<{MP{qFyoLEGeOAjqR3w0$~v#eUhyKD1jMCgk^h?#hvbCZYDWhm3-TYE zCdSXAj~>MOe-@Zgh^IvelDjOYGCK;lcB2O~{IEaJs)_2&j5{cqk- zg&+v`e@V-iA|QPKT`@?2q(c2?AC{kerW8Pg|7V~7K$y{#K{P@B)2#N)rxZXJcj7(x zv7LQk{h!_=bn09QFU0?zmDPauP+dmK)r%DhXg6MWI<;$x|~ww&Zt`nO@$4=2P-G+0X9;(|KB!Qt8`WF}GnCW*WSJc3q>sut&McLT@g3LJ2szXo(=P zfC(EMy#!SJmXP@sLBAMglyou2v<%w|Bu%7PGZ;nsvD(nQ67fMWRy0HADpXZXUTqXyzoS!80(6ID zuUIjq;UB65qjj?&zg(8h8;;bFDMXc&Z-=d`@kg>mWA_MV;9~>!_tx-0Q&HO($eFZ#&QV?>U#HYMPQboJQn%i(vd6i=sPP;UNk zMCA61&+|v6PIp-Xc#gbiy)98hdMUuFbbIVY8+Pj$3n_LKik}*Bd=zZ+2)~akds=K` z4(zyWiH*z+w9TT!`&pe%cFS~I%vGBRfpIz7r{iBy)O@d%SE#V-uh!uk|p^ z{_bEAOar|R#VwC#-rg0wK)p}OHzE%a@hqJ9XqXR8u8pib7Xh)VPYq$ zYi-(dxx5F(^V9-PGCVgC=^19K2BDWA4i48^Dj5?pjb-9h!j+d|+50NEaW#x#&k-JS zy#fZQ`Y^6YT4o~GC6lwBei$rDBMkwzHp0wS8Y^B zHa_CDD5mtrHDir zLz-MU0IM9%8FAJ<~gdDhjQNxUjcy4!thxHYkUo5FK0c;Y&{5np~X)}|p-ul8MdSi2E<_^@5{MICQsAl?B}rhTP_z-#qeFJzS@N*qHk#piJvT zj}&vI%Lz4Wc9$BvD^0*=OX#Tcxu&|$3*T4SJ|c*@y!5xvDS0eqdOHBcB3D+MH(&YW zF&F%#2`j~lysIBfwizpKSLp9A>UEZwy;|UkO@%=xUhK13Kt8bh)Ak4QC{Uk(8|CRV2*JXIqpw-(i3@sQyU$+eo9Hi|&DG zcHBM4SpF?Fo za2&i@mR2VrGJal8IupAuN)xA2S+*wxh`V0~SAV(CYowq3WMSV*uSM4F$Tj*;rKAsK zrBjW$%?u&TW$*yhU)(afHy?KyQrS*Jo!sQd;j1p>m!s5p?MvZT8Dn4|FqK1fY2HOg zO`smej6qgktEM+6{dhpJS~-<)moqC>h5?I?;_A~(|5Ea}tr3N)iW0=G9>W}A)P7mH zsC6I|R`o0-3muZUhj`=iqW{zF@7C1n#5~!YDpow7*pg+*A(0pE+cLe3tk=2o~7~fq`saY-Euu?yA-WFn2C<&OzlNi z+SID*+q5XnN*TawFPo3LA1lB;r3Kll%@Yrrn=87HJ?PptPnT2*ffK}+vZ$Rlm}YMa z3UWCt7%T%M)lw3*FwNk_)7Y(XQb9P|aqZg~M$(lUSHs-FN$4Xu;nU~y2n#vpTU^-0 zyk4a>1GwEMsa^xWC};$NOX}<4PKOs#qsRqX8gb}ny%pHAR5b(AjjOK2RtJyId_DfO%alDRXLQod zZcSyimmj9VGK<6%mMWxQ;>swv3warZoQ+#yl~C&W6GU;N3}xbl5kyrM-aa|zO#M!@ zj~JQ`rpAR`o(Z~`5$aChI)fT*Pz(aoI8}A?;@KJ=d~9)nlzz2J5o-}q*VYSoO*(3} z`MnA_7D7B}U>tj5vnGR;F?Z@Z9!lZrmDVE6JBF*l-@Sux@;MCt)eCy;R*CDgvgShV`Qp5FW13@Kgt-iPB~IID3Xe1O|4f0K511bsGjur4fJUGY#Z!NjD3USCmqdo9YLJ9B_aegvaB8MjtW(OmxR5##Kvc>?LHl+dz5t|5Fc#w_?^ZB#_S zL_2}j^5K#>i){7jyOTU%<*er!0HPXT7M#%zAbdp=?t5zKk1^o>ZYALDd;4J0U_;-M_R#Bp#L+WA zCL9D&@6ul3-S$N;btb0r%?73e(iln6) zpOZI`VcrQ-q?g04yqB8aU{YKPh+lX>EQ25l`Rybd$5p#=q!`BD@t$E#6`Rq~H!NNnBHVm{qd@%0_vBe(Q^dg>N4 zTxNjH4&It#;r#QFdmN6h5*fFz${OTRYp9*T^ge{dU z%S^tBL|JA8gXSagnWXs!Rg}R11^bc3KPve@5EcZn`)j@*dP*I{|A)r-5y(ibtOg*W z|HE?d^hRytp)3&dYmj^{x4gCA{7W47y<>4DA*(cDFTa}C^lGw43Vr0rJh+T)Wn1t z)trLY23)i%wqv`+iWW+%irsn=qlqFVb=i9mG{_S7wtl#aKAqmZ)c9sdin8xNd)vc)sT!a{Ix;Xn+zk@c} z5d(baVZfOepsb15!BwqOU<<<`VI~Pcf2Rd-(IuK-rNan^_{>KSu%N?&W~j{>9ZF?} zFOLj1b_8(t3=m$xtm$_e?Po&T+qyGjUja*o>ok`CT0jT2MNRA!yXv%2G50U;GMf{; zuRsC#u%s;%1T+U&5N&}Fr?Y9iuD;8Nc<62*#R`K!;hnJenaN-9916n0{Z1_-T~gQEL=O3?lBcjll_6^g~V30F}@vwAy0Jp9kHV|p3j>|&c!?hNqL@sv4LW+!P@CILLXodfFD5q&^axD zI{ZY95_Cr z(18-5jV&MaC9qzMW((PbT9hT4ii#IM)I+>l2-YGg^WvPD@|WQRU(f|CPa&sGP>OPp zG%(bRrhzFeil$Jcza;vp2+qM*ifc3AOMY_v&q^LyO-f&&MUe{b4X)HI5vyfDht1tt>*&Z9jFmz`D7asqsfKP?7YP24Fh zyq1U`*5>X8HwT*09oe-WI&=ugFDGRdn9M9N9v#*-JZ1EA!J?PLc#Gqb*L`&W2b+rE zlO8N2okha*<~a=|czT1%6()-XK5z^{l;jawj#-O};*%}-wG&F0)my62I+4&t34+$I z&;V3XY6^ZxX<`GM^;2D|RV!;>ac(zh2+KKStu-=0x?hx#_BnT!sWuV6J~KQ*yH?k?S5QgRv$ac*kUk~~ zresnN$QMPBU5U10!NzAjcL?eqIT|IXKZc-kh!f2YX3Zfo7C1}TrmICaTu9dWKrute zccY+8aZE8jN9E^O=R1)p*x!(=5=TYCjLtVGSr2?Ae^ii(*bOUbDpwEza#Pbzh}Eza zDklNRpj4G4y2@;!WGT~MM@Ak)sect0#FCcz$EX3jdGV|;{K1ctZyE4eN4%b%xkA5L zLC&EVQlH$}ClII9$y7>wc^)k}#F}t@JY7_%KgaS27xmiY1i5oGUA3_eDnIJ|*J#90 zY*9P}n9fKxMvriPVo7WOh&Zf4q0&H~s!o%#_jNd`+emcX5cqk+jB30MU5zYA=vV{K z;Q^6p^!O9RL4Fjzq3YVLqMQi056u$ej!bet7B4|t&=^j^S&%$T1bBH2-T1B&Mf>=)>ODU9|rHaJ?H2Z&HD z%ukNcVtOysL21lS2$7(uoq@Ttf?Y$+EO9`d0M~hJDZ6N<(XC*Z(DhvBg0pC0#2TEq zbZXy)QNDjK=+~Edi4tgRQ3EZO6}+hK1Hoc&vUBQ++Ul>W8+ydrC>tp6%2W+6?YZAx ztKtQESVKJPauVkN-})~|bIv_<;oEqjZy7)$caU@3kP>rQpp{|zenD)0&Bmol5Pu?z z>o6%rbY{%rsXX_*#)G`tLLLKHdZS8;uwfQnpntk!m?OksbwKoE1&JD_dM6epIz0wV zSiiiR)Zq$tf}W)lO&#@y4`wHE!O}1UM6TonqX!k@px!0m_<+RwxYQ|8wP0?{DSiyg z(4j>%NxM0|K3V8Em3LxqeuAO$5=Vy@Vu`#V%h;jjXR4tWqG#q5Ta|dcH*OS{SBS1O z>=PdaBFp@S42LF~kr}2SoDx@uD7lZ|k6M^ljSy0ug3qy;Jg`OjJ`01w#>Wg5sywJ9*`=pri`B1& z^5koAQXf)sl==zFrt+pOjn-ZtJs-!@*+cevB~aAQ4q3gc4*=}v4dB}9-S#JE9r z3_Jj~CS92t^Y3rhc68x1|nIpB#RE>L{89A zBn%SDuVH+p2i}b$2Ffv~Xm$1v((VS!z~lg_{g=aQgKG8H#uXw{VY_8FSB5qE5;}Mj&?Mre@y4x)d^XjB_cUAyA zv*W^)Yr~8RQ+W#yMdA*i-b1vG`v4mfw_Is1)zEctRnf&8Z(_gt zpmxNZ3G@t03rczjnDqR|5VCY)L>fSMsnn`q#{l=@z?<>e)d(lFrgOs;K^XKC3b1yP z%E7#;Z4(Pmw6~?3K$~nhaR%Z^L>faLuT~jQb*LjT9k7Km$Q)Tk1CU&Sr+0g(D$?4 zJ#dt-?FYFx^R*5%r=F1eh2#hON@rb3_y>HCz#uynx7OdxcBzOgk@pA=6I`rumtfKE z%7yP_aUEGh@^AECIKNTkFz2N>`>7}87nY)$;$spWveD?)j%I>8leGRBVM4A0a&Iga zdFk2$l|lSV#B(hW42!J(g&sidxTrvM z4hDD^Q(;;L_9_!dZ%b|)l=M>|%YOYkls?-sXdk-aLuoL?anvGq{W=Bg%ksK%vJ*Zb+UXcS60YRGXDF*=4h5I zez#&wA0O@&IZi)+Okd*&aOm^2bhO>#B=)3lPKs(-vi^N)W70QvP1T@ZN^;4e$sv$G z=I71M9N(f6-<=)#R}$s-U*fkn@Jp>oCkj0i3UzxXzeu#zRKC;(hJj) z93sn`h!~1dAp#IJa28*Sg!B#RI%?CV+la* zh-TwbyfyW$PdA)WfPI5rLggi!zliglKw+XS|BI_{4(_b!x{Yl+6WjL0wmGqF|6-dH z+qP{_l8J5GzImSSe(!th{<*7m_vxn3k(_`?I$$RV| zAR2-wOZW>aLtr`e@%e0j z-zgD`1nwwbVROW<>S8~~5AOMA@)6yEeMVjGkvVoRF>-qxAV0D?j*Yrg_;)gnmAkX} z_jH}3t_t=#W96;^M6a1Sg$&P7S^Mho-dm=J1=f498hbOEEk$)3?3wuNh&(nP=k-}L z7R`|>HW>pa%|q79YPU#h$(aULdwI>2h%Fn#1=epKjTeluO?*>a%}1O|k0`MXK9gpb?F$AmdCyk0VPk)3-D#tl9B_=cdVALh& zC$NCvUeF_!M~vbs(x1tomNXYv9_t@&FyTpM(de%`nyX|Rtg8h_Ns=Zr3`)t<)mIy~ zdztHe6k7|AJJ&GVdXfOWDDY0|vOjOM6=t(Y-C(O3!MJ5=|!<*ow4UTSc9au}2&r%Km4qmYY zC_7PBz0jX4a{S%?)CvGvjCv2B`!p<+jdNzb-1hG+yP=Q_m(sfvNw(bsdXjhqnj6s| zx?|g(eckErQ0CDcu`(S2tUt<%Hbe!6kWWXu!OVD92D9UA0dI4tP8#ZCWH&ED4M$7L zw|d+LD=&xkFQabx#HRzttm+k6dc}@rjXKk_sxBSf3}b)ClQF={T5YeJzW-WT)K#wI zC;imHl_L68F88J+yf@~HVv5!a!Rfx&iB?j{=asxR%Hm2t8+H8J3C;w!iONLN>6wt}F^^ArBGw&GVRjvBHx{f(p6MIyfWJ%0HRKd(Zu0a7Gw zp~E+V<9p(zzX^-d-dVJIV`owLWn|wugI}OmgUdJV`Zoa3sFz~gSmpUf9G;+M!F%Q` zq45iB)WuRUrCP0%lXp4uGW(Vq4&zMq_ys7}CYcv!O5HCmRA2Ws((mV7J@gI~zih&@ z!PQ>i4zFb|iR`{c*j!rnG4+Em6QyEE@-l^eIM6k69{P+^Ux_9EW=U9@6n_-VRXjQ3 zEl%&kn-T&#Wbp&U`K~#6xA7ixBE1uN?{C!?GFb0+v=a5dlvOALZ>#TQ1hpr5`acVK zgg91MD1OiTc9bDDzB7NzHXJMYCac|ALB%bdSo#L~`Zf7_xl*jm3$NbGm!i|{N-_nZ z={O^a%fkLj2!y9&+LG2N01&@d8g zBzhXViXH_VegVpnT5YrUUj9F|+!n$(5c+>{J^616PyLHdF!VR)YZWy0XXLln{lCh% z_GP>|{{nd6gl@J&A^X*OLaKkOtX{MXc z`}I077HQVJYETd-v13+wcD;3d>u=D=Tttc(K;NTw44}M&DB_*BkX;<{LL~p=``@=k z1kc!nyQ$@a?NDE;S9`0S^%2AL6!S6oJ-ax#TVa_Hj?LBML ztF@g5f3_K5xFvRM6$g4pL+(Oq#!_C?YH&-1i45(wvg#p}1ogMl>Bj_UwuIH_2y0!( z_njm!VStMuDQg}~Ze{eQ{c~%ciq`;4L_-r99`pMMYhH2j;i$GWMnD?SIlw7C7~(8R9-L~>IbUuiiKHNp8$MSD z8-B?mmkFm#nE>8V0a{s&1H@jDDH*1eIZj)rULhuGZcMa{nOm~N89TbT4gqI?wHxw& zz(3_=BIN~#1bu+YgC}UvVYG@pzU`Al?cjsj! z>dZ)79EAb2GDsyd8k&c6uVY>h!6lZ>5S!Hp!w1(tizJ>a2*<*)3i#3zF!)A#g_1ui z)hi5o7R#}eVUX)(4kmF)s|SRmN@3t~17LacXnjCdOV)-CMP=!(>3x7{-_qs;b+QET zhEG8blE+`i+45AuI)9d6C!OU(Xzk}-37x&U>y!-L^GR^yp$)G^U^38anzuC;6bNIv z$H*Pcp_uIL^M+Q7^QtuFqQa_6nkyeesHOB z{^oLv4ni)y;Y^U7l^-pp?R9XHr?%x#%#U5 zM5_(s3%|jwT{{%xVYlagJ0Q|mQXRH!&3To+z2a>ksuqx{FBc#&q&)NL}Z?66#u0sld(owV|~O?SKzFUFR-a?rMS!VG`y|_r5dJ zBfFPdykK0&-l`lq0dS_BaRlmP*6Y@N0=U2`zn?i(hDgp&fm30L(_mH0Sjl&6JS$bX zd{32wJeX!(wmI)!C@1seo7zj8wu%Aka~ zI7^h+CA-<(f~Sb9ah3%J=bim_SzHD|8>o^VFx_PR86f1Q38co7)Ym^mpohXNTO zU8`}vf#TlZj?XfxT@5BPeQ`L`DNiCt(ecyADu?i<(%BbU4-6j0KnE&*7LA^^t{LKE6|H$=un60hqI?Ut?vB+)~-|3+kxNdg)7! zXGtqG#GKVpa;{#K|6mD&hD9kk(HUdLW_==Lp)NgnEau<KP#9M5CrMRC^(%{>QI@+qTVj31 z1cz4d53x<8XTum#wN+kggb6pp5xSUjsU9*6JLEv0sm4L+?&eF*vOwPujYUzQ`a@nu zGACImF+xZO&81bt@X!=178OuViL$`y)wVsMt<1fZ4jyw%%_3D|eH?Qmb@ShHAj+vs zbUn@3-$QKkzNv+gjIp78VEFIy5wDxD_PyfK4 zElA6#C|h8lu(Cd(aK}lH26b(C2cwOY%DU%yzLO!rLYYGRdmJN>V=- zz8!Q45W&F*?}9)(KW$z^#UnzdadNNGB>uA3rc1&C!M5V z%YN^ZYsa6Crr$-8%M&LfJKcLL9RZ9FtzLPOKVwj%l|M1fNrB_Xf1$&c;6w7)bz7GM z56T1$Eyze)wmuFmI9s;Cw1VHr*=Z8SP&|0Xh9VS5(~aG8p%`7J(B2Rf&vmB`Mgoe2 z(XO5>_r?m$yHkU%vnSEuZE0di0*@4$%ca*+=k5ULNB3}ob z5<8YCMO$;8e}M6jG=T)Vo^)o!%X9UMAr03(FNlSvl$~gd@;B382a%le7q5L0)|@Hk zd2-gML|)15h(9wy3XH4x${e})W&&&&D^6UTju^PzHt%1-o9@m;>D@YjZrr%PvY&)D za54gbp|1C{r-~r&!Tu_9U+qA~ci+r&3lzNid|uxlri^N7`p6v}4gJX7jh3y_`=xR0 zp?LJ`rVGKh4DnZ`N|%8SObb3?hdW`x;>=NMTJ_5>sM-$JPmSO%g<4Nlvju>oM0|f& z6ZA{+@rN=>b+%mI?0fOF-Pj16*VOsT-QgsLU}x1G&%4<^mVso?%vHk`=O@FD4HqHs z#3yHV9BkmNmnQ0-1zesL(;=o?FmzL9AW+lvGPuojzj8xm9CiH_4QW{vP94XZ%40!+ zzeC|mwZQAxx4{eKik@wW7Q+Ch;C_OsB#Uu?&FLwPwXy9HgWuxrkQn!8+H{UI-)4_8 zH~$*R*e%qj(oxN`0MfeUivo|C=9NVfl^X>hu)cV1Y-?BXavy zJ%tFylmo|6u1X+Z9LCkOEXyb=P)UsroVY6f51c}Iw+^d>$67A@UyI@tHyLEtyv&KE zCXgy56;@pQ)R&R#=+YS;@Co_gN#d+SMLrGUEnsa`*oDM9i z|KSKaV$f^NX^-viCBD0r6N}{Px(%CuI{$CMFlf3#+qF2pB7aNY$Y3AY-Frh zfQ_ZOa17VmWN;)?&P2t9+F=3+@q$!M60=YX%qQ{JXa{kG`GXsJQwHM?JRHvnf~_Ln z0IUEsa{^)SG2;iQ0D5+gqId#wr}q(8gm_>DvH}D|5&i1!`U~>s&Wpd)4U$s^B);Ue!eVdQV%r!I37$B;8z|KMxb199W$Ga-2TwyH6Af_7I-2>$pW z)-^Wg0)Pw-rFNO7nWuqqoudwI3QTLY`ZGIL;*KVJsHq-K>{Tk{s6YNWlSzJLr=DrZ zzCgnFOIrO8_`Ok-U6J%LL3`3= z$%HwDPmZg>fnqz#Tn`L$SBhTl*|AA`wq6k-8Ec3h3!B)$`!7FWVglC>RSOvyup5pd zChR|9Jv2`qI9Wdl1?8<OQ?Plp?o+DZYG6EJIVvj z{m4k0We$at#!f!$-;LZogdbCPxNo8_wpZiUC{NkFgQVS8#SnIiMA8P&eT`gmg7Dq7 zTYK8swRABOjcrc$8jbj(*%9{J!D%xWLl?=AD1S7~RxYnYsxj4I&wJ4sf2gsKZE+cjJ=;0NjlCg=C`Hz{9xZqYz}Fuy zSp?2=(x&{DO)h5D^J5)E$pNkt^%|p!tul0N+68f&rEp21j$|GL3{O!$L9qZ{pAyPj zcf1vFwtlBt*O}DcI1MI;JTE>0JyzJdf>s(Ne(5C`$6qCjo@C%vxMfx`j<{l;Tuu%^ zwb>+H(hrCvNiw4e67FjxJDZl0n!_493y5jQpIZlJU-`LhLQh}$&1}MZt$|Lg!=HJD z*%pQpjf5V{ZP$WlH{*A^GIRneB+hA*`+1F|Dh2pP)l!T}Vw)TAqZagMwO7i{Pr=$_ zm6LR%A(!B-Zhx3~Nog)eV_`dKn=^I{_H&{$y8L9>J}oAXDn`?dnznDYK0$nd&=epM_)Ut!)$G0D^1>{DCoccQ+WLQ{Edn zIYTt%ja;_vJ9>yNai9vokp|>T-gEC>;qDi-Z2ND8l=xt7-t)OnJ)M+X$9HVw{~Fv} z+JQv1IQDn1clCoMqLNI^*+xww#E1352-k}1)O4@CP2!hg5hw1UN=i>B7W``;&Lm9B zvW3jLFi9kJR3j-b-vW2?(^_IPN>OoAEiVEGXaL=r4a=FpU_t2vKG-cn@ZOZaRh4iGP5!}-V|MC=6V3eKLrE(%OSq(Gq%Pyt zSAfM85|UmHn=ZFcL@t}r%b!<9ytT<%*4v#A130gJ?bn7bg8XZ0o)o$z`C$?iPKhbr zW=vxjJ>l(#XBG{pv?ij^rUKPSY0#ZzTm-^TH2^Q6!qzHFnn;4nq7<5DF)~2TuUf9$ zM)>7{rC+#t=ty8f%rh|gI=|LtI7`pp){JHz-7S!P-(iftc(fly%8+Bbc}2<-d*GlX zd0jRN76an+o&`)g4(%9a~JCd^Rxjs z%vjl$5?31LG!C|V@%V!A>hH0JL(Y8Oin+9-b!ktFY$wE-MJR(j7A&t4VGuFkW;Y0y z5*n-@6jq9W>KO;?{5cJmgdy1Y={>NGq@k@~lTtc&mdNA@x=PA*@8`3*pk&%$5LB5z zW5Q*Ghy}a^f}*?$Ax_YZW3uzDXe)1$qwDg;CcNq2R6=LaeFs=lZ;nEH26ZBgOgS$P z=B~+wYvTE1Lf%9KM|i}8c;1M!BozjvHsBa4Rr*7~(D>@B1F|zWx+8u+H$dLP65OZQ zk&R}2r{#qfU3pqC>9&j)fSomMW|RY}DaoBPa3myt899R=HiKU{+bMFG^bnzj-QsE3 za;r2MPN$;uYJgKM(~1SH%fG&8{LUo;IyKwE3f7bP$p+5}b~$=SgHz%oyE}@rVkK5k z*QyFQzX7IftwHE~RNpC)B*P2?yIa3l@03WW_IcS9YW2X@To|PmJ)=js+{dtnDU*Gx zl6I_wf@PlM2svyXm9w=e;Bv6%yE+G}Ezt(oA>P5whp7jLT7m5=7EPLnv>*m+fP?GK zo)KiOB^`UpLo6!?MSo|sF;LWqy?%o0|FxwUIN@t zd9g#FxLb*;T0d?<-r0J zuk>P$9y+kqIf~aHU-m+ zlSc>CaSSNT6b^G`DgjyhWOj-K?j2(bH^pw1Y!z^Sbg>HFUfP*e8IFrgVih~?y$ttz z68Gt%G@Ox-dw#>bpn%0iA|o@X-lGG`^3A+6yAyLc%cQD5RM>yZz%D)?k<+c9h&I4! zl`iL!AzDEu9%}%sozEFhmK~hAtHJQq{bMQwM^tn7()aR>24+vTwG)q9WoN&SST>_v zB1|pApn3NK;eg7xKhf~yD3dKC11DvNJ7{CJWM1bul7Nsn7bldsB+j_#&M^R3LJ)9^ z>yW;13)Xu8hnrJt(b-P)27XFUHO^5VzW8-+CrzAJJGdiaQyP|z6?MPF^|qXZ00;Sp z+1a>Mfj4ITSA7*z8{l`6w8=PAHzC`b+Va&$nB!W4F+cO=q?lud<(O$gBp|%BfQBN0^L7b5g9?6T3vE-9{R~5zcuYX> zRMi*B&^t`&*#N9joGO&CK#hsT8UjC*v4QQELExKqKJy4}OXKS)@Vf zTq^;PQ)!icfDDR=MP{{91Lc@MH1(eHU*m`$O36PanTc@=vK-nsBNmWT!WX9>ekm$} zD=Ngio1?ak!{*~P$#v@Nxl`SkDgmUM9$(s@4#V4~@Rmr`fz@$Tp z+VYeUZGMcn`np$~P?$*r29OdOiO4Bhh;FTyzt$d9B7pNZQ5Aiaw1&K%5w}6z6OZC( zwbId-pUzDrHzGnEE0OG8)Y`6tAJ8n0d?KYbOgh|qen#KY%<%y@u&%5NSmL|NrC*RO z((kg8tJ$e1oQBpYs641bP7cSDJ>L$^LrHwY>>9j&ZrtDyRx=M zdGQWFd%rfS4s@N?H5~CTBq(bgj3uAM4Tby(wnrdTg z|D4sXaU~9QBEKS3_#Q!2gZZ*UBF*(^FJLCdBNKwLMg z+Z&{+PhxzB|bCK!j<#< zcfyt>x)U9j0W(VwQ^V`fxML^7PWJgIl?hkpt7S>$s!A#B{$TUFtH(xhN6hk-E@C$muxD&7V8B9Y7IFMHn zd=MfkAz91Gj5gH_y>d&C`f8#WguOY*q%A!r%A0D1k_%5&!1%F~kw8YA_4^yQeTJITQRQw&b2 z;e~GjxdSPO{^xm({`dk1uR&U&a6mv#_^Hd33k1Z1^-r}MKb4`G z7rO=24AchppT57z>H;GqBi-lUZ(2DuRvnliHQNysDHW!L7d`dG3lsq$B4RG6P~Jq; zNN?DrM(LAF-6;6?59Pfr^x#D>Vk667KaP<>=YhxH`8P-H~6QZEASWH4D}XMzf!zTy-n2 zFK(l9sYqqU2t7mGpm#U=$VaprV!H5A!qB_XJ6OXBudVqqrsw?ibxcOLB%n`ni1Nm zAI2^e%~%}Wt~6I?tB-&5A7-RCw%0Xi{3XMsGZ2L3TgVg;33GIiPmP`}xjROVX40G)7_N;yNi&)A$jfH7%O0ua zvxHpo*i8rCQE{r9$C4e8Wc*5J=XNV#-gT%iJ*ZIST34#Q?zBHl%;L9Sp}L!aG%_!@aO$6M9A zv*8<6h)m+|9@3bXf8VADyy|!t3H@sO!!G}7J&GUsIIJ_v zKfsqDP-3gEzfb${rV-_HF`hoCwd@&PX0xYG{sfcOPn7cfru2@#n_*-}(g=A-Wj8wy zQf%KbadscSWWlSSOj1+14Yd#|E|y1sQTC2C6j=0RsE}Cn1d-N~)xiDGl=sw>AN5S{9UrPR zqx^_u8)H8=u}OFk29XvM*2ri)9!Nho>H1+s<4{}QkHP8{1CY8<)6dR!S;f1zyQ&4aazucQ6>2?gc0O2U zx)DH2k9uM_+~^cjRFakOi}vM-(^l}wXF&Ff?=*Ij%pwfRx>M+8!Wi7pEv@;)4#oWZ zn`Mo3;zk}#iW46fMoeC7{HHN`N6Dby{^=Aog(wcknYq15-c9zQLn!LmV;=Pwx6_j2 z6)azLsqq60tU6vJBB__SM#UL^26hG}%mFZ@OJ4^v1tK{!`dn-@&sz_XV600sTRv$E zqjJG|FFBw}ydqqrvl&}(rDX}3up zjMk97V*$1hXP?l<&OYoDeoe}ze<@Bj)j`Bf3@ko+BD-;@oV91+s~AZ={#o*KeIKCO zeudO{7&DC{3ySXf3w6;jD?~eN*1KASV-9SbJ%%}Mq5_8&=QlwbrwHiDNfpQ$#`WKZ z$UMT6Pn?pZ7F<%>0SYiJ%dQP0cd(_#08;H9h02CLe=Zzh?BRXa^|BoV|Lj<5nps#= zWm@enAHmYIx4%nV*&?`?Da%1qAaj6wS8nhl|C3DR+W>CtSR)#a!r|E(_K+GdSBdpN zd1gAN5P+RziwuC9vzsp*_hQsOvW16CEoSgXNLbZX)a&GOW^Y8yMbn?4{ccV% zuw2_Vbwfn4w4KhPtu*3RE|S)emoJO;5`WcBXEk3x=yYW!>_M}nTmSa)5pV!byH^6I z-y55^YfRP-cKFQBj|lwBNRQ0{tC|3 zbtZ+G-(WI$i}vQ#$n)8DXr#2YbGY~mFls^h#fh6$Qdx5$dipRtGJ_MzwlryKMaX#Z z_JUYOX`Rx=mS%S{3K*%R#n%82b~p^TF|09|^>wBm!_(!QplFK)UP6r@VsB^>zmlF& z2uPJ+hB_zSZ!-g!DlwxbtQy2F)JeM|YGz%#G)R#u7d&5&;;A=L4{I7^$^SlV#kp$; zq8&XHbYDWayvv5$l{Kg9dyCg=%9}JrkNhp#!f5T*J5^Do$=Zs4rq2fG&%^2{9W=VN zOSoO;@~E%#*`+5NlY1k|TReC7+NhH-+^}ZnMS>;^dz02oBOI(dgrf?|&9~t{7Q##- zs>ukaI#OTi=zN3jeg86cP&i)kS(as#f?9eSbt|9X+ zC|9pQ+IyyZ8$5mVEm8P$G3FXMAZ2HN>xR;!PJwAAl=8ik)^Y+qDQhD^1gFScd9urf z{RgwD9mB_I28k(NYtuzSk`LewP>sKq*_4aXcrg-Y{n)K32t3t7@vO43+bEe(*M>+<*k$M)Zawe&R!%Hq8TT!Nk1H z_S?bkW*Pz1ce$tIZ8C#axdbcwu>k0GI3pS}6m<|L2q9Pbs9&O(pP@~}eY1Api{ZF` z=(xGgZGOfsN3j{=t`5@6nS0eT`MIMKkwu}0*`c-UvQ8uIuZg1i1~2OGJ#d81vFk0!sEzU)H0OcR{-*vc#%u7v#g+lwHS&%CDUu_JppV^G+YUuiN6zF zv$unKRj}FHcVRb%bx58a4+H9IHhcY0ElX)QI?! zSI*LLf_^UL5`2a9K%!GU-!|X`4Bh}v#lrHrs6ch`P z^{wxOc7W~sHDznsfc=A%v=*uzB7O>(Ci^!Kj!@h}oPI!fxOnFIG&zY^VMC$>(uIK) zpSDc}hUJ3Z@MiQ#{v364ijtR>*H%gM!e&*oRNj?9gEYH(PB5>$t7-RF$JJNcfIL6= zwzES%NNR5zGF?&_9M%md;YVIxSUes;VyH>Mav2AwnZ0N-$W*f(`!r&3l_C(W6?~dL z&Ksuc=ZzMLg~l0pL;#yE$mOAm%2OT`88t5@#_uId(ud|9kse#y`PaSnpB`4!`9mG7 z^Ko06xobGAb0E=GBQ|0Me)95SJP%A~*+;3-o9 z{3zj)4s?8m zJ8v<<4LnODpIXj|OflQvXheWNUQin=PEk>s5<}mlX|vc=YAcnXtU@bZj5Vv9-T{O#Za^E5}(Zz@QJPuF#8Q7t`d8ei{qU&i2qLm^sK$iQLC@MRY zJA8uXsAY;9Uv#$UHT+2C9VFYT1A3i=1MEAes& zT)4WvW9MjnK4{h{~bgOl@3;j#IfDx&uS@e$p5Oc-;DER&I=6-RcAw7 zRQ{AieyiabYh@$Z8W8qSKzV0WE!KcP`~WY9Ppb$7c@zP9S8G&&b4C;b)Z^ph;oAa{ zRT7$dX)B74$YMx{#uLax-$~fK1%7YY3`vI$BRQ6Cb&- z{8&j#6JDGaf}A3KYFPt~8(Z=kS#o5BgkKte>UdpKaY9UM*^BF60D}QsnR{X(C7EdA zVXm~hR&cO^8sZM-v1C2ewC(2XG#OLb3>(O`0ooGv^4OvhRC0uFt6c6Na;lVqzKsD$ zxU}?UR139xWN%rpVySzEwU`q`6YGaaraf~?0(Fn>5YPS?FG<&ejAnbh^K_RxMPf;t zk5TId2!pMuosE?p;LNF;Y@>?WcH%{Gb-tL)85)op;U?W0dkrsUxpo|PIh18>)#18= z>{S!-v_o&%e`iyiA|ozi6gJja4zx#ced6a;tKIi@($rt3avopqLCKAmJicDB8`wGn z?Ky$Kg#ioQK3E8XC;`r|^0Sq!JLwS_op7qHSeqqr@NP#Cz$G+H9t+B&jqaU^&ff(V z<*xD=6nZV$Cgg1?l{$m&nmYI#Rare#KZK$2%6=v$$yL2;P*%Pq#TBabC9cYA3R=`6 z!(!rDml~`=0_&j@6HB3bK5&F>MTe7R@_Ij>c9RrQaxpt_t#%h2a=YQfkk^q%&O zCCwFkXtg~#=QBk`=O)7X6sWqSD70H?91CTBml>J?+`v+cKaJlW^fL?Vn@K?oTa$L= zL(}Ha_1$rBT_W4YVABhrSIw(|6O_L+sQ++|LX8!R>tI#VpS2!V4hHT(&C${zES?~7 zy-JY2XJvqDrA0V;H9BD~oOMGZ-m-nhrZTmCkbjKywJyS#Pb`YNPzt-n0{arlJ2ZO=i7O z?ui}b5;JqkaVkf?d}z(~P>Ni4Z{DlfJLj)44mX;isV@S-Xi?=@tqt#wE|(1%;GgoF zAX39;dKz^Zy|yKZzXa=fC5bVith+c2`66;(pE>PqpLeRu?5I>&w&|HUzVJP61+!HK z(CzHp=;nM|)KOLt9oWudFPdvAn(Nu+{KTu8H1Kj65;~Vur5{ zQ%hDzH`Q2=DYFSSB&4b`o{?L_!e%Yz9QxLOqCT(6tSU(;NAA+VRb4disf8>Jh7$wm zAnkaU&zw}D4S2u1j#jt(q<-fPlqbpqG$bnH}BtwlPHl~Tsej>WYojFpZHPBSOi!750qsj>$vP&pXLaXpzz z(dJ2&Eb&K(cI*(;TzH^Kdv2)ng^+pW{xwC~9;^SLv^u!^3uoz8aBq;QmfE)h*n6vO zHe*<{bC8?1vX2dK-TDAa2M^V4joyw#(69EcoXqu!HQx%AmiV)=edDFp9vQ*h${IM) z@sWL7kx4Q8$`U3S=73hu7s&!Fc_933!;bbAiMV}x8xJw`E3+>Ku6ipP{PqFjwc>r4 z$$i{zH%&aiHf>Tqz%~a&>9%JCpfJ9~Q(wO+%nnoKSbLeBKL&}@>09G;I<@^tdq05f zZH<`n%=dP!Am3JpnPcylGrjGknIvv1ZI`7s-cZQ#m7JNVAhA;Z5c@WStW(cwKfX1jQ{HdgLF zHhwW;q;&*KyS(du@U8e$t*{yP0yQ^Atw?*VQ1XmxIbuB7LqnWl2=5qc*O>I|H8+o^ zLM_)jN{TTAb!c6NT-8VbmsTa#Ta?u(-@eGgV?mkoe!o`25G-MxD5L1*&_;KR6wc9! zr8_KShFu)B(XdE2=q1VJlky>#>V?)#`s^%PTs7fl_i|m6XENU*ElnD|p_&`q6t$Ap z1g(#lqNKvVm^Uai#}@DSJtKq~s*q)8gg4A+maZgveL2q=Q77zs$NmkTx6iaDPd+_%OTy`p zfcGYGDD*I!;^y}YP>*g|Uvb`d(1y|9>0u+?m9=Zf_yzUJz)~ypBO|omgisA?h##Fy zS)L|n9FnD$pXAVV@I9PfW9lAMw|bkLeL7?1&eq^{V_c;jUbmA$FPP^72oi6o9yoi1 z)d{2%s48EGHutR}6X6p=GANV2!OcLCtqFEZw)+WfF<94-Fg_E!p8;cSUntYBR>QOV z3-DjG=U@1nF%eYbzoMqGDWHFW|AQ-A{sx_Z{!hf45Bm5oR4?K9%3s1&0t)jF$}Evj zAs~QtMr8F3d~<>SgY199?d`mnslIifXaHTOxu0l!Ye4?2kAc+5N<9@IA7QF%kbP1m z`6h)!Omql)^C-&ca!@iT+ygyIuYY1<@N+&CW89R74NLeb$4K2~F1wDhJK8b`1$r_; z+@n;nN#JJSs@bALOxBD~wbp7g#U>8Zj~XMryoz_MaNpJ~ew*oEP9~+>*gi80!2ygQ z-{Y*<+ss8v=8d6jQuOSY_b0yOGmXM*RrN5os;IX- zL_bjr4>`PEA&YQwr9J|p@p{QAgw%;=_ln95l0^uXo9-r?)&;$=Ywfnwho8G+k#we=o(ZdDojj+oNj)}T-j8VJ4&V1rclEF<5R=7c2L#@mQ>FfII5^RK7w>yTWq|ItEr7$uiV+?Pq8I=#Lbn0@txeu6kAywPenTZ2eE3 z<*hwZT5uBhSdb2x9tlO#f;VV2n6Y2~UD^LE%9MI6J&2NTh2IeP)EnLJ+Q~Z%@{sYeuW&DQh%7hmaaxQ9Y@%2}eFKQAK@$N{a4^ z+?kMt1(8i2gA}X}viU>?$O-(PxH-HOJOnF@O3hl+zJ!4%pY+A)0@(D-z4r&)sY(2) ztSc$N$5nC#5W;pjCJ!2d8H>@xN|ATo&pJjxbNUo!sv1mMUDK|mxdT(GE+K687+R{M z30yKsTU+{FzEIwy^`VYw zq~Fk@1A^dNI_r|;LjIb;r)|c>eAzJ+{p%IQl_v_MNpP|><`mcxr1LEP_DL)IgCsiu zeaefV#60I8Sa3s$bwNTOkB283No(&1@$tVCRT$+W_~k9x*^df~vWoLV?xUx&w;P3X zvKPm!6dAS>BU)tCM4*W9_ zc|H_4a~+V}=8`FFQd4$D)m%8w$lCvixEpszyb{bUhf5K5Ms=`t!VwxN4UCi;t`{f{ zU9<3)?3k&r2;nTiO>ySsVY5Yfu~}j@MU{%t6~x?lM01B2kg~xQWIN=D@fG;rfJZ}! zk?cj{L`RVir?RDEO+{6Tu@nRY>Plz(%}w^%w6JZW0>r+rb=2k0P9N(hv2tT&MZrhQ ziDB*`N7IXO?9t$CL=g-xv3g>4Mb%i{GrwYO{W&(eXUSeXHiKjAE@JA%GUlye8pSf~ zEMyv79(t}GrXRK)eqwcHd1TaES{}eM?JQ!N#4>nSFbZNCcNaH8OMX8EjNr8FxKhVU zbpMXDrDfQc8k-a%D*1z?Ic5rTm!_0W7wzkA?^FBYSRhfmNJTyQ$# z^&E}aQ+)e>sCvifzMeN;yK!S%jg7`m8{4*RdpBxq+qP}nX=B^|#!r9$^{jQydA(=9 z*|XN%GkfmOb*;ETa{gI%p1|uF5~ZDxDN9_Ltgf`9jkFUitqX2on-Bp}${GsX>S-F< z#3B8Woqigvs$2=&vh0l6qKgFVb9gjHE30JJ&$a&eJzsN%G}J4OA4VT}Hk`HSTM|DV zlqmX@rD|?b&%MT-4a@sIHp$O5xwdLdCYjW=u`_)X);@iv$EfwN@72A#MDyLVcfD%x z3etX&s*%l^qRsHjSmJ1!!pmslIJ3dac;LXd;aIh)U$JQzGOL*4$hZJFcx~KP&+Aug z8%E4(rUWwHIbdv%Rtf6cCJyZ~yls%y22YX$Wgo~c5UYVJMON%BIjo=OrJRnz|nSsl0{M}5%Q2EkB3FG@gc0+z@ z8QPF2>>f6EKDRPMIyR8SqLqlYVQ(c;BC5~Ql;22gd@2H;4-<)cAR=9?=OX5^%oa+7 z!H^-QsMu(gE`l7kEYo12>oVOKN`z&bt~OVYUz5%;XPiGG+i?s#mryKxpLm>6tn8w9 zA9;etPxsP%g2hke)psANhv7SNAFhYhW%MFa6W(qAB3cu+$qYc`bBYQw2j;`)6Q_~P zt(pB1H;IXGq*C44txZ@xG3u98ZBKPl(!&O$I&l7eQHa1krJ%4T31^jARO*CUa^2xT zU9LN+Y=KP^j)z@BW2HNLMXY>e9DS^@z*=Gc8>jpPit5aH9|4jN?gXlzgNZMkmn&deQ}U)sZ{mWb3QEc`+b|;yTJw(5&DG^<@04WzicdaWzSWVNeUn} zWKU0kmZUGRYcgbg>w{=t1e`7i3KsjXUho%>O?6&a7fWL9wvlg7A#{@8$f~a;PV=rw z1bCJ|1pnUxXR1>{8Ho4g%M5>tzaqr{>KoSo50x+Cg*8ZAnq%GC%wPiJAV>L%!Ym<| zpP&1)4~1i_-weGW;|Dkn$JlRr$___&vV66Rt0gVlDozh;Oyo*1@?4Kx)lO@EEB;k0 zZS5GXwN(h^{(RPAFU~4EW6$5z|lf>aoAa59OXeAg_nduiX zeLA7w*I1Qw`Q5?MN6M0xm5aN0Aiyqf;s^a-?HweswumWw9rW7~XwpJuRjIqM5Uw5Y zWNgGt#P(KQ`fNHq*pbA%w#1eFYJ#_!`ULvu^6hC6nTHeBVE&ar#LZ zg%c>m6WE+{1f8_L1t_@89xO&=vkl{Lu@VSz-R6>IF2=lMfX7Ls7CW?}1Jk=vqNSYd zTFcL5Bto!f@FE3%9931U>!Q{3vul_m{Qj={YG^g#K`=tP`L6$BxOH&R=D9IG)1q$J zg>;LUT*_BnT(y)aa3XJ6XOiqa`lBS&VikTsJqVIU%Nnk0LZ|z*-Ay0U)6KA?M}aZt zhj?E1TYu{gy!c-cNK>dXz>L4HffEOA80#BOc)0dx_0mrDFG7@PwH=&P;SA^eWr5f2 zjs%D1T`c@DF1cU?1qNpM7{wisYFsG5@7+Uq*bmD$Wpl}=A1pIU3egsr_hqG;4CQ7_ z!mV{6>thoOARlcsSnc~lbzPdEiUWg$PaiOZRVv2y;n1Z8MHN{BJb2^r$zkbK2ysN3 zPZkLw?Xxti1j91N)p=pw8pLDbq5L{gR!K#8j`UPbC9N~VES9oKgIge9E*J72R4ZlH zHL6faC8=}6jQ%Aw{6;&Os8IqZE)}VgN%2~xm?vTM6EmE`Egg;`Rvtd|0kWX6?xUKq z#N|0N|J}E{bqM?&_=>KCkA(>7-2sluEAq?9+C^hHiC+ch7b~^G^!D4ztSm_?y`I^@ z^U&*zMF?}|SO%fdj(M2_N2ah*fTFoRVAJK$!$FrRjp~f#&aWR#-2WN(`uzF|K>`r(MIrs#yptV1C-nyln& z_?(=80X2`rY+jMwILzXp62Tc5H ztDvy4;p+#H{xc2Jx{5!f7{!?(*$$Juvs=W`gW$T3jwZ)nj%McWDbP(r8!7$-#A+`t zC%eU<+NtbN0GY>>|1k%e* zT$B~{)6W@O*T6GIp$qWy`uWf6()d594 z@p>yvfP`Ek(ihs7910#JuHun6WF8Bm?a|}<2iCf7hfM1$4R*ZyZ zYf1fK_dQ;puc{yV@91AvTE2-#rNFu3-gJMxp8HEthdfNZT&SAbUg^dpm<&G8^N8zu zxSk!;dSK1-&DW$~U2>5&`^pv5uVU(Gx396?fQs`?E|{~ej~0)V{_Bn{=H{H2_p5hc z7MMM=*!;&27&B9U!p&eo&FwBluBb{C6%ebnDQT6CZYF3`eW&J{b5}%?E6ql&y|I~8 zH-48te9)>tP5_9c@Ga~gxpr^}=lrXF#WkXjbOvs;!nbGd zp_A?UKKI%g@Ga#FFA#dUi^0*nifi>M4_at=IJB9@89o+oM!AoiS-J`v>%z93hM6c` zKX89IYngZ5uJBXgE6p*II9%ZZ_8_$hExeiLoSa$qkYsvz=VWV|iAa`5s@VLv?IzO& zv-zi6_`Extr7A|xe&eyicl@%>I(Hah-KYi{Y(mQ#r17$Fv?2VVMU+O-6xdDwWs zEgMsgI7WpAX*loEEi^LgxC*`b@3ecH_)0j;nD7c#s=Vp%w9}~4TI;ow39}zun7|R7 zTS7zgitL|B=orkCXwSk+noMDM(tZ}pbx|Viht)1~wh^_r!vWM2Sskzm@$VeZXrmd2 z5g_5YX^C3FVbk}%5vrG&mhG{E(Wx=?>ozQ=g7Zc?$NqKn>xJ*8qE!8MQTR=D;!Pb` z(m44lb_IcDSr=x;bwac)cZb7<34owWy6kWy!}{8io~kM;TqT)7LE{pmRSMj=Ia)(l z(6XF+Wd7;UrcsoP&X~=hZ4C4b+-S0ezrIe7jjF0+qt`b)_@|~bcuM}k@1!f|#ICgM zK_RM~-oA(ryL_55eyCBR;C}7c>4A<{mFjZ?x~h-Uk3(`T3r9Ni(2F7#&%kE3fqUd@ zR=!cf^=x`^xtG%`k9t(hvjAp>XfJs{v=m~@pOwXaCzg|M5zl0GT7nxD?H;{OnA$@@ zditsfHC@f)2RqS^290g{e`Z8P==;0W%RFeGrV|~@PevFjRPRT$56RhDVo#p^#5p;; zW@Y_Q$~I#>k7vFSoNVaG>I2FC{H@CYKY7M~!>it~2@oNw3@PtF!TCCcY>|(ZG-_?3 zAS&FoD+>M~@Z*qo$0pl2Ko8B<8KR4EyClUZDMNicYSkHHP%Bh5;5L6_RnZACKaD|N zWtk8|a(fO{R3yCO--eP>9~l{- zjlsH&3pI`FB3|`NcSSwwTKVDr5bZ>-4a>cd{X^o)@hq=Nv3pqi3OBUqg81QI!opMP zzOll?Jvo2ol!I6=1;VmuM2iGx&I?{ZQKDTwu_@b`6PY zhuHZ3zKUxLSA9YV;bm%&D9gtpMt{wL6xj85rdWoy(H+Lym4~W9Al{MEae26 z=|Shz)#$e-JB?4B;kFJ)#!Tg{B1S!Gj=_U|1xE0h#-iQtf6qZY-G+Tz@gtPYznv%X zy3-K0ueio_#(_I8mW+A7Jj z^VzE;D|$UF2Vz#i9Tp?F5wBK+dX_+0p}^5lJsy9Wl)=)Xa|J03Ji<_s*6iAEgkSa& ztk#*6u29q98t&Cen3nD*M`@(oU7L0VX>R!tp{CRE%z>tut?>4aG08o9GDZBfQ$e3V znt}>=zv^9V33uEE58?=?IN3jK0|E?O`8{ek_|X;+Nfa{wK0R-oSov8x-YI@3V!gsf z3GFvI*Kijf zP0d&MaXRvMu zc3^Xy&+zJpxx6g%EFCdFgTk;S2Or7_!Xl)n`v`@$2S*IKbeoXN-_Qu;t3;kRBWrmZ z2$>S?a(_QczfUN54cHd2U*K+|dz^OtccABV5MT@m(EE<~Lx2QVnDu%IMloYef8l(W z>>Z0`+QvYXTiRwr#IkT`hc>sk{z{GEPz)WjxJ_TxB@sA;M{mfrJY6ldXrm>{?Tbnq zs`UkWj>;X|fbQ~FL33YShk@N05||mtA z$TngzsRYJ&v7@&HmzvgQ7G}cH<-)7=16mqT@3nZ)y-dqlbn38OMxVHQge8OUz9{oH zj{H;8U9|rM7eT|iD0bFcuq9W&Tq#75`d$zXTs=7a$8G{ixM~vpgdMY)zIW1FTjO z%T19jaS47-NF%FDm*226@LI57oqg+R_u<^m^~Tnj(`tvP_Q~}66*^(f7F?L~I~2XR z>q=f}Jdeh-;8RbWE=4L0?2Mib50s=d9WpUWOS!v#bnP*UBK`O)tGA3Zpv6=9Pdo#VDfcYu`#Mt81;TTkATU3N?xK#Z@`ei7VWa& z2g_VG$yIi6IGwL>$al=PB3vp32u!T>I41Hov-rob0cA`2u^bjidDZk=nSB@hzHIYy zHoE{S%QSC}UIM)?wo&JF#B6i?+3EA&*dg10Y{JwFDLnqlTl}@gHze@ql)&8neSh`N9^FZr4KSDDeQmI z5yk3CKL;2Hq5de6wiD%>+^mhEwhSO%bF4!W_e~kKU`c2Xxu2nfeyml=4|dal@AYA4?G63W zgf5U${ke4Ry@Kr?@u3s_1)=EKc6s|kvd889ULZsHdU5N5=3V&+_pt!Zgb9;4V>>9m zDiplp+az^unosm~w`l=IoJ+$TC8sTn8Q-+EoXw_9Iq)UB1VBH6g4@R4{^^0})^}#| zDcHYo&BzC@-#>AU*$KB5|J*Ila{fm$!y00tw@8-9jNOH&-&39bvy?$If!lg-F>Cyfp`z z_`!AZ4u#R1B7jz0A7hh_A2|l^(#b;Nwsoy>YvbPgo9PpA(U$VPH_3^+HrmIg;fFx( zR>|s>bJXGUQ@szZ=ZOl=dUzY!zGN&@9~-P0Gc|618h&p#W(1@%F%=Dcl0h@1xU>U( zV9=jw{66bFG5y{@Xp_IExKNvSOgwN(deONEvxmsGO@T~Udjp!z7}IO_Oyry$a1dU_ zC}#OwfZSyN>Y&(;q9p#ouS$Ev2F*7rW$@J&^jelEK?cbODt9gO%owU-+)id!2jkT~ zs<=a^T5;!L*n!ON_}KNFyZtKm_i>yYRAHI5J)CMqh;_v(Ls|>89REZdTMhf$2uOAO z1}t_QHb68TnP{aHT3lTZX~b^*DVrvDf_nKUN&r_ zliA*G5RYQ)??Oh=*+#WhN`qJgHR)J1rp z!4N}ily(B+AFo0U5_IopZiH7hMDh?i#6gtbO{z5|Y)0f<2|0GjiHSF4igX+8@!8+T zAC92)cTGq70v$qcUy!tJ<){Zkg3u_GG=MiRHuTrA(|VC@buLi@S*@upg*3F+c%VU_4O{d`b-2t*R)fQ~cG1Dg7;I*K6M5XL>7 zJId8&KIIdX>Z2Cgey2!bk4?8@YUz`81|D;z6qdO*-jPmJA{@7gbSg;QTCic+6+nW& zWtYOFRMw%)K)?NGEhBcNYmhEUt-z^PWegokKT|A!4cf&+kS<-Vm2TvD(b(FzRWnq# zg5F|s&P`Gcw|+Y+`Wqw{Mj0|z0VrG$+x{*qGj2{WxKA|4uIKFt_Kt=R?BR}mE}XtU zj}Imvh=+b;O9;Cgmc$1h`r4KYAK3DbxJGuM>4yKa=Y9R<12%h5a3BIC&>0K_%U|1H zL-~Dqy%u)a7Eg^KHir5jJlZYkhdtd_JC*zKm+j_p5J#|x7JP3+Ij|S>yeN^5>p&

H~J)yD^)9n9U7abaBJGm7|8FTYW6iG%-u~H&m#s}WXNEUd{ z-=E1X*xQm;4;G;Mfr;;?Y!BP_hHUl2t-}+72yI86YB8TE_=RmEMpig{ex#??{xmcq zZiIKnGk~(a#=1e611nLe2e!?>4%&hYI!WWXMR@p}{~4Pvs@^o^Y|7cbc4RVS*ZpEVI=`gu`Fh(j-WwdW11X=DgvVLn@*&aoFuWWef_3ZhZ%la2|Hcvv{ z8{7DnoJaz?n)!4)7ko<>RtzifRTuA%pUu?-KMS%A3%r!Xs$@B7XEI+TTqr(8#p{Hk zjRMD{peGercBm=fqm_qyeN5?9X!df|#)6mrIj!hCQIskBfR%BRR*5v|6dEXpC>BCH zH8{fv7I?oBi~>oTK1V4UIkjh<4u8&bNn&d5Xc$JHP&zjkicrift1icI7A{=qCz&vU*9lrqGb*m=ClLS~bqL*%cCS8rE_8S%4$p)ErjpLu>IrUbj+$u9g92~mT72_2!o*p6^vCZNn{iRlw0Rtz}& z(r+ni=qb&sNsg+5eTtCGXrv9Qm5|{vk?9rjdMnuar4AAc2OM1A4794c#_$c29DIUT z*Mjx{{9{hZ);d(IewVkDukh6@x4rlLlk=1Q@IC&OU9qyQOFYftpMqp6d7t>8=_rRN z=7~uV86@U$8B<&7r>0wF(x+)tV_}_6w<#TZ^R9W7cQ03~4b{g}o%p$QS@^5f*KWLw zm0H-yiO`p1>I6BT8Ch<;qHoHcJf=%Td|WXO_R*jVp&02dbP5sOw z;Q`9@AC$;`f{a!`mzNXsc+sY~_+(M4co%DeN=p0Z8ONJVtdAAFs*3V4-@k)wZjb>D z0n7+GA6R+bZv%!*aDjlSStLS8-aOZ#L6Kw6k(6`|5Wrn%W+9j32$sD&jvqGts|1Emg{72Ok1ZqzUVz{owogLBV?^={24A7xE_w zY0zLYlf*YjZdgYja!LWEOiEe2uQT%v45ZTG_Xx1sEkunjbo^Krn#WMKmaVJM@uRRb z>K%*>flWnE8YgvCl^t+r?NLpGuOFXRlF6pH>&wLAFFl`E-N20J;kmK=7Yzz%HV;U( z;mKv7?ym%C+WsE}YszGasKNv@YsRD#JfdbJH*0Yo_Dq~P1&Z2hu{+8JcFSThEtubS z8>qbZv4E{q|x*HevCmY=@#<@Km=69Qy*_(|123Hs{KY67s zZNb?S;~J(Cy9Q*UwRylhoiHrIXimT*!q-;;-e#AJi}NJ`-FFCI-9R)_1;Uo!!(&S4SgBXnSBksFi|u*t(MWl#>HC{$qe}Ck zNm4O!oqim*BIC{VoM~grQ*a53@C50yFzy;FI;2_oBLrKKYhfm5Sgf3@=4PQs_%e|| zp^P`5^dki8_vI;mW@l0Ft5f#)VpjeriAJt*tzHCh~3F7W5c6t;h$ zR8so{{f$l9xZGE-XN`}#nELt@WeNaeA|XLvm~_Miz}DYf38Jd7~`lsTD00} zMbJsyB2y{dOxgL@q$a@HvK$rz{v?AI9|C9FxiS-?P)VxL*X0af;BBe~R7~-Dd#Tab zIQwGL%gJZb#MbsU6Z!-qEhXv+%!CjNV~aVJmccsK4ei99>tJ3HO@6vta*kc{GQSO< z9OIQ4XM$P9@!4@We1SQkdz`DxT1%@HHy?}KP&yD~xgPQ06^%}B^D80*UcuKKza>YP zC~KcEtzDEO5Vh5p^JeMMExhPLC;u=v-*+Wpj9ZcR`^Wz$+nIzO2G5q>DxG~URJvky zAW5!|MQigP*S*d(S&f`2eB5*MQ5b-MRQn{VNA&qhY!*pj?~iYmCCK`+d1ZZaaXjlF zZg^VZvz9DtURjw{>*PlO^jjekGZd1gfNR?RsVCObL<`<6|b^ zP8fnK1f0I+B#QZk-#NmaI*{v(bb;p~u_j5a8ETdqmW+DJm5VKf%}5;PU~;vv5w^sb zEpSehtMds^eJPuKhbHO`wBlI15N>mET5m2SJ%O*E4u=_7>`cfbIyx{yZ^uZQ|?^s*Cyg&W8-NBL?+tVd<&?Bvzw1~p2{ z;mSu7r4e=NJV~J*z-qh3@i(0+V}i`zBr1!6HQJVurT9Kp=nv?b`o*gEJu?>dWmdQ< z^Oooy(^tE!W^^fdS?=(biG@4p?x?HTh&I8=h-wMq?hs-N(8s;M1GBldF|=Q3PeEWM zJ+GMVcbew7B-!7|+`~9;{0&X!B&|Z#+y$PAU^o+inb2Lnxs$%}-dCTwzMu6Gk$yVJ%wm-zq zTJkQPGVdzz>cTsMh;MM8LluQdcySQ^vfbj~A8Wmz+rUb%^PVub`}p$Glgg+W;^#<_ zSF&6Y`N3ac7F0ypN?fzsG7ppT?3nB~K(0R4hIWKN5;RgaQ1;}1xFX&!gJi2@lSEPu zr6190wyx=UD8*z|kbBIzxQb%#=cL#oJZF&?9z18cqAnd@$93~d;%ka-n|_y~>$JE6Jbk)loGR-chc=0f)23$^<7Q=| z!;{JEB#iF6eAQvQZ}k@r%T)!fgyHZSY;@ZFtnLd@Mr9=Z^QP?UOgw*2M&`NuUFst5 z+JeZ{AnS4molZ635;=lwErU$F)GA~Bb!nJcj#4_?deT*;pk>*<>L~EIyezo2R6dj~ z>9QpQF45WkXg{36atE=&&iB)do>?(zTc94@#r!O<&fx6nYypb;13Fb350NgM*G#>t z>AMnSg{I{))WYPhA*($eWBg z^klIhrtUr)V_8asdkyg8ArwBAXx@FyDIgoaX-hQ!+I!rZ6+inw-^ zIW#y0qeZo}Gp6t-7Dr+ykl$IM;5}Ze?>LFHQm%{R&huX=9q`$4K_QiV zkXcv=1mi6%Ul!@kmu_LtbXTMRRGXY~DO2Meb+;l{-&M zk+X^Y7ox#yH&jwPcQ{9a4757%Gvp8|2h=ix=Man}%e=J%s+z;n>6)@hTbe|rh*F6= z-&*q_H>0%aQ0!@`HdW$4{2umDyKCcN9du})xSM+Zpxcm%(3&!dx1%9~E6V1eB{5Q} zn-BC>+2X&pBRAMGHDxk6HGDn*gD>|e+?rm0!}7sN91!N@VfCc^ND6%cvLsk#_vd}h zkl!Plob7P?9)c{teaR=_2+1&4ZnV2OfM7u@tph_)p8n6tim*wQL-4{Ey?>+7$fJ@q zf}O7_u0U87%?3jZ%Q(6B7q(Lwnu)8AuN{AaG~T;(T=u)(qhu*rVCfXlci@>BUnx~_ z>;h#fwA42e_;henD1zk29n%v&O?KzXc4bZFplY#MUHTcuE^vkS9(o=0=K4}zx{Nb_ z3wkkpQ*ugjl3t9>?cR2d{$Xv*41W14Lf$ya*Tnr zgnQz0;lUQdhtkq>`R)Ln=Nr$dwc5-))~97>JKa9~Gu3s5#Wp#hSKzagB$JptBApg7 zeKJ|l9cy1Vd)x1>!4~8brIh$pT&TN~IsYT}6CoMXs7qrqrTKZhK(=qO>=YW-Q}ni< z?xs@%2a!$ci}6bpjVfYURvdOj0sAE&5Gz-7BW6Stwc#SIfg- z_ZcByMbGWTYW4;IwnHNy-}G3;t(}iDuC;LcZvz#vVayUY8573CsdrasF0x}}=+~Bk zJtHBuwhfSZrN_#(>v}J$Q9V4eA-~c2xZ}M+q6+6O5622Bd0ekwBw2*F1MB4o7++u@ zh|#Vu;(VsZ07f9fy^f|wn{At;4(lve)At$qX3Zc^jer z{d!nIb^_O-Cz0n2JlDxHx16Wma7riLbt`8yRj2#5F-Ej zGS!8<^7PSnk=8vfsbOa1;qHl44sRBlR=vSVmSF)a50CiuTbM8W<%}H_R>oZ&tegA_^WDc$yDFaHEo~v zx?>bzHt0lcZmd7qmAw32V^7o)uLYtlptkJ!NyX2{44-l0mYD=(x-01EZn2CV+&^$B z2NO9)b->yfP)_i>=%6z8zaDXE4B1*|uV%FcgjVIdL)VB^V;pp5 zQ`8)p?NB^9j#bFdhkktiUt%D{|1v9{KEcU9{(+>fQof6U5hd3^!nJ^oAyof|hx+fi z$>+<<`oDAIH~jxKM*eR=@CC#J;(vJ+^MBERVY1blanvtg{AvHkkNR&w%0VX|4$!3W zPZYkC_6d-u@v^p&kdq9eE{4L9AcIpk%6X~S*8t)=>++0DAc_ixjNZcF;dOq)ApO;}oGPq6-B zw)gaNxAXMVd(Y=n*6aA|3?Ga^Fu-SFki31KZSe3zK8FzQW=@y?uW`sLX`X>AJ)cI8 zbIB5Vjh%e6R)Jd026~JLq6z$=4$(ShB@)zt?-IjCiu2!J;>_w#t8u4pbXG9Jk4t@* zu@tE-d47BC3{ix-+FBhPjW)KIR+aXfi*=gc5Cb~f|Fw1@|K7{$n~hC^!T>JNJzgpu zOn&8?Mk$}qB8^lXR~wXiC1h*TF+&H;(N$W=D6E%0eaC1KUOLO4+s+o_#X3UJ@7Tab z8V<=<@v&>?t|-y1lB`keNM1Fg;^h>}SEFat;I4Avv(j8b+#4({b+B*{-Y<9H6%T}n zQfp<}WEw>h4d@CWRK_z{6b0C}bYTIzmquf*7H~lCuIt zO{37kM(&CgH3LK`u?iwlv}{8zKw9~EHn)QmS$c`+z>-w{ya;Bp5onWWJs7oe(lA%Nf1^69{FpiM)pt&iYxt*RghTGWQq7-O%qN&4 zC*nCBOK~zqmY>NEcyNW-PqmeJIbz5u%J6N~DpP1i42+^3bU`gg|MYo7Qsf3)>l?oe z$Rx~jipgY?MhNqF%)e$vyo|*muvklk%5cSv`&@|Mp@)l(SpbK$?aD|<85g69UUEcj z$!^kbENk{+&8}f8Syh-_R{YjNrIZ6cym5O9-jpZ3OLU_bYBwjbmHrbY)2_sW(|&)W zAz)-MauuFQjEqp!=aD(6pxGghD1_C#d-^VFrbH2hp;K|;sAAIp23LFyt!?5J_

R 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

s!t&W|)iGy(E_CgQ0@H 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 z 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 - + From f48d4cfa79cdae7bc02abf913cbd1ff3b8716786 Mon Sep 17 00:00:00 2001 From: Roger Zurawicki Date: Fri, 18 Nov 2011 00:19:59 -0500 Subject: [PATCH 34/44] Bug fix: fully clipping GATKSAMRecords and flushing ops Reads that are emptied after clipping become new GATKSAMRecords. When applying ClippingOps, the ops are cleared after the clipping --- .../broadinstitute/sting/utils/clipreads/ReadClipper.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java index 6e4ddddc4..e4806838d 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java @@ -121,7 +121,7 @@ public class ReadClipper { public GATKSAMRecord hardClipSoftClippedBases () { if (read.isEmpty()) - return read; + return new GATKSAMRecord(read.getHeader()); int readIndex = 0; int cutLeft = -1; // first position to hard clip (inclusive) @@ -171,6 +171,9 @@ public class ReadClipper { clippedRead = op.apply(algorithm, clippedRead); } wasClipped = true; + ops.clear(); + if ( clippedRead.isEmpty() ) + return new GATKSAMRecord( clippedRead.getHeader() ); return clippedRead; } catch (CloneNotSupportedException e) { throw new RuntimeException(e); // this should never happen From 768b27322bda816e6c6c5a05b4a6dcbb74f1efcb Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Fri, 18 Nov 2011 12:29:15 -0500 Subject: [PATCH 37/44] I figured out why we were getting tons of hom var genotype calls with Mauricio's low quality (synthetic) reduced reads: the RR implementation in the UG was not capping the base quality by the mapping quality, so all the low quality reads were used to generate GLs. Fixed. --- .../DiploidSNPGenotypeLikelihoods.java | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidSNPGenotypeLikelihoods.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidSNPGenotypeLikelihoods.java index 666fe88a3..295cf8688 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidSNPGenotypeLikelihoods.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidSNPGenotypeLikelihoods.java @@ -28,7 +28,6 @@ package org.broadinstitute.sting.gatk.walkers.genotyper; import net.sf.samtools.SAMUtils; import org.broadinstitute.sting.utils.BaseUtils; import org.broadinstitute.sting.utils.fragments.FragmentCollection; -import org.broadinstitute.sting.utils.fragments.FragmentUtils; import org.broadinstitute.sting.utils.MathUtils; import org.broadinstitute.sting.utils.QualityUtils; import org.broadinstitute.sting.utils.exceptions.UserException; @@ -275,19 +274,20 @@ public class DiploidSNPGenotypeLikelihoods implements Cloneable { public int add(PileupElement elt, boolean ignoreBadBases, boolean capBaseQualsAtMappingQual, int minBaseQual) { byte obsBase = elt.getBase(); + byte qual = qualToUse(elt, ignoreBadBases, capBaseQualsAtMappingQual, minBaseQual); if ( elt.isReducedRead() ) { // reduced read representation - byte qual = elt.getQual(); - if ( BaseUtils.isRegularBase( elt.getBase() )) { + if ( BaseUtils.isRegularBase( obsBase )) { add(obsBase, qual, (byte)0, (byte)0, elt.getRepresentativeCount()); // fast calculation of n identical likelihoods return elt.getRepresentativeCount(); // we added nObs bases here - } else // odd bases or deletions => don't use them - return 0; - } else { - byte qual = qualToUse(elt, ignoreBadBases, capBaseQualsAtMappingQual, minBaseQual); - return qual > 0 ? add(obsBase, qual, (byte)0, (byte)0, 1) : 0; + } + + // odd bases or deletions => don't use them + return 0; } + + return qual > 0 ? add(obsBase, qual, (byte)0, (byte)0, 1) : 0; } public int add(List overlappingPair, boolean ignoreBadBases, boolean capBaseQualsAtMappingQual, int minBaseQual) { @@ -511,20 +511,19 @@ public class DiploidSNPGenotypeLikelihoods implements Cloneable { * @return */ private static byte qualToUse(PileupElement p, boolean ignoreBadBases, boolean capBaseQualsAtMappingQual, int minBaseQual) { - if ( ignoreBadBases && !BaseUtils.isRegularBase( p.getBase() ) ) { + if ( ignoreBadBases && !BaseUtils.isRegularBase( p.getBase() ) ) return 0; - } else { - byte qual = p.getQual(); - if ( qual > SAMUtils.MAX_PHRED_SCORE ) - throw new UserException.MalformedBAM(p.getRead(), String.format("the maximum allowed quality score is %d, but a quality of %d was observed in read %s. Perhaps your BAM incorrectly encodes the quality scores in Sanger format; see http://en.wikipedia.org/wiki/FASTQ_format for more details", SAMUtils.MAX_PHRED_SCORE, qual, p.getRead().getReadName())); - if ( capBaseQualsAtMappingQual ) - qual = (byte)Math.min((int)p.getQual(), p.getMappingQual()); - if ( (int)qual < minBaseQual ) - qual = (byte)0; + byte qual = p.getQual(); - return qual; - } + if ( qual > SAMUtils.MAX_PHRED_SCORE ) + throw new UserException.MalformedBAM(p.getRead(), String.format("the maximum allowed quality score is %d, but a quality of %d was observed in read %s. Perhaps your BAM incorrectly encodes the quality scores in Sanger format; see http://en.wikipedia.org/wiki/FASTQ_format for more details", SAMUtils.MAX_PHRED_SCORE, qual, p.getRead().getReadName())); + if ( capBaseQualsAtMappingQual ) + qual = (byte)Math.min((int)p.getQual(), p.getMappingQual()); + if ( (int)qual < minBaseQual ) + qual = (byte)0; + + return qual; } // ----------------------------------------------------------------------------------------------------------------- From c62082ba1b5d1c1b29842f671e5ef0ff6ba60d40 Mon Sep 17 00:00:00 2001 From: Eric Banks Date: Fri, 18 Nov 2011 12:34:27 -0500 Subject: [PATCH 38/44] Making this class public again as per request from Cancer folks --- .../sting/gatk/walkers/genotyper/DiploidGenotype.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidGenotype.java b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidGenotype.java index b5987963f..106bb1982 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidGenotype.java +++ b/public/java/src/org/broadinstitute/sting/gatk/walkers/genotyper/DiploidGenotype.java @@ -34,7 +34,7 @@ import org.broadinstitute.sting.utils.BaseUtils; * Time: 6:46:09 PM * To change this template use File | Settings | File Templates. */ -enum DiploidGenotype { +public enum DiploidGenotype { AA ('A', 'A'), AC ('A', 'C'), AG ('A', 'G'), From 8bb4d4dca32e3b8521c2d7ba22db5a5e9966cccf Mon Sep 17 00:00:00 2001 From: Matt Hanna Date: Tue, 13 Sep 2011 10:49:16 -0400 Subject: [PATCH 39/44] First pass of the asynchronous block loader. Block loads are only triggered on queue empty at this point. Disabled by default (enable with nt:io=?). --- .../src/net/sf/samtools/BAMFileReader.java | 762 ++++++++++++++++++ .../src/net/sf/samtools/GATKBAMFileSpan.java | 13 + .../java/src/net/sf/samtools/GATKChunk.java | 16 + .../net/sf/samtools/util/BAMInputStream.java | 72 ++ .../util/BlockCompressedInputStream.java | 483 +++++++++++ .../sting/commandline/CommandLineProgram.java | 10 +- .../sting/gatk/CommandLineGATK.java | 4 +- .../sting/gatk/GenomeAnalysisEngine.java | 146 ++-- .../arguments/GATKArgumentCollection.java | 22 +- .../reads/BAMBlockStartIterator.java | 128 --- .../datasources/reads/BAMIndexContent.java | 195 ----- .../gatk/datasources/reads/BAMOverlap.java | 29 - .../gatk/datasources/reads/BAMSchedule.java | 18 +- .../gatk/datasources/reads/BAMScheduler.java | 136 +++- .../reads/BGZFBlockLoadingDispatcher.java | 85 ++ .../datasources/reads/BlockInputStream.java | 436 ++++++++++ .../gatk/datasources/reads/BlockLoader.java | 188 +++++ .../datasources/reads/FileHandleCache.java | 231 ++++++ .../gatk/datasources/reads/FilePointer.java | 46 +- .../datasources/reads/IntervalSharder.java | 445 +--------- .../reads/LocusShardBalancer.java} | 40 +- .../datasources/reads/LocusShardStrategy.java | 178 ---- .../reads/LowMemoryIntervalSharder.java | 68 -- .../datasources/reads/MonolithicShard.java | 34 - .../reads/MonolithicShardStrategy.java | 77 -- .../gatk/datasources/reads/ReadShard.java | 9 +- .../datasources/reads/ReadShardBalancer.java | 115 +++ .../datasources/reads/ReadShardStrategy.java | 183 ----- .../gatk/datasources/reads/ReaderBin.java | 33 - .../gatk/datasources/reads/SAMDataSource.java | 257 +++--- .../datasources/reads/SAMReaderPosition.java | 120 +++ .../gatk/datasources/reads/ShardBalancer.java | 21 + .../gatk/datasources/reads/ShardStrategy.java | 31 - .../reads/ShardStrategyFactory.java | 117 --- .../reads/utilities/FindLargeShards.java | 10 +- .../reference/ReferenceDataSource.java | 64 +- .../executive/HierarchicalMicroScheduler.java | 3 +- .../gatk/executive/LinearMicroScheduler.java | 3 +- .../sting/gatk/executive/MicroScheduler.java | 16 +- .../resourcemanagement/ThreadAllocation.java | 93 +++ .../providers/LocusViewTemplate.java | 3 +- .../datasources/reads/MockLocusShard.java | 3 +- .../reads/SAMBAMDataSourceUnitTest.java | 223 ----- .../reads/SAMDataSourceUnitTest.java | 147 ++++ .../traversals/TraverseReadsUnitTest.java | 16 +- 45 files changed, 3292 insertions(+), 2037 deletions(-) create mode 100644 public/java/src/net/sf/samtools/BAMFileReader.java create mode 100644 public/java/src/net/sf/samtools/util/BAMInputStream.java create mode 100755 public/java/src/net/sf/samtools/util/BlockCompressedInputStream.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMBlockStartIterator.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMIndexContent.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMOverlap.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BGZFBlockLoadingDispatcher.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BlockInputStream.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BlockLoader.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/FileHandleCache.java rename public/java/src/{net/sf/samtools/GATKBinList.java => org/broadinstitute/sting/gatk/datasources/reads/LocusShardBalancer.java} (52%) delete mode 100755 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LocusShardStrategy.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LowMemoryIntervalSharder.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/MonolithicShard.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/MonolithicShardStrategy.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShardBalancer.java delete mode 100755 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShardStrategy.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReaderBin.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMReaderPosition.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardBalancer.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardStrategy.java delete mode 100644 public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardStrategyFactory.java create mode 100644 public/java/src/org/broadinstitute/sting/gatk/resourcemanagement/ThreadAllocation.java delete mode 100755 public/java/test/org/broadinstitute/sting/gatk/datasources/reads/SAMBAMDataSourceUnitTest.java create mode 100755 public/java/test/org/broadinstitute/sting/gatk/datasources/reads/SAMDataSourceUnitTest.java diff --git a/public/java/src/net/sf/samtools/BAMFileReader.java b/public/java/src/net/sf/samtools/BAMFileReader.java new file mode 100644 index 000000000..5005b6265 --- /dev/null +++ b/public/java/src/net/sf/samtools/BAMFileReader.java @@ -0,0 +1,762 @@ +/* + * Copyright (c) 2011, 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 net.sf.samtools; + + +import net.sf.samtools.util.*; +import net.sf.samtools.SAMFileReader.ValidationStringency; + +import java.io.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * Internal class for reading and querying BAM files. + */ +class BAMFileReader extends SAMFileReader.ReaderImplementation { + // True if reading from a File rather than an InputStream + private boolean mIsSeekable = false; + + // For converting bytes into other primitive types + private BinaryCodec mStream = null; + + // Underlying compressed data stream. + private final BAMInputStream mInputStream; + private SAMFileHeader mFileHeader = null; + + // Populated if the file is seekable and an index exists + private File mIndexFile; + private BAMIndex mIndex = null; + private long mFirstRecordPointer = 0; + private CloseableIterator mCurrentIterator = null; + + // If true, all SAMRecords are fully decoded as they are read. + private final boolean eagerDecode; + + // For error-checking. + private ValidationStringency mValidationStringency; + + // For creating BAMRecords + private SAMRecordFactory samRecordFactory; + + /** + * Use the caching index reader implementation rather than the disk-hit-per-file model. + */ + private boolean mEnableIndexCaching = false; + + /** + * Use the traditional memory-mapped implementation for BAM file indexes rather than regular I/O. + */ + private boolean mEnableIndexMemoryMapping = true; + + /** + * Add information about the origin (reader and position) to SAM records. + */ + private SAMFileReader mFileReader = null; + + /** + * Prepare to read BAM from a stream (not seekable) + * @param stream source of bytes. + * @param eagerDecode if true, decode all BAM fields as reading rather than lazily. + * @param validationStringency Controls how to handle invalidate reads or header lines. + */ + BAMFileReader(final InputStream stream, + final File indexFile, + final boolean eagerDecode, + final ValidationStringency validationStringency, + final SAMRecordFactory factory) + throws IOException { + mIndexFile = indexFile; + mIsSeekable = false; + mInputStream = stream instanceof BAMInputStream ? (BAMInputStream)stream : new BlockCompressedInputStream(stream); + mStream = new BinaryCodec(new DataInputStream((InputStream)mInputStream)); + this.eagerDecode = eagerDecode; + this.mValidationStringency = validationStringency; + this.samRecordFactory = factory; + readHeader(null); + } + + /** + * Prepare to read BAM from a file (seekable) + * @param file source of bytes. + * @param eagerDecode if true, decode all BAM fields as reading rather than lazily. + * @param validationStringency Controls how to handle invalidate reads or header lines. + */ + BAMFileReader(final File file, + final File indexFile, + final boolean eagerDecode, + final ValidationStringency validationStringency, + final SAMRecordFactory factory) + throws IOException { + this(new BlockCompressedInputStream(file), indexFile!=null ? indexFile : findIndexFile(file), eagerDecode, file.getAbsolutePath(), validationStringency, factory); + if (mIndexFile != null && mIndexFile.lastModified() < file.lastModified()) { + System.err.println("WARNING: BAM index file " + mIndexFile.getAbsolutePath() + + " is older than BAM " + file.getAbsolutePath()); + } + } + + BAMFileReader(final SeekableStream strm, + final File indexFile, + final boolean eagerDecode, + final ValidationStringency validationStringency, + final SAMRecordFactory factory) + throws IOException { + this(strm instanceof BAMInputStream ? (BAMInputStream)strm : new BlockCompressedInputStream(strm), + indexFile, + eagerDecode, + strm.getSource(), + validationStringency, + factory); + } + + private BAMFileReader(final BAMInputStream inputStream, + final File indexFile, + final boolean eagerDecode, + final String source, + final ValidationStringency validationStringency, + final SAMRecordFactory factory) + throws IOException { + mIndexFile = indexFile; + mIsSeekable = true; + mInputStream = inputStream; + mStream = new BinaryCodec(new DataInputStream((InputStream)inputStream)); + this.eagerDecode = eagerDecode; + this.mValidationStringency = validationStringency; + this.samRecordFactory = factory; + readHeader(source); + mFirstRecordPointer = inputStream.getFilePointer(); + } + + /** + * If true, writes the source of every read into the source SAMRecords. + * @param enabled true to write source information into each SAMRecord. + */ + void enableFileSource(final SAMFileReader reader, final boolean enabled) { + this.mFileReader = enabled ? reader : null; + } + + /** + * If true, uses the caching version of the index reader. + * @param enabled true to write source information into each SAMRecord. + */ + public void enableIndexCaching(final boolean enabled) { + if(mIndex != null) + throw new SAMException("Unable to turn on index caching; index file has already been loaded."); + this.mEnableIndexCaching = enabled; + } + + /** + * If false, disable the use of memory mapping for accessing index files (default behavior is to use memory mapping). + * This is slower but more scalable when accessing large numbers of BAM files sequentially. + * @param enabled True to use memory mapping, false to use regular I/O. + */ + public void enableIndexMemoryMapping(final boolean enabled) { + if (mIndex != null) { + throw new SAMException("Unable to change index memory mapping; index file has already been loaded."); + } + this.mEnableIndexMemoryMapping = enabled; + } + + @Override void enableCrcChecking(final boolean enabled) { + this.mInputStream.setCheckCrcs(enabled); + } + + @Override void setSAMRecordFactory(final SAMRecordFactory factory) { this.samRecordFactory = factory; } + + /** + * @return true if ths is a BAM file, and has an index + */ + public boolean hasIndex() { + return (mIndexFile != null); + } + + /** + * Retrieves the index for the given file type. Ensure that the index is of the specified type. + * @return An index of the given type. + */ + public BAMIndex getIndex() { + if(mIndexFile == null) + throw new SAMException("No index is available for this BAM file."); + if(mIndex == null) + mIndex = mEnableIndexCaching ? new CachingBAMFileIndex(mIndexFile, getFileHeader().getSequenceDictionary(), mEnableIndexMemoryMapping) + : new DiskBasedBAMFileIndex(mIndexFile, getFileHeader().getSequenceDictionary(), mEnableIndexMemoryMapping); + return mIndex; + } + + void close() { + if (mStream != null) { + mStream.close(); + } + if (mIndex != null) { + mIndex.close(); + } + mStream = null; + mFileHeader = null; + mIndex = null; + } + + SAMFileHeader getFileHeader() { + return mFileHeader; + } + + /** + * Set error-checking level for subsequent SAMRecord reads. + */ + void setValidationStringency(final SAMFileReader.ValidationStringency validationStringency) { + this.mValidationStringency = validationStringency; + } + + SAMFileReader.ValidationStringency getValidationStringency() { + return this.mValidationStringency; + } + + /** + * Prepare to iterate through the SAMRecords in file order. + * Only a single iterator on a BAM file can be extant at a time. If getIterator() or a query method has been called once, + * that iterator must be closed before getIterator() can be called again. + * A somewhat peculiar aspect of this method is that if the file is not seekable, a second call to + * getIterator() begins its iteration where the last one left off. That is the best that can be + * done in that situation. + */ + CloseableIterator getIterator() { + if (mStream == null) { + throw new IllegalStateException("File reader is closed"); + } + if (mCurrentIterator != null) { + throw new IllegalStateException("Iteration in progress"); + } + if (mIsSeekable) { + try { + mInputStream.seek(mFirstRecordPointer); + } catch (IOException exc) { + throw new RuntimeException(exc.getMessage(), exc); + } + } + mCurrentIterator = new BAMFileIterator(); + return mCurrentIterator; + } + + @Override + CloseableIterator getIterator(final SAMFileSpan chunks) { + if (mStream == null) { + throw new IllegalStateException("File reader is closed"); + } + if (mCurrentIterator != null) { + throw new IllegalStateException("Iteration in progress"); + } + if (!(chunks instanceof BAMFileSpan)) { + throw new IllegalStateException("BAMFileReader cannot handle this type of file span."); + } + + // Create an iterator over the given chunk boundaries. + mCurrentIterator = new BAMFileIndexIterator(((BAMFileSpan)chunks).toCoordinateArray()); + return mCurrentIterator; + } + + /** + * Gets an unbounded pointer to the first record in the BAM file. Because the reader doesn't necessarily know + * when the file ends, the rightmost bound of the file pointer will not end exactly where the file ends. However, + * the rightmost bound is guaranteed to be after the last read in the file. + * @return An unbounded pointer to the first record in the BAM file. + */ + @Override + SAMFileSpan getFilePointerSpanningReads() { + return new BAMFileSpan(new Chunk(mFirstRecordPointer,Long.MAX_VALUE)); + } + + /** + * Prepare to iterate through the SAMRecords that match the given interval. + * Only a single iterator on a BAMFile can be extant at a time. The previous one must be closed + * before calling any of the methods that return an iterator. + * + * Note that an unmapped SAMRecord may still have a reference name and an alignment start for sorting + * purposes (typically this is the coordinate of its mate), and will be found by this method if the coordinate + * matches the specified interval. + * + * Note that this method is not necessarily efficient in terms of disk I/O. The index does not have perfect + * resolution, so some SAMRecords may be read and then discarded because they do not match the specified interval. + * + * @param sequence Reference sequence sought. + * @param start Desired SAMRecords must overlap or be contained in the interval specified by start and end. + * A value of zero implies the start of the reference sequence. + * @param end A value of zero implies the end of the reference sequence. + * @param contained If true, the alignments for the SAMRecords must be completely contained in the interval + * specified by start and end. If false, the SAMRecords need only overlap the interval. + * @return Iterator for the matching SAMRecords + */ + CloseableIterator query(final String sequence, final int start, final int end, final boolean contained) { + if (mStream == null) { + throw new IllegalStateException("File reader is closed"); + } + if (mCurrentIterator != null) { + throw new IllegalStateException("Iteration in progress"); + } + if (!mIsSeekable) { + throw new UnsupportedOperationException("Cannot query stream-based BAM file"); + } + mCurrentIterator = createIndexIterator(sequence, start, end, contained? QueryType.CONTAINED: QueryType.OVERLAPPING); + return mCurrentIterator; + } + + /** + * Prepare to iterate through the SAMRecords with the given alignment start. + * Only a single iterator on a BAMFile can be extant at a time. The previous one must be closed + * before calling any of the methods that return an iterator. + * + * Note that an unmapped SAMRecord may still have a reference name and an alignment start for sorting + * purposes (typically this is the coordinate of its mate), and will be found by this method if the coordinate + * matches the specified interval. + * + * Note that this method is not necessarily efficient in terms of disk I/O. The index does not have perfect + * resolution, so some SAMRecords may be read and then discarded because they do not match the specified interval. + * + * @param sequence Reference sequence sought. + * @param start Alignment start sought. + * @return Iterator for the matching SAMRecords. + */ + CloseableIterator queryAlignmentStart(final String sequence, final int start) { + if (mStream == null) { + throw new IllegalStateException("File reader is closed"); + } + if (mCurrentIterator != null) { + throw new IllegalStateException("Iteration in progress"); + } + if (!mIsSeekable) { + throw new UnsupportedOperationException("Cannot query stream-based BAM file"); + } + mCurrentIterator = createIndexIterator(sequence, start, -1, QueryType.STARTING_AT); + return mCurrentIterator; + } + + public CloseableIterator queryUnmapped() { + if (mStream == null) { + throw new IllegalStateException("File reader is closed"); + } + if (mCurrentIterator != null) { + throw new IllegalStateException("Iteration in progress"); + } + if (!mIsSeekable) { + throw new UnsupportedOperationException("Cannot query stream-based BAM file"); + } + try { + final long startOfLastLinearBin = getIndex().getStartOfLastLinearBin(); + if (startOfLastLinearBin != -1) { + mInputStream.seek(startOfLastLinearBin); + } else { + // No mapped reads in file, just start at the first read in file. + mInputStream.seek(mFirstRecordPointer); + } + mCurrentIterator = new BAMFileIndexUnmappedIterator(); + return mCurrentIterator; + } catch (IOException e) { + throw new RuntimeException("IOException seeking to unmapped reads", e); + } + } + + /** + * Reads the header from the file or stream + * @param source Note that this is used only for reporting errors. + */ + private void readHeader(final String source) + throws IOException { + + final byte[] buffer = new byte[4]; + mStream.readBytes(buffer); + if (!Arrays.equals(buffer, BAMFileConstants.BAM_MAGIC)) { + throw new IOException("Invalid BAM file header"); + } + + final int headerTextLength = mStream.readInt(); + final String textHeader = mStream.readString(headerTextLength); + final SAMTextHeaderCodec headerCodec = new SAMTextHeaderCodec(); + headerCodec.setValidationStringency(mValidationStringency); + mFileHeader = headerCodec.decode(new StringLineReader(textHeader), + source); + + final int sequenceCount = mStream.readInt(); + if (mFileHeader.getSequenceDictionary().size() > 0) { + // It is allowed to have binary sequences but no text sequences, so only validate if both are present + if (sequenceCount != mFileHeader.getSequenceDictionary().size()) { + throw new SAMFormatException("Number of sequences in text header (" + + mFileHeader.getSequenceDictionary().size() + + ") != number of sequences in binary header (" + sequenceCount + ") for file " + source); + } + for (int i = 0; i < sequenceCount; i++) { + final SAMSequenceRecord binarySequenceRecord = readSequenceRecord(source); + final SAMSequenceRecord sequenceRecord = mFileHeader.getSequence(i); + if (!sequenceRecord.getSequenceName().equals(binarySequenceRecord.getSequenceName())) { + throw new SAMFormatException("For sequence " + i + ", text and binary have different names in file " + + source); + } + if (sequenceRecord.getSequenceLength() != binarySequenceRecord.getSequenceLength()) { + throw new SAMFormatException("For sequence " + i + ", text and binary have different lengths in file " + + source); + } + } + } else { + // If only binary sequences are present, copy them into mFileHeader + final List sequences = new ArrayList(sequenceCount); + for (int i = 0; i < sequenceCount; i++) { + sequences.add(readSequenceRecord(source)); + } + mFileHeader.setSequenceDictionary(new SAMSequenceDictionary(sequences)); + } + } + + /** + * Reads a single binary sequence record from the file or stream + * @param source Note that this is used only for reporting errors. + */ + private SAMSequenceRecord readSequenceRecord(final String source) { + final int nameLength = mStream.readInt(); + if (nameLength <= 1) { + throw new SAMFormatException("Invalid BAM file header: missing sequence name in file " + source); + } + final String sequenceName = mStream.readString(nameLength - 1); + // Skip the null terminator + mStream.readByte(); + final int sequenceLength = mStream.readInt(); + return new SAMSequenceRecord(SAMSequenceRecord.truncateSequenceName(sequenceName), sequenceLength); + } + + /** + * Iterator for non-indexed sequential iteration through all SAMRecords in file. + * Starting point of iteration is wherever current file position is when the iterator is constructed. + */ + private class BAMFileIterator implements CloseableIterator { + private SAMRecord mNextRecord = null; + private final BAMRecordCodec bamRecordCodec; + private long samRecordIndex = 0; // Records at what position (counted in records) we are at in the file + + BAMFileIterator() { + this(true); + } + + /** + * @param advance Trick to enable subclass to do more setup before advancing + */ + BAMFileIterator(final boolean advance) { + this.bamRecordCodec = new BAMRecordCodec(getFileHeader(), samRecordFactory); + this.bamRecordCodec.setInputStream(BAMFileReader.this.mStream.getInputStream()); + + if (advance) { + advance(); + } + } + + public void close() { + if (mCurrentIterator != null && this != mCurrentIterator) { + throw new IllegalStateException("Attempt to close non-current iterator"); + } + mCurrentIterator = null; + } + + public boolean hasNext() { + return (mNextRecord != null); + } + + public SAMRecord next() { + final SAMRecord result = mNextRecord; + advance(); + return result; + } + + public void remove() { + throw new UnsupportedOperationException("Not supported: remove"); + } + + void advance() { + try { + mNextRecord = getNextRecord(); + + if (mNextRecord != null) { + ++this.samRecordIndex; + // Because some decoding is done lazily, the record needs to remember the validation stringency. + mNextRecord.setValidationStringency(mValidationStringency); + + if (mValidationStringency != ValidationStringency.SILENT) { + final List validationErrors = mNextRecord.isValid(); + SAMUtils.processValidationErrors(validationErrors, + this.samRecordIndex, BAMFileReader.this.getValidationStringency()); + } + } + if (eagerDecode && mNextRecord != null) { + mNextRecord.eagerDecode(); + } + } catch (IOException exc) { + throw new RuntimeException(exc.getMessage(), exc); + } + } + + /** + * Read the next record from the input stream. + */ + SAMRecord getNextRecord() throws IOException { + final long startCoordinate = mInputStream.getFilePointer(); + final SAMRecord next = bamRecordCodec.decode(); + final long stopCoordinate = mInputStream.getFilePointer(); + + if(mFileReader != null && next != null) + next.setFileSource(new SAMFileSource(mFileReader,new BAMFileSpan(new Chunk(startCoordinate,stopCoordinate)))); + + return next; + } + + /** + * @return The record that will be return by the next call to next() + */ + protected SAMRecord peek() { + return mNextRecord; + } + } + + /** + * Prepare to iterate through SAMRecords matching the target interval. + * @param sequence Desired reference sequence. + * @param start 1-based start of target interval, inclusive. + * @param end 1-based end of target interval, inclusive. + * @param queryType contained, overlapping, or starting-at query. + */ + private CloseableIterator createIndexIterator(final String sequence, + final int start, + final int end, + final QueryType queryType) { + long[] filePointers = null; + + // Hit the index to determine the chunk boundaries for the required data. + final SAMFileHeader fileHeader = getFileHeader(); + final int referenceIndex = fileHeader.getSequenceIndex(sequence); + if (referenceIndex != -1) { + final BAMIndex fileIndex = getIndex(); + final BAMFileSpan fileSpan = fileIndex.getSpanOverlapping(referenceIndex, start, end); + filePointers = fileSpan != null ? fileSpan.toCoordinateArray() : null; + } + + // Create an iterator over the above chunk boundaries. + final BAMFileIndexIterator iterator = new BAMFileIndexIterator(filePointers); + + // Add some preprocessing filters for edge-case reads that don't fit into this + // query type. + return new BAMQueryFilteringIterator(iterator,sequence,start,end,queryType); + } + + enum QueryType {CONTAINED, OVERLAPPING, STARTING_AT} + + /** + * Look for BAM index file according to standard naming convention. + * + * @param dataFile BAM file name. + * @return Index file name, or null if not found. + */ + private static File findIndexFile(final File dataFile) { + // If input is foo.bam, look for foo.bai + final String bamExtension = ".bam"; + File indexFile; + final String fileName = dataFile.getName(); + if (fileName.endsWith(bamExtension)) { + final String bai = fileName.substring(0, fileName.length() - bamExtension.length()) + BAMIndex.BAMIndexSuffix; + indexFile = new File(dataFile.getParent(), bai); + if (indexFile.exists()) { + return indexFile; + } + } + + // If foo.bai doesn't exist look for foo.bam.bai + indexFile = new File(dataFile.getParent(), dataFile.getName() + ".bai"); + if (indexFile.exists()) { + return indexFile; + } else { + return null; + } + } + + private class BAMFileIndexIterator extends BAMFileIterator { + + private long[] mFilePointers = null; + private int mFilePointerIndex = 0; + private long mFilePointerLimit = -1; + + /** + * Prepare to iterate through SAMRecords stored in the specified compressed blocks at the given offset. + * @param filePointers the block / offset combination, stored in chunk format. + */ + BAMFileIndexIterator(final long[] filePointers) { + super(false); // delay advance() until after construction + mFilePointers = filePointers; + advance(); + } + + SAMRecord getNextRecord() + throws IOException { + // Advance to next file block if necessary + while (mInputStream.getFilePointer() >= mFilePointerLimit) { + if (mFilePointers == null || + mFilePointerIndex >= mFilePointers.length) { + return null; + } + final long startOffset = mFilePointers[mFilePointerIndex++]; + final long endOffset = mFilePointers[mFilePointerIndex++]; + mInputStream.seek(startOffset); + mFilePointerLimit = endOffset; + } + // Pull next record from stream + return super.getNextRecord(); + } + } + + /** + * A decorating iterator that filters out records that are outside the bounds of the + * given query parameters. + */ + private class BAMQueryFilteringIterator implements CloseableIterator { + /** + * The wrapped iterator. + */ + private final CloseableIterator wrappedIterator; + + /** + * The next record to be returned. Will be null if no such record exists. + */ + private SAMRecord mNextRecord; + + private final int mReferenceIndex; + private final int mRegionStart; + private final int mRegionEnd; + private final QueryType mQueryType; + + public BAMQueryFilteringIterator(final CloseableIterator iterator,final String sequence, final int start, final int end, final QueryType queryType) { + this.wrappedIterator = iterator; + final SAMFileHeader fileHeader = getFileHeader(); + mReferenceIndex = fileHeader.getSequenceIndex(sequence); + mRegionStart = start; + if (queryType == QueryType.STARTING_AT) { + mRegionEnd = mRegionStart; + } else { + mRegionEnd = (end <= 0) ? Integer.MAX_VALUE : end; + } + mQueryType = queryType; + mNextRecord = advance(); + } + + /** + * Returns true if a next element exists; false otherwise. + */ + public boolean hasNext() { + return mNextRecord != null; + } + + /** + * Gets the next record from the given iterator. + * @return The next SAM record in the iterator. + */ + public SAMRecord next() { + if(!hasNext()) + throw new NoSuchElementException("BAMQueryFilteringIterator: no next element available"); + final SAMRecord currentRead = mNextRecord; + mNextRecord = advance(); + return currentRead; + } + + /** + * Closes down the existing iterator. + */ + public void close() { + if (this != mCurrentIterator) { + throw new IllegalStateException("Attempt to close non-current iterator"); + } + mCurrentIterator = null; + } + + /** + * @throws UnsupportedOperationException always. + */ + public void remove() { + throw new UnsupportedOperationException("Not supported: remove"); + } + + SAMRecord advance() { + while (true) { + // Pull next record from stream + if(!wrappedIterator.hasNext()) + return null; + + final SAMRecord record = wrappedIterator.next(); + // If beyond the end of this reference sequence, end iteration + final int referenceIndex = record.getReferenceIndex(); + if (referenceIndex != mReferenceIndex) { + if (referenceIndex < 0 || + referenceIndex > mReferenceIndex) { + return null; + } + // If before this reference sequence, continue + continue; + } + if (mRegionStart == 0 && mRegionEnd == Integer.MAX_VALUE) { + // Quick exit to avoid expensive alignment end calculation + return record; + } + final int alignmentStart = record.getAlignmentStart(); + // If read is unmapped but has a coordinate, return it if the coordinate is within + // the query region, regardless of whether the mapped mate will be returned. + final int alignmentEnd; + if (mQueryType == QueryType.STARTING_AT) { + alignmentEnd = -1; + } else { + alignmentEnd = (record.getAlignmentEnd() != SAMRecord.NO_ALIGNMENT_START? + record.getAlignmentEnd(): alignmentStart); + } + + if (alignmentStart > mRegionEnd) { + // If scanned beyond target region, end iteration + return null; + } + // Filter for overlap with region + if (mQueryType == QueryType.CONTAINED) { + if (alignmentStart >= mRegionStart && alignmentEnd <= mRegionEnd) { + return record; + } + } else if (mQueryType == QueryType.OVERLAPPING) { + if (alignmentEnd >= mRegionStart && alignmentStart <= mRegionEnd) { + return record; + } + } else { + if (alignmentStart == mRegionStart) { + return record; + } + } + } + } + } + + private class BAMFileIndexUnmappedIterator extends BAMFileIterator { + private BAMFileIndexUnmappedIterator() { + while (this.hasNext() && peek().getReferenceIndex() != SAMRecord.NO_ALIGNMENT_REFERENCE_INDEX) { + advance(); + } + } + } + +} diff --git a/public/java/src/net/sf/samtools/GATKBAMFileSpan.java b/public/java/src/net/sf/samtools/GATKBAMFileSpan.java index 623f46291..4692c6671 100644 --- a/public/java/src/net/sf/samtools/GATKBAMFileSpan.java +++ b/public/java/src/net/sf/samtools/GATKBAMFileSpan.java @@ -25,6 +25,7 @@ package net.sf.samtools; import net.sf.picard.util.PeekableIterator; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import java.util.ArrayList; import java.util.Arrays; @@ -47,6 +48,18 @@ public class GATKBAMFileSpan extends BAMFileSpan { super(); } + /** + * Create a new GATKBAMFileSpan from an existing BAMFileSpan. + * @param sourceFileSpan + */ + public GATKBAMFileSpan(SAMFileSpan sourceFileSpan) { + if(!(sourceFileSpan instanceof BAMFileSpan)) + throw new SAMException("Unable to create GATKBAMFileSpan from a SAMFileSpan. Please submit a BAMFileSpan instead"); + BAMFileSpan sourceBAMFileSpan = (BAMFileSpan)sourceFileSpan; + for(Chunk chunk: sourceBAMFileSpan.getChunks()) + add(chunk instanceof GATKChunk ? chunk : new GATKChunk(chunk)); + } + /** * Convenience constructor to construct a BAM file span from * a single chunk. diff --git a/public/java/src/net/sf/samtools/GATKChunk.java b/public/java/src/net/sf/samtools/GATKChunk.java index f590809e2..5d349e72e 100644 --- a/public/java/src/net/sf/samtools/GATKChunk.java +++ b/public/java/src/net/sf/samtools/GATKChunk.java @@ -69,6 +69,22 @@ public class GATKChunk extends Chunk { super.setChunkEnd(value); } + public long getBlockStart() { + return getChunkStart() >>> 16; + } + + public int getBlockOffsetStart() { + return (int)(getChunkStart() & 0xFFFF); + } + + public long getBlockEnd() { + return getChunkEnd() >>> 16; + } + + public int getBlockOffsetEnd() { + return ((int)getChunkEnd() & 0xFFFF); + } + /** * Computes an approximation of the uncompressed size of the * chunk, in bytes. Can be used to determine relative weights diff --git a/public/java/src/net/sf/samtools/util/BAMInputStream.java b/public/java/src/net/sf/samtools/util/BAMInputStream.java new file mode 100644 index 000000000..d825c23d5 --- /dev/null +++ b/public/java/src/net/sf/samtools/util/BAMInputStream.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2011, 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 net.sf.samtools.util; + +import java.io.IOException; + +/** + * An input stream formulated for use reading BAM files. Supports + */ +public interface BAMInputStream { + /** + * Seek to the given position in the file. Note that pos is a special virtual file pointer, + * not an actual byte offset. + * + * @param pos virtual file pointer + */ + public void seek(final long pos) throws IOException; + + /** + * @return virtual file pointer that can be passed to seek() to return to the current position. This is + * not an actual byte offset, so arithmetic on file pointers cannot be done to determine the distance between + * the two. + */ + public long getFilePointer(); + + /** + * Determines whether or not the inflater will re-calculated the CRC on the decompressed data + * and check it against the value stored in the GZIP header. CRC checking is an expensive + * operation and should be used accordingly. + */ + public void setCheckCrcs(final boolean check); + + public int read() throws java.io.IOException; + + public int read(byte[] bytes) throws java.io.IOException; + + public int read(byte[] bytes, int i, int i1) throws java.io.IOException; + + public long skip(long l) throws java.io.IOException; + + public int available() throws java.io.IOException; + + public void close() throws java.io.IOException; + + public void mark(int i); + + public void reset() throws java.io.IOException; + + public boolean markSupported(); +} diff --git a/public/java/src/net/sf/samtools/util/BlockCompressedInputStream.java b/public/java/src/net/sf/samtools/util/BlockCompressedInputStream.java new file mode 100755 index 000000000..fae2fc89b --- /dev/null +++ b/public/java/src/net/sf/samtools/util/BlockCompressedInputStream.java @@ -0,0 +1,483 @@ +/* + * The MIT License + * + * Copyright (c) 2009 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 net.sf.samtools.util; + + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +import net.sf.samtools.FileTruncatedException; + +/* + * Utility class for reading BGZF block compressed files. The caller can treat this file like any other InputStream. + * It probably is not necessary to wrap this stream in a buffering stream, because there is internal buffering. + * The advantage of BGZF over conventional GZip format is that BGZF allows for seeking without having to read the + * entire file up to the location being sought. Note that seeking is only possible if the ctor(File) is used. + * + * c.f. http://samtools.sourceforge.net/SAM1.pdf for details of BGZF format + */ +public class BlockCompressedInputStream extends InputStream implements BAMInputStream { + private InputStream mStream = null; + private SeekableStream mFile = null; + private byte[] mFileBuffer = null; + private byte[] mCurrentBlock = null; + private int mCurrentOffset = 0; + private long mBlockAddress = 0; + private int mLastBlockLength = 0; + private final BlockGunzipper blockGunzipper = new BlockGunzipper(); + + + /** + * Note that seek() is not supported if this ctor is used. + */ + public BlockCompressedInputStream(final InputStream stream) { + mStream = IOUtil.toBufferedStream(stream); + mFile = null; + } + + /** + * Use this ctor if you wish to call seek() + */ + public BlockCompressedInputStream(final File file) + throws IOException { + mFile = new SeekableFileStream(file); + mStream = null; + + } + + public BlockCompressedInputStream(final URL url) { + mFile = new SeekableBufferedStream(new SeekableHTTPStream(url)); + mStream = null; + } + + /** + * For providing some arbitrary data source. No additional buffering is + * provided, so if the underlying source is not buffered, wrap it in a + * SeekableBufferedStream before passing to this ctor. + */ + public BlockCompressedInputStream(final SeekableStream strm) { + mFile = strm; + mStream = null; + } + + /** + * Determines whether or not the inflater will re-calculated the CRC on the decompressed data + * and check it against the value stored in the GZIP header. CRC checking is an expensive + * operation and should be used accordingly. + */ + public void setCheckCrcs(final boolean check) { + this.blockGunzipper.setCheckCrcs(check); + } + + /** + * @return the number of bytes that can be read (or skipped over) from this input stream without blocking by the + * next caller of a method for this input stream. The next caller might be the same thread or another thread. + * Note that although the next caller can read this many bytes without blocking, the available() method call itself + * may block in order to fill an internal buffer if it has been exhausted. + */ + public int available() + throws IOException { + if (mCurrentBlock == null || mCurrentOffset == mCurrentBlock.length) { + readBlock(); + } + if (mCurrentBlock == null) { + return 0; + } + return mCurrentBlock.length - mCurrentOffset; + } + + /** + * Closes the underlying InputStream or RandomAccessFile + */ + public void close() + throws IOException { + if (mFile != null) { + mFile.close(); + mFile = null; + } else if (mStream != null) { + mStream.close(); + mStream = null; + } + // Encourage garbage collection + mFileBuffer = null; + mCurrentBlock = null; + } + + /** + * Reads the next byte of data from the input stream. The value byte is returned as an int in the range 0 to 255. + * If no byte is available because the end of the stream has been reached, the value -1 is returned. + * This method blocks until input data is available, the end of the stream is detected, or an exception is thrown. + + * @return the next byte of data, or -1 if the end of the stream is reached. + */ + public int read() + throws IOException { + return (available() > 0) ? mCurrentBlock[mCurrentOffset++] : -1; + } + + /** + * Reads some number of bytes from the input stream and stores them into the buffer array b. The number of bytes + * actually read is returned as an integer. This method blocks until input data is available, end of file is detected, + * or an exception is thrown. + * + * read(buf) has the same effect as read(buf, 0, buf.length). + * + * @param buffer the buffer into which the data is read. + * @return the total number of bytes read into the buffer, or -1 is there is no more data because the end of + * the stream has been reached. + */ + public int read(final byte[] buffer) + throws IOException { + return read(buffer, 0, buffer.length); + } + + private volatile ByteArrayOutputStream buf = null; + private static final byte eol = '\n'; + private static final byte eolCr = '\r'; + + /** + * Reads a whole line. A line is considered to be terminated by either a line feed ('\n'), + * carriage return ('\r') or carriage return followed by a line feed ("\r\n"). + * + * @return A String containing the contents of the line, excluding the line terminating + * character, or null if the end of the stream has been reached + * + * @exception IOException If an I/O error occurs + * @ + */ + public String readLine() throws IOException { + int available = available(); + if (available == 0) { + return null; + } + if(null == buf){ // lazy initialisation + buf = new ByteArrayOutputStream(8192); + } + buf.reset(); + boolean done = false; + boolean foundCr = false; // \r found flag + while (!done) { + int linetmpPos = mCurrentOffset; + int bCnt = 0; + while((available-- > 0)){ + final byte c = mCurrentBlock[linetmpPos++]; + if(c == eol){ // found \n + done = true; + break; + } else if(foundCr){ // previous char was \r + --linetmpPos; // current char is not \n so put it back + done = true; + break; + } else if(c == eolCr){ // found \r + foundCr = true; + continue; // no ++bCnt + } + ++bCnt; + } + if(mCurrentOffset < linetmpPos){ + buf.write(mCurrentBlock, mCurrentOffset, bCnt); + mCurrentOffset = linetmpPos; + } + available = available(); + if(available == 0){ + // EOF + done = true; + } + } + return buf.toString(); + } + + /** + * Reads up to len bytes of data from the input stream into an array of bytes. An attempt is made to read + * as many as len bytes, but a smaller number may be read. The number of bytes actually read is returned as an integer. + * + * This method blocks until input data is available, end of file is detected, or an exception is thrown. + * + * @param buffer buffer into which data is read. + * @param offset the start offset in array b at which the data is written. + * @param length the maximum number of bytes to read. + * @return the total number of bytes read into the buffer, or -1 if there is no more data because the end of + * the stream has been reached. + */ + public int read(final byte[] buffer, int offset, int length) + throws IOException { + final int originalLength = length; + while (length > 0) { + final int available = available(); + if (available == 0) { + // Signal EOF to caller + if (originalLength == length) { + return -1; + } + break; + } + final int copyLength = Math.min(length, available); + System.arraycopy(mCurrentBlock, mCurrentOffset, buffer, offset, copyLength); + mCurrentOffset += copyLength; + offset += copyLength; + length -= copyLength; + } + return originalLength - length; + } + + /** + * Seek to the given position in the file. Note that pos is a special virtual file pointer, + * not an actual byte offset. + * + * @param pos virtual file pointer + */ + public void seek(final long pos) + throws IOException { + if (mFile == null) { + throw new IOException("Cannot seek on stream based file"); + } + // Decode virtual file pointer + // Upper 48 bits is the byte offset into the compressed stream of a block. + // Lower 16 bits is the byte offset into the uncompressed stream inside the block. + final long compressedOffset = BlockCompressedFilePointerUtil.getBlockAddress(pos); + final int uncompressedOffset = BlockCompressedFilePointerUtil.getBlockOffset(pos); + final int available; + if (mBlockAddress == compressedOffset && mCurrentBlock != null) { + available = mCurrentBlock.length; + } else { + mFile.seek(compressedOffset); + mBlockAddress = compressedOffset; + mLastBlockLength = 0; + readBlock(); + available = available(); + } + if (uncompressedOffset > available || + (uncompressedOffset == available && !eof())) { + throw new IOException("Invalid file pointer: " + pos); + } + mCurrentOffset = uncompressedOffset; + } + + private boolean eof() throws IOException { + if (mFile.eof()) { + return true; + } + // If the last remaining block is the size of the EMPTY_GZIP_BLOCK, this is the same as being at EOF. + return (mFile.length() - (mBlockAddress + mLastBlockLength) == BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length); + } + + /** + * @return virtual file pointer that can be passed to seek() to return to the current position. This is + * not an actual byte offset, so arithmetic on file pointers cannot be done to determine the distance between + * the two. + */ + public long getFilePointer() { + if (mCurrentOffset == mCurrentBlock.length) { + // If current offset is at the end of the current block, file pointer should point + // to the beginning of the next block. + return BlockCompressedFilePointerUtil.makeFilePointer(mBlockAddress + mLastBlockLength, 0); + } + return BlockCompressedFilePointerUtil.makeFilePointer(mBlockAddress, mCurrentOffset); + } + + public static long getFileBlock(final long bgzfOffset) { + return BlockCompressedFilePointerUtil.getBlockAddress(bgzfOffset); + } + + /** + * @param stream Must be at start of file. Throws RuntimeException if !stream.markSupported(). + * @return true if the given file looks like a valid BGZF file. + */ + public static boolean isValidFile(final InputStream stream) + throws IOException { + if (!stream.markSupported()) { + throw new RuntimeException("Cannot test non-buffered stream"); + } + stream.mark(BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH); + final byte[] buffer = new byte[BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH]; + final int count = readBytes(stream, buffer, 0, BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH); + stream.reset(); + return count == BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH && isValidBlockHeader(buffer); + } + + private static boolean isValidBlockHeader(final byte[] buffer) { + return (buffer[0] == BlockCompressedStreamConstants.GZIP_ID1 && + (buffer[1] & 0xFF) == BlockCompressedStreamConstants.GZIP_ID2 && + (buffer[3] & BlockCompressedStreamConstants.GZIP_FLG) != 0 && + buffer[10] == BlockCompressedStreamConstants.GZIP_XLEN && + buffer[12] == BlockCompressedStreamConstants.BGZF_ID1 && + buffer[13] == BlockCompressedStreamConstants.BGZF_ID2); + } + + private void readBlock() + throws IOException { + + if (mFileBuffer == null) { + mFileBuffer = new byte[BlockCompressedStreamConstants.MAX_COMPRESSED_BLOCK_SIZE]; + } + int count = readBytes(mFileBuffer, 0, BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH); + if (count == 0) { + // Handle case where there is no empty gzip block at end. + mCurrentOffset = 0; + mBlockAddress += mLastBlockLength; + mCurrentBlock = new byte[0]; + return; + } + if (count != BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH) { + throw new IOException("Premature end of file"); + } + final int blockLength = unpackInt16(mFileBuffer, BlockCompressedStreamConstants.BLOCK_LENGTH_OFFSET) + 1; + if (blockLength < BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH || blockLength > mFileBuffer.length) { + throw new IOException("Unexpected compressed block length: " + blockLength); + } + final int remaining = blockLength - BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH; + count = readBytes(mFileBuffer, BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH, remaining); + if (count != remaining) { + throw new FileTruncatedException("Premature end of file"); + } + inflateBlock(mFileBuffer, blockLength); + mCurrentOffset = 0; + mBlockAddress += mLastBlockLength; + mLastBlockLength = blockLength; + } + + private void inflateBlock(final byte[] compressedBlock, final int compressedLength) + throws IOException { + final int uncompressedLength = unpackInt32(compressedBlock, compressedLength-4); + byte[] buffer = mCurrentBlock; + mCurrentBlock = null; + if (buffer == null || buffer.length != uncompressedLength) { + try { + buffer = new byte[uncompressedLength]; + } catch (NegativeArraySizeException e) { + throw new RuntimeException("BGZF file has invalid uncompressedLength: " + uncompressedLength, e); + } + } + blockGunzipper.unzipBlock(buffer, compressedBlock, compressedLength); + mCurrentBlock = buffer; + } + + private int readBytes(final byte[] buffer, final int offset, final int length) + throws IOException { + if (mFile != null) { + return readBytes(mFile, buffer, offset, length); + } else if (mStream != null) { + return readBytes(mStream, buffer, offset, length); + } else { + return 0; + } + } + + private static int readBytes(final SeekableStream file, final byte[] buffer, final int offset, final int length) + throws IOException { + int bytesRead = 0; + while (bytesRead < length) { + final int count = file.read(buffer, offset + bytesRead, length - bytesRead); + if (count <= 0) { + break; + } + bytesRead += count; + } + return bytesRead; + } + + private static int readBytes(final InputStream stream, final byte[] buffer, final int offset, final int length) + throws IOException { + int bytesRead = 0; + while (bytesRead < length) { + final int count = stream.read(buffer, offset + bytesRead, length - bytesRead); + if (count <= 0) { + break; + } + bytesRead += count; + } + return bytesRead; + } + + private int unpackInt16(final byte[] buffer, final int offset) { + return ((buffer[offset] & 0xFF) | + ((buffer[offset+1] & 0xFF) << 8)); + } + + private int unpackInt32(final byte[] buffer, final int offset) { + return ((buffer[offset] & 0xFF) | + ((buffer[offset+1] & 0xFF) << 8) | + ((buffer[offset+2] & 0xFF) << 16) | + ((buffer[offset+3] & 0xFF) << 24)); + } + + public enum FileTermination {HAS_TERMINATOR_BLOCK, HAS_HEALTHY_LAST_BLOCK, DEFECTIVE} + + public static FileTermination checkTermination(final File file) + throws IOException { + final long fileSize = file.length(); + if (fileSize < BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length) { + return FileTermination.DEFECTIVE; + } + final RandomAccessFile raFile = new RandomAccessFile(file, "r"); + try { + raFile.seek(fileSize - BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length); + byte[] buf = new byte[BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length]; + raFile.readFully(buf); + if (Arrays.equals(buf, BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK)) { + return FileTermination.HAS_TERMINATOR_BLOCK; + } + final int bufsize = (int)Math.min(fileSize, BlockCompressedStreamConstants.MAX_COMPRESSED_BLOCK_SIZE); + buf = new byte[bufsize]; + raFile.seek(fileSize - bufsize); + raFile.read(buf); + for (int i = buf.length - BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length; + i >= 0; --i) { + if (!preambleEqual(BlockCompressedStreamConstants.GZIP_BLOCK_PREAMBLE, + buf, i, BlockCompressedStreamConstants.GZIP_BLOCK_PREAMBLE.length)) { + continue; + } + final ByteBuffer byteBuffer = ByteBuffer.wrap(buf, i + BlockCompressedStreamConstants.GZIP_BLOCK_PREAMBLE.length, 4); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final int totalBlockSizeMinusOne = byteBuffer.getShort() & 0xFFFF; + if (buf.length - i == totalBlockSizeMinusOne + 1) { + return FileTermination.HAS_HEALTHY_LAST_BLOCK; + } else { + return FileTermination.DEFECTIVE; + } + } + return FileTermination.DEFECTIVE; + } finally { + raFile.close(); + } + } + + private static boolean preambleEqual(final byte[] preamble, final byte[] buf, final int startOffset, final int length) { + for (int i = 0; i < length; ++i) { + if (preamble[i] != buf[i + startOffset]) { + return false; + } + } + return true; + } +} + + diff --git a/public/java/src/org/broadinstitute/sting/commandline/CommandLineProgram.java b/public/java/src/org/broadinstitute/sting/commandline/CommandLineProgram.java index bed1e710e..b0b57f7fc 100644 --- a/public/java/src/org/broadinstitute/sting/commandline/CommandLineProgram.java +++ b/public/java/src/org/broadinstitute/sting/commandline/CommandLineProgram.java @@ -331,12 +331,12 @@ public abstract class CommandLineProgram { * used to indicate an error occured * * @param msg the message - * @param e the error + * @param t the error */ - public static void exitSystemWithError(String msg, final Exception e) { + public static void exitSystemWithError(String msg, final Throwable t) { errorPrintf("------------------------------------------------------------------------------------------%n"); errorPrintf("stack trace %n"); - e.printStackTrace(); + t.printStackTrace(); errorPrintf("------------------------------------------------------------------------------------------%n"); errorPrintf("A GATK RUNTIME ERROR has occurred (version %s):%n", CommandLineGATK.getVersionNumber()); @@ -394,8 +394,8 @@ public abstract class CommandLineProgram { * * @param e the exception occured */ - public static void exitSystemWithError(Exception e) { - exitSystemWithError(e.getMessage(), e); + public static void exitSystemWithError(Throwable t) { + exitSystemWithError(t.getMessage(), t); } /** diff --git a/public/java/src/org/broadinstitute/sting/gatk/CommandLineGATK.java b/public/java/src/org/broadinstitute/sting/gatk/CommandLineGATK.java index b8488dc9a..d3db35c07 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/CommandLineGATK.java +++ b/public/java/src/org/broadinstitute/sting/gatk/CommandLineGATK.java @@ -99,8 +99,8 @@ public class CommandLineGATK extends CommandLineExecutable { } catch (net.sf.samtools.SAMException e) { // Let's try this out and see how it is received by our users exitSystemWithSamError(e); - } catch (Exception e) { - exitSystemWithError(e); + } catch (Throwable t) { + exitSystemWithError(t); } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java index 2ceb4ab46..f2e0b5d0c 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java +++ b/public/java/src/org/broadinstitute/sting/gatk/GenomeAnalysisEngine.java @@ -35,6 +35,7 @@ import org.broadinstitute.sting.gatk.arguments.ValidationExclusion; import org.broadinstitute.sting.gatk.datasources.reads.*; import org.broadinstitute.sting.gatk.datasources.reference.ReferenceDataSource; import org.broadinstitute.sting.gatk.datasources.rmd.ReferenceOrderedDataSource; +import org.broadinstitute.sting.gatk.resourcemanagement.ThreadAllocation; import org.broadinstitute.sting.gatk.samples.SampleDB; import org.broadinstitute.sting.gatk.executive.MicroScheduler; import org.broadinstitute.sting.gatk.filters.FilterManager; @@ -126,6 +127,11 @@ public class GenomeAnalysisEngine { */ private Collection filters; + /** + * Controls the allocation of threads between CPU vs IO. + */ + private ThreadAllocation threadAllocation; + /** * A currently hacky unique name for this GATK instance */ @@ -199,6 +205,9 @@ public class GenomeAnalysisEngine { if (this.getArguments().nonDeterministicRandomSeed) resetRandomGenerator(System.currentTimeMillis()); + // Determine how the threads should be divided between CPU vs. IO. + determineThreadAllocation(); + // Prepare the data for traversal. initializeDataSources(); @@ -218,7 +227,7 @@ public class GenomeAnalysisEngine { // create the output streams " initializeOutputStreams(microScheduler.getOutputTracker()); - ShardStrategy shardStrategy = getShardStrategy(readsDataSource,microScheduler.getReference(),intervals); + Iterable shardStrategy = getShardStrategy(readsDataSource,microScheduler.getReference(),intervals); // execute the microscheduler, storing the results return microScheduler.execute(this.walker, shardStrategy); @@ -266,6 +275,16 @@ public class GenomeAnalysisEngine { return Collections.unmodifiableList(filters); } + /** + * Parse out the thread allocation from the given command-line argument. + */ + private void determineThreadAllocation() { + Tags tags = parsingEngine.getTags(argCollection.numberOfThreads); + Integer numCPUThreads = tags.containsKey("cpu") ? Integer.parseInt(tags.getValue("cpu")) : null; + Integer numIOThreads = tags.containsKey("io") ? Integer.parseInt(tags.getValue("io")) : null; + this.threadAllocation = new ThreadAllocation(argCollection.numberOfThreads,numCPUThreads,numIOThreads); + } + /** * Allow subclasses and others within this package direct access to the walker manager. * @return The walker manager used by this package. @@ -286,7 +305,7 @@ public class GenomeAnalysisEngine { throw new UserException.CommandLineException("Read-based traversals require a reference file but none was given"); } - return MicroScheduler.create(this,walker,this.getReadsDataSource(),this.getReferenceDataSource().getReference(),this.getRodDataSources(),this.getArguments().numberOfThreads); + return MicroScheduler.create(this,walker,this.getReadsDataSource(),this.getReferenceDataSource().getReference(),this.getRodDataSources(),threadAllocation); } protected DownsamplingMethod getDownsamplingMethod() { @@ -397,103 +416,49 @@ public class GenomeAnalysisEngine { * @param intervals intervals * @return the sharding strategy */ - protected ShardStrategy getShardStrategy(SAMDataSource readsDataSource, ReferenceSequenceFile drivingDataSource, GenomeLocSortedSet intervals) { + protected Iterable getShardStrategy(SAMDataSource readsDataSource, ReferenceSequenceFile drivingDataSource, GenomeLocSortedSet intervals) { ValidationExclusion exclusions = (readsDataSource != null ? readsDataSource.getReadsInfo().getValidationExclusionList() : null); ReferenceDataSource referenceDataSource = this.getReferenceDataSource(); - // Use monolithic sharding if no index is present. Monolithic sharding is always required for the original - // sharding system; it's required with the new sharding system only for locus walkers. - if(readsDataSource != null && !readsDataSource.hasIndex() ) { - if(!exclusions.contains(ValidationExclusion.TYPE.ALLOW_UNINDEXED_BAM)) + + // If reads are present, assume that accessing the reads is always the dominant factor and shard based on that supposition. + if(!readsDataSource.isEmpty()) { + if(!readsDataSource.hasIndex() && !exclusions.contains(ValidationExclusion.TYPE.ALLOW_UNINDEXED_BAM)) throw new UserException.CommandLineException("Cannot process the provided BAM file(s) because they were not indexed. The GATK does offer limited processing of unindexed BAMs in --unsafe mode, but this GATK feature is currently unsupported."); - if(intervals != null && !argCollection.allowIntervalsWithUnindexedBAM) + if(!readsDataSource.hasIndex() && intervals != null && !argCollection.allowIntervalsWithUnindexedBAM) throw new UserException.CommandLineException("Cannot perform interval processing when reads are present but no index is available."); - Shard.ShardType shardType; if(walker instanceof LocusWalker) { if (readsDataSource.getSortOrder() != SAMFileHeader.SortOrder.coordinate) throw new UserException.MissortedBAM(SAMFileHeader.SortOrder.coordinate, "Locus walkers can only traverse coordinate-sorted data. Please resort your input BAM file(s) or set the Sort Order tag in the header appropriately."); - shardType = Shard.ShardType.LOCUS; + if(intervals == null) + return readsDataSource.createShardIteratorOverMappedReads(referenceDataSource.getReference().getSequenceDictionary(),new LocusShardBalancer()); + else + return readsDataSource.createShardIteratorOverIntervals(intervals,new LocusShardBalancer()); + } + else if(walker instanceof ReadWalker || walker instanceof ReadPairWalker || walker instanceof DuplicateWalker) { + // Apply special validation to read pair walkers. + if(walker instanceof ReadPairWalker) { + if(readsDataSource.getSortOrder() != SAMFileHeader.SortOrder.queryname) + throw new UserException.MissortedBAM(SAMFileHeader.SortOrder.queryname, "Read pair walkers are exceptions in that they cannot be run on coordinate-sorted BAMs but instead require query name-sorted files. You will need to resort your input BAM file in query name order to use this walker."); + if(intervals != null && !intervals.isEmpty()) + throw new UserException.CommandLineException("Pairs traversal cannot be used in conjunction with intervals."); + } + + if(intervals == null) + return readsDataSource.createShardIteratorOverAllReads(new ReadShardBalancer()); + else + return readsDataSource.createShardIteratorOverIntervals(intervals,new ReadShardBalancer()); } - else if(walker instanceof ReadWalker || walker instanceof DuplicateWalker || walker instanceof ReadPairWalker) - shardType = Shard.ShardType.READ; else - throw new UserException.CommandLineException("The GATK cannot currently process unindexed BAM files"); - - List region; - if(intervals != null) - region = intervals.toList(); - else { - region = new ArrayList(); - for(SAMSequenceRecord sequenceRecord: drivingDataSource.getSequenceDictionary().getSequences()) - region.add(getGenomeLocParser().createGenomeLoc(sequenceRecord.getSequenceName(),1,sequenceRecord.getSequenceLength())); - } - - return new MonolithicShardStrategy(getGenomeLocParser(), readsDataSource,shardType,region); + throw new ReviewedStingException("Unable to determine walker type for walker " + walker.getClass().getName()); + } + else { + final int SHARD_SIZE = walker instanceof RodWalker ? 100000000 : 100000; + if(intervals == null) + return referenceDataSource.createShardsOverEntireReference(readsDataSource,genomeLocParser,SHARD_SIZE); + else + return referenceDataSource.createShardsOverIntervals(readsDataSource,intervals,SHARD_SIZE); } - - ShardStrategy shardStrategy; - ShardStrategyFactory.SHATTER_STRATEGY shardType; - - long SHARD_SIZE = 100000L; - - if (walker instanceof LocusWalker) { - if (walker instanceof RodWalker) SHARD_SIZE *= 1000; - - if (intervals != null && !intervals.isEmpty()) { - if (readsDataSource == null) - throw new IllegalArgumentException("readsDataSource is null"); - if(!readsDataSource.isEmpty() && readsDataSource.getSortOrder() != SAMFileHeader.SortOrder.coordinate) - throw new UserException.MissortedBAM(SAMFileHeader.SortOrder.coordinate, "Locus walkers can only traverse coordinate-sorted data. Please resort your input BAM file(s) or set the Sort Order tag in the header appropriately."); - - shardStrategy = ShardStrategyFactory.shatter(readsDataSource, - referenceDataSource.getReference(), - ShardStrategyFactory.SHATTER_STRATEGY.LOCUS_EXPERIMENTAL, - drivingDataSource.getSequenceDictionary(), - SHARD_SIZE, - getGenomeLocParser(), - intervals); - } else - shardStrategy = ShardStrategyFactory.shatter(readsDataSource, - referenceDataSource.getReference(), - ShardStrategyFactory.SHATTER_STRATEGY.LOCUS_EXPERIMENTAL, - drivingDataSource.getSequenceDictionary(), - SHARD_SIZE,getGenomeLocParser()); - } else if (walker instanceof ReadWalker || - walker instanceof DuplicateWalker) { - shardType = ShardStrategyFactory.SHATTER_STRATEGY.READS_EXPERIMENTAL; - - if (intervals != null && !intervals.isEmpty()) { - shardStrategy = ShardStrategyFactory.shatter(readsDataSource, - referenceDataSource.getReference(), - shardType, - drivingDataSource.getSequenceDictionary(), - SHARD_SIZE, - getGenomeLocParser(), - intervals); - } else { - shardStrategy = ShardStrategyFactory.shatter(readsDataSource, - referenceDataSource.getReference(), - shardType, - drivingDataSource.getSequenceDictionary(), - SHARD_SIZE, - getGenomeLocParser()); - } - } else if (walker instanceof ReadPairWalker) { - if(readsDataSource != null && readsDataSource.getSortOrder() != SAMFileHeader.SortOrder.queryname) - throw new UserException.MissortedBAM(SAMFileHeader.SortOrder.queryname, "Read pair walkers are exceptions in that they cannot be run on coordinate-sorted BAMs but instead require query name-sorted files. You will need to resort your input BAM file in query name order to use this walker."); - if(intervals != null && !intervals.isEmpty()) - throw new UserException.CommandLineException("Pairs traversal cannot be used in conjunction with intervals."); - - shardStrategy = ShardStrategyFactory.shatter(readsDataSource, - referenceDataSource.getReference(), - ShardStrategyFactory.SHATTER_STRATEGY.READS_EXPERIMENTAL, - drivingDataSource.getSequenceDictionary(), - SHARD_SIZE, - getGenomeLocParser()); - } else - throw new ReviewedStingException("Unable to support walker of type" + walker.getClass().getName()); - - return shardStrategy; } protected boolean flashbackData() { @@ -751,6 +716,8 @@ public class GenomeAnalysisEngine { return new SAMDataSource( samReaderIDs, + threadAllocation, + argCollection.numberOfBAMFileHandles, genomeLocParser, argCollection.useOriginalBaseQualities, argCollection.strictnessLevel, @@ -763,8 +730,7 @@ public class GenomeAnalysisEngine { getWalkerBAQApplicationTime() == BAQ.ApplicationTime.ON_INPUT ? argCollection.BAQMode : BAQ.CalculationMode.OFF, getWalkerBAQQualityMode(), refReader, - argCollection.defaultBaseQualities, - !argCollection.disableLowMemorySharding); + argCollection.defaultBaseQualities); } /** diff --git a/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java b/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java index 8078a1ea4..64b63dcd2 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java +++ b/public/java/src/org/broadinstitute/sting/gatk/arguments/GATKArgumentCollection.java @@ -194,10 +194,14 @@ public class GATKArgumentCollection { @Argument(fullName = "unsafe", shortName = "U", doc = "If set, enables unsafe operations: nothing will be checked at runtime. For expert users only who know what they are doing. We do not support usage of this argument.", required = false) public ValidationExclusion.TYPE unsafe; - @Argument(fullName = "num_threads", shortName = "nt", doc = "How many threads should be allocated to running this analysis", required = false) - public int numberOfThreads = 1; + /** How many threads should be allocated to this analysis. */ + @Argument(fullName = "num_threads", shortName = "nt", doc = "How many threads should be allocated to running this analysis.", required = false) + public Integer numberOfThreads = 1; - @Input(fullName = "read_group_black_list", shortName="rgbl", doc="Filters out read groups matching : or a .txt file containing the filter strings one per line", required = false) + @Argument(fullName = "num_bam_file_handles", shortName = "bfh", doc="The total number of BAM file handles to keep open simultaneously", required=false) + public Integer numberOfBAMFileHandles = null; + + @Input(fullName = "read_group_black_list", shortName="rgbl", doc="Filters out read groups matching : or a .txt file containing the filter strings one per line.", required = false) public List readGroupBlackList = null; // -------------------------------------------------------------------------------------------------------------- @@ -292,9 +296,6 @@ public class GATKArgumentCollection { @Hidden public boolean allowIntervalsWithUnindexedBAM = false; - @Argument(fullName="disable_experimental_low_memory_sharding",doc="Disable experimental low-memory sharding functionality",required=false) - public boolean disableLowMemorySharding = false; - // -------------------------------------------------------------------------------------------------------------- // // methods @@ -365,7 +366,11 @@ public class GATKArgumentCollection { (other.downsampleCoverage != null && !other.downsampleCoverage.equals(this.downsampleCoverage))) { return false; } - if (other.numberOfThreads != this.numberOfThreads) { + if (!other.numberOfThreads.equals(this.numberOfThreads)) { + return false; + } + if ((other.numberOfBAMFileHandles == null && this.numberOfBAMFileHandles != null) || + (other.numberOfBAMFileHandles != null && !other.numberOfBAMFileHandles.equals(this.numberOfBAMFileHandles))) { return false; } if (other.intervalMerging != this.intervalMerging) { @@ -389,9 +394,6 @@ public class GATKArgumentCollection { if (allowIntervalsWithUnindexedBAM != other.allowIntervalsWithUnindexedBAM) return false; - if (disableLowMemorySharding != other.disableLowMemorySharding) - return false; - return true; } diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMBlockStartIterator.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMBlockStartIterator.java deleted file mode 100644 index de938e845..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMBlockStartIterator.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2011, 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 org.broadinstitute.sting.utils.exceptions.StingException; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.channels.FileChannel; -import java.util.Iterator; - -/** - * Created by IntelliJ IDEA. - * User: mhanna - * Date: Feb 7, 2011 - * Time: 2:46:34 PM - * To change this template use File | Settings | File Templates. - */ -public class BAMBlockStartIterator implements Iterator { - /** - * How large is a BGZF header? - */ - private static int BGZF_HEADER_SIZE = 18; - - /** - * Where within the header does the BLOCKSIZE actually live? - */ - private static int BLOCK_SIZE_HEADER_POSITION = BGZF_HEADER_SIZE - 2; - - private FileChannel bamInputChannel; - private ByteBuffer headerByteBuffer; - - private long nextLocation = 0; - - public BAMBlockStartIterator(File bamFile) { - try { - FileInputStream bamInputStream = new FileInputStream(bamFile); - bamInputChannel = bamInputStream.getChannel(); - - headerByteBuffer = ByteBuffer.allocate(BGZF_HEADER_SIZE); - headerByteBuffer.order(ByteOrder.LITTLE_ENDIAN); - - } - catch(IOException ex) { - throw new StingException("Could not open file",ex); - } - } - - public boolean hasNext() { - return nextLocation != -1; - } - - public Long next() { - long currentLocation = nextLocation; - advance(); - return currentLocation; - } - - public void remove() { - throw new UnsupportedOperationException("Cannot remove from a BAMBlockStartIterator"); - } - - private void advance() { - int readStatus; - - headerByteBuffer.clear(); - try { - readStatus = bamInputChannel.read(headerByteBuffer); - } - catch(IOException ex) { - throw new StingException("Could not read header data",ex); - } - - if(readStatus == -1) { - nextLocation = -1; - try { - bamInputChannel.close(); - } - catch(IOException ex) { - throw new StingException("Could not close input file",ex); - } - return; - } - - headerByteBuffer.position(BLOCK_SIZE_HEADER_POSITION); - int blockSize = headerByteBuffer.getShort(); - - try { - bamInputChannel.position(bamInputChannel.position()+blockSize-BGZF_HEADER_SIZE+1); - nextLocation = bamInputChannel.position(); - } - catch(IOException ex) { - throw new StingException("Could not reposition input stream",ex); - } - } - - public static void main(String argv[]) throws IOException { - BAMBlockStartIterator blockStartIterator = new BAMBlockStartIterator(new File("/Users/mhanna/testdata/reads/MV1994.bam")); - int i = 0; - while(blockStartIterator.hasNext()) - System.out.printf("%d -> %d%n",i++,blockStartIterator.next()); - } -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMIndexContent.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMIndexContent.java deleted file mode 100644 index 4d91fb45f..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMIndexContent.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (c) 2011, 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.GATKBin; -import net.sf.samtools.GATKChunk; -import net.sf.samtools.LinearIndex; - -import java.util.*; - -/** - * Represents the contents of a bam index file for one reference. - * A BAM index (.bai) file contains information for all references in the bam file. - * This class describes the data present in the index file for one of these references; - * including the bins, chunks, and linear index. - */ -class BAMIndexContent { - /** - * The reference sequence for the data currently loaded. - */ - private final int mReferenceSequence; - - /** - * A list of all bins in the above reference sequence. - */ - private final BinList mBinList; - - /** - * The linear index for the reference sequence above. - */ - private final LinearIndex mLinearIndex; - - - /** - * @param referenceSequence Content corresponds to this reference. - * @param bins Array of bins represented by this content, possibly sparse - * @param numberOfBins Number of non-null bins - * @param linearIndex Additional index used to optimize queries - */ - BAMIndexContent(final int referenceSequence, final GATKBin[] bins, final int numberOfBins, final LinearIndex linearIndex) { - this.mReferenceSequence = referenceSequence; - this.mBinList = new BinList(bins, numberOfBins); - this.mLinearIndex = linearIndex; - } - - /** - * Reference for this Content - */ - public int getReferenceSequence() { - return mReferenceSequence; - } - - /** - * Does this content have anything in this bin? - */ - public boolean containsBin(final GATKBin bin) { - return mBinList.getBin(bin.getBinNumber()) != null; - } - - /** - * @return iterable list of bins represented by this content - */ - public BinList getBins() { - return mBinList; - } - - /** - * @return the number of non-null bins represented by this content - */ - int getNumberOfNonNullBins() { - return mBinList.getNumberOfNonNullBins(); - } - - /** - * @return all chunks associated with all bins in this content - */ - public List getAllChunks() { - List allChunks = new ArrayList(); - for (GATKBin b : mBinList) - if (b.getChunkList() != null) { - allChunks.addAll(Arrays.asList(b.getChunkList())); - } - return Collections.unmodifiableList(allChunks); - } - - /** - * @return the linear index represented by this content - */ - public LinearIndex getLinearIndex() { - return mLinearIndex; - } - - /** - * This class is used to encapsulate the list of Bins store in the BAMIndexContent - * While it is currently represented as an array, we may decide to change it to an ArrayList or other structure - */ - class BinList implements Iterable { - - private final GATKBin[] mBinArray; - public final int numberOfNonNullBins; - public final int maxBinNumber; // invariant: maxBinNumber = mBinArray.length -1 since array is 0 based - - /** - * @param binArray a sparse array representation of the bins. The index into the array is the bin number. - * @param numberOfNonNullBins - */ - BinList(GATKBin[] binArray, int numberOfNonNullBins) { - this.mBinArray = binArray; - this.numberOfNonNullBins = numberOfNonNullBins; - this.maxBinNumber = mBinArray.length - 1; - } - - GATKBin getBin(int binNumber) { - if (binNumber > maxBinNumber) return null; - return mBinArray[binNumber]; - } - - int getNumberOfNonNullBins() { - return numberOfNonNullBins; - } - - /** - * Gets an iterator over all non-null bins. - * - * @return An iterator over all bins. - */ - public Iterator iterator() { - return new BinIterator(); - } - - private class BinIterator implements Iterator { - /** - * Stores the bin # of the Bin currently in use. - */ - private int nextBin; - - public BinIterator() { - nextBin = 0; - } - - /** - * Are there more bins in this set, waiting to be returned? - * - * @return True if more bins are remaining. - */ - public boolean hasNext() { - while (nextBin <= maxBinNumber) { - if (getBin(nextBin) != null) return true; - nextBin++; - } - return false; - } - - /** - * Gets the next bin in the provided BinList. - * - * @return the next available bin in the BinList. - */ - public GATKBin next() { - if (!hasNext()) - throw new NoSuchElementException("This BinIterator is currently empty"); - GATKBin result = getBin(nextBin); - nextBin++; - return result; - } - - public void remove() { - throw new UnsupportedOperationException("Unable to remove from a bin iterator"); - } - } - } - -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMOverlap.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMOverlap.java deleted file mode 100644 index 15a372ca6..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMOverlap.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.broadinstitute.sting.gatk.datasources.reads; - -import net.sf.samtools.Bin; - -import java.util.HashMap; -import java.util.Map; - -/** - * Models a bin at which all BAM files in the merged input stream overlap. - */ -class BAMOverlap { - public final int start; - public final int stop; - - private final Map bins = new HashMap(); - - public BAMOverlap(final int start, final int stop) { - this.start = start; - this.stop = stop; - } - - public void addBin(final SAMReaderID id, final Bin bin) { - bins.put(id,bin); - } - - public Bin getBin(final SAMReaderID id) { - return bins.get(id); - } -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMSchedule.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMSchedule.java index 521bcd5a3..762722fcd 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMSchedule.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMSchedule.java @@ -84,21 +84,21 @@ public class BAMSchedule implements CloseableIterator { /** * Create a new BAM schedule based on the given index. - * @param indexFiles Index files. + * @param dataSource The SAM data source to use. * @param intervals List of */ - public BAMSchedule(final Map indexFiles, final List intervals) { + public BAMSchedule(final SAMDataSource dataSource, final List intervals) { if(intervals.isEmpty()) throw new ReviewedStingException("Tried to write schedule for empty interval list."); - referenceSequence = intervals.get(0).getContigIndex(); + referenceSequence = dataSource.getHeader().getSequence(intervals.get(0).getContig()).getSequenceIndex(); createScheduleFile(); - readerIDs.addAll(indexFiles.keySet()); + readerIDs.addAll(dataSource.getReaderIDs()); for(final SAMReaderID reader: readerIDs) { - final GATKBAMIndex index = indexFiles.get(reader); + final GATKBAMIndex index = dataSource.getIndex(reader); final GATKBAMIndexData indexData = index.readReferenceSequence(referenceSequence); int currentBinInLowestLevel = GATKBAMIndex.getFirstBinInLevel(GATKBAMIndex.getNumIndexLevels()-1); @@ -237,7 +237,10 @@ public class BAMSchedule implements CloseableIterator { if(selectedIterators.isEmpty()) return; + // Create the target schedule entry BAMScheduleEntry mergedScheduleEntry = new BAMScheduleEntry(currentStart,currentStop); + + // For each schedule entry with data, load the data into the merged schedule. for (int reader = selectedIterators.nextSetBit(0); reader >= 0; reader = selectedIterators.nextSetBit(reader+1)) { PeekableIterator scheduleIterator = scheduleIterators.get(reader); BAMScheduleEntry individualScheduleEntry = scheduleIterator.peek(); @@ -248,6 +251,11 @@ public class BAMSchedule implements CloseableIterator { scheduleIterator.next(); } + // For each schedule entry without data, add a blank entry. + for (int reader = selectedIterators.nextClearBit(0); reader < readerIDs.size(); reader = selectedIterators.nextClearBit(reader+1)) { + mergedScheduleEntry.addFileSpan(readerIDs.get(reader),new GATKBAMFileSpan()); + } + nextScheduleEntry = mergedScheduleEntry; } diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMScheduler.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMScheduler.java index 47eb55b28..dca4cc771 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMScheduler.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BAMScheduler.java @@ -27,7 +27,12 @@ package org.broadinstitute.sting.gatk.datasources.reads; import net.sf.picard.util.PeekableIterator; import net.sf.samtools.GATKBAMFileSpan; import net.sf.samtools.GATKChunk; +import net.sf.samtools.SAMFileHeader; +import net.sf.samtools.SAMFileSpan; +import net.sf.samtools.SAMSequenceDictionary; +import net.sf.samtools.SAMSequenceRecord; import org.broadinstitute.sting.utils.GenomeLoc; +import org.broadinstitute.sting.utils.GenomeLocParser; import org.broadinstitute.sting.utils.GenomeLocSortedSet; import java.util.*; @@ -42,21 +47,86 @@ public class BAMScheduler implements Iterator { private FilePointer nextFilePointer = null; - private final GenomeLocSortedSet loci; + private GenomeLocSortedSet loci; + private PeekableIterator locusIterator; + private GenomeLoc currentLocus; - private final PeekableIterator locusIterator; + public static BAMScheduler createOverMappedReads(final SAMDataSource dataSource, final SAMSequenceDictionary referenceSequenceDictionary, final GenomeLocParser parser) { + BAMScheduler scheduler = new BAMScheduler(dataSource); + GenomeLocSortedSet intervals = new GenomeLocSortedSet(parser); + for(SAMSequenceRecord sequence: referenceSequenceDictionary.getSequences()) { + // Match only on sequence name; trust startup validation to make sure all the sequences match. + if(dataSource.getHeader().getSequenceDictionary().getSequence(sequence.getSequenceName()) != null) + intervals.add(parser.createOverEntireContig(sequence.getSequenceName())); + } + scheduler.populateFilteredIntervalList(intervals); + return scheduler; + } - private GenomeLoc currentLocus; + public static BAMScheduler createOverAllReads(final SAMDataSource dataSource, final GenomeLocParser parser) { + BAMScheduler scheduler = new BAMScheduler(dataSource); + scheduler.populateUnfilteredIntervalList(parser); + return scheduler; + } - public BAMScheduler(final SAMDataSource dataSource, final GenomeLocSortedSet loci) { + public static BAMScheduler createOverIntervals(final SAMDataSource dataSource, final GenomeLocSortedSet loci) { + BAMScheduler scheduler = new BAMScheduler(dataSource); + scheduler.populateFilteredIntervalList(loci); + return scheduler; + } + + + private BAMScheduler(final SAMDataSource dataSource) { this.dataSource = dataSource; - for(SAMReaderID reader: dataSource.getReaderIDs()) - indexFiles.put(reader,(GATKBAMIndex)dataSource.getIndex(reader)); + for(SAMReaderID reader: dataSource.getReaderIDs()) { + GATKBAMIndex index = dataSource.getIndex(reader); + if(index != null) + indexFiles.put(reader,dataSource.getIndex(reader)); + } + } + + /** + * The consumer has asked for a bounded set of locations. Prepare an iterator over those locations. + * @param loci The list of locations to search and iterate over. + */ + private void populateFilteredIntervalList(final GenomeLocSortedSet loci) { this.loci = loci; - locusIterator = new PeekableIterator(loci.iterator()); - if(locusIterator.hasNext()) - currentLocus = locusIterator.next(); - advance(); + if(!indexFiles.isEmpty()) { + // If index data is available, start up the iterator. + locusIterator = new PeekableIterator(loci.iterator()); + if(locusIterator.hasNext()) + currentLocus = locusIterator.next(); + advance(); + } + else { + // Otherwise, seed the iterator with a single file pointer over the entire region. + nextFilePointer = generatePointerOverEntireFileset(); + for(GenomeLoc locus: loci) + nextFilePointer.addLocation(locus); + locusIterator = new PeekableIterator(Collections.emptyList().iterator()); + } + } + + /** + * The consumer has provided null, meaning to iterate over all available data. Create a file pointer stretching + * from just before the start of the region to the end of the region. + */ + private void populateUnfilteredIntervalList(final GenomeLocParser parser) { + this.loci = new GenomeLocSortedSet(parser); + locusIterator = new PeekableIterator(Collections.emptyList().iterator()); + nextFilePointer = generatePointerOverEntireFileset(); + } + + /** + * Generate a span that runs from the end of the BAM header to the end of the fle. + * @return A file pointer over the specified region. + */ + private FilePointer generatePointerOverEntireFileset() { + FilePointer filePointer = new FilePointer(); + Map currentPosition = dataSource.getCurrentPosition(); + for(SAMReaderID reader: dataSource.getReaderIDs()) + filePointer.addFileSpans(reader,createSpanToEndOfFile(currentPosition.get(reader).getGATKChunks().get(0).getChunkStart())); + return filePointer; } public boolean hasNext() { @@ -67,7 +137,9 @@ public class BAMScheduler implements Iterator { if(!hasNext()) throw new NoSuchElementException("No next element available in interval sharder"); FilePointer currentFilePointer = nextFilePointer; + nextFilePointer = null; advance(); + return currentFilePointer; } @@ -79,13 +151,12 @@ public class BAMScheduler implements Iterator { if(loci.isEmpty()) return; - nextFilePointer = null; while(nextFilePointer == null && currentLocus != null) { // special case handling of the unmapped shard. if(currentLocus == GenomeLoc.UNMAPPED) { nextFilePointer = new FilePointer(GenomeLoc.UNMAPPED); for(SAMReaderID id: dataSource.getReaderIDs()) - nextFilePointer.addFileSpans(id,new GATKBAMFileSpan(new GATKChunk(indexFiles.get(id).getStartOfLastLinearBin(),Long.MAX_VALUE))); + nextFilePointer.addFileSpans(id,createSpanToEndOfFile(indexFiles.get(id).getStartOfLastLinearBin())); currentLocus = null; continue; } @@ -96,7 +167,7 @@ public class BAMScheduler implements Iterator { int coveredRegionStop = Integer.MAX_VALUE; GenomeLoc coveredRegion = null; - BAMScheduleEntry scheduleEntry = getNextOverlappingBAMScheduleEntry(indexFiles,currentLocus); + BAMScheduleEntry scheduleEntry = getNextOverlappingBAMScheduleEntry(currentLocus); // No overlapping data at all. if(scheduleEntry != null) { @@ -108,7 +179,6 @@ public class BAMScheduler implements Iterator { } else { // Always create a file span, whether there was covered data or not. If there was no covered data, then the binTree is empty. - //System.out.printf("Shard: index file = %s; reference sequence = %d; ",index.getIndexFile(),currentLocus.getContigIndex()); for(SAMReaderID reader: indexFiles.keySet()) nextFilePointer.addFileSpans(reader,new GATKBAMFileSpan()); } @@ -116,21 +186,13 @@ public class BAMScheduler implements Iterator { // Early exit if no bins were found. if(coveredRegion == null) { // for debugging only: maximum split is 16384. - if(currentLocus.size() > 16384) { - GenomeLoc[] splitContigs = currentLocus.split(currentLocus.getStart()+16384); - nextFilePointer.addLocation(splitContigs[0]); - currentLocus = splitContigs[1]; - } - else { - nextFilePointer.addLocation(currentLocus); - currentLocus = locusIterator.hasNext() ? locusIterator.next() : null; - } + nextFilePointer.addLocation(currentLocus); + currentLocus = locusIterator.hasNext() ? locusIterator.next() : null; continue; } // Early exit if only part of the first interval was found. if(currentLocus.startsBefore(coveredRegion)) { - // for debugging only: maximum split is 16384. int splitPoint = Math.min(coveredRegion.getStart()-currentLocus.getStart(),16384)+currentLocus.getStart(); GenomeLoc[] splitContigs = currentLocus.split(splitPoint); nextFilePointer.addLocation(splitContigs[0]); @@ -175,25 +237,30 @@ public class BAMScheduler implements Iterator { /** * Get the next overlapping tree of bins associated with the given BAM file. - * @param indices BAM indices. * @param currentLocus The actual locus for which to check overlap. * @return The next schedule entry overlapping with the given list of loci. */ - private BAMScheduleEntry getNextOverlappingBAMScheduleEntry(final Map indices, final GenomeLoc currentLocus) { + private BAMScheduleEntry getNextOverlappingBAMScheduleEntry(final GenomeLoc currentLocus) { + // Make sure that we consult the BAM header to ensure that we're using the correct contig index for this contig name. + // This will ensure that if the two sets of contigs don't quite match (b36 male vs female ref, hg19 Epstein-Barr), then + // we'll be using the correct contig index for the BAMs. + // TODO: Warning: assumes all BAMs use the same sequence dictionary! Get around this with contig aliasing. + final int currentContigIndex = dataSource.getHeader().getSequence(currentLocus.getContig()).getSequenceIndex(); + // Stale reference sequence or first invocation. (Re)create the binTreeIterator. - if(lastReferenceSequenceLoaded == null || lastReferenceSequenceLoaded != currentLocus.getContigIndex()) { + if(lastReferenceSequenceLoaded == null || lastReferenceSequenceLoaded != currentContigIndex) { if(bamScheduleIterator != null) bamScheduleIterator.close(); - lastReferenceSequenceLoaded = currentLocus.getContigIndex(); + lastReferenceSequenceLoaded = currentContigIndex; // Naive algorithm: find all elements in current contig for proper schedule creation. List lociInContig = new LinkedList(); for(GenomeLoc locus: loci) { - if(locus.getContigIndex() == lastReferenceSequenceLoaded) + if(dataSource.getHeader().getSequence(locus.getContig()).getSequenceIndex() == lastReferenceSequenceLoaded) lociInContig.add(locus); } - bamScheduleIterator = new PeekableIterator(new BAMSchedule(indices,lociInContig)); + bamScheduleIterator = new PeekableIterator(new BAMSchedule(dataSource,lociInContig)); } if(!bamScheduleIterator.hasNext()) @@ -209,4 +276,13 @@ public class BAMScheduler implements Iterator { return (bamScheduleEntry != null && bamScheduleEntry.overlaps(currentLocus)) ? bamScheduleEntry : null; } + /** + * Create a span from the given start point to the end of the file. + * @param startOfRegion Start of the region, in encoded coordinates (block start << 16 & block offset). + * @return A file span from the given point to the end of the file. + */ + private GATKBAMFileSpan createSpanToEndOfFile(final long startOfRegion) { + return new GATKBAMFileSpan(new GATKChunk(startOfRegion,Long.MAX_VALUE)); + } + } diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BGZFBlockLoadingDispatcher.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BGZFBlockLoadingDispatcher.java new file mode 100644 index 000000000..f468d2020 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BGZFBlockLoadingDispatcher.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2011, 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 org.broadinstitute.sting.utils.exceptions.ReviewedStingException; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Preloads BGZF blocks in preparation for unzipping and data processing. + * TODO: Right now, the block loader has all threads blocked waiting for a work request. Ultimately this should + * TODO: be replaced with a central thread management strategy. + */ +public class BGZFBlockLoadingDispatcher { + /** + * The file handle cache, used when allocating blocks from the dispatcher. + */ + private final FileHandleCache fileHandleCache; + + private final ExecutorService threadPool; + + private final Queue inputQueue; + + public BGZFBlockLoadingDispatcher(final int numThreads, final int numFileHandles) { + threadPool = Executors.newFixedThreadPool(numThreads); + fileHandleCache = new FileHandleCache(numFileHandles); + inputQueue = new LinkedList(); + + threadPool.execute(new BlockLoader(this,fileHandleCache,true)); + } + + /** + * Initiates a request for a new block load. + * @param readerPosition Position at which to load. + */ + void queueBlockLoad(final SAMReaderPosition readerPosition) { + synchronized(inputQueue) { + inputQueue.add(readerPosition); + inputQueue.notify(); + } + } + + /** + * Claims the next work request from the queue. + * @return The next work request, or null if none is available. + */ + SAMReaderPosition claimNextWorkRequest() { + synchronized(inputQueue) { + while(inputQueue.isEmpty()) { + try { + inputQueue.wait(); + } + catch(InterruptedException ex) { + throw new ReviewedStingException("Interrupt occurred waiting for next block reader work item"); + } + } + return inputQueue.poll(); + } + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BlockInputStream.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BlockInputStream.java new file mode 100644 index 000000000..e377f865d --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BlockInputStream.java @@ -0,0 +1,436 @@ +/* + * Copyright (c) 2011, 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.GATKBAMFileSpan; +import net.sf.samtools.GATKChunk; +import net.sf.samtools.util.BAMInputStream; +import net.sf.samtools.util.BlockCompressedFilePointerUtil; +import net.sf.samtools.util.BlockCompressedInputStream; +import net.sf.samtools.util.RuntimeEOFException; +import net.sf.samtools.util.SeekableStream; +import org.broad.tribble.util.BlockCompressedStreamConstants; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.LinkedList; + +/** + * Presents decompressed blocks to the SAMFileReader. + */ +public class BlockInputStream extends SeekableStream implements BAMInputStream { + /** + * Mechanism for triggering block loads. + */ + private final BGZFBlockLoadingDispatcher dispatcher; + + /** + * The reader whose data is supplied by this input stream. + */ + private final SAMReaderID reader; + + /** + * Length of the input stream. + */ + private final long length; + + /** + * The latest error reported by an asynchronous block load. + */ + private Throwable error; + + /** + * Current position. + */ + private SAMReaderPosition position; + + /** + * A stream of compressed data blocks. + */ + private final ByteBuffer buffer; + + /** + * Offsets of the given blocks in the buffer. + */ + private LinkedList blockOffsets = new LinkedList(); + + /** + * Source positions of the given blocks in the buffer. + */ + private LinkedList blockPositions = new LinkedList(); + + /** + * Provides a lock to wait for more data to arrive. + */ + private final Object lock = new Object(); + + /** + * An input stream to use when comparing data back to what it should look like. + */ + private final BlockCompressedInputStream validatingInputStream; + + /** + * Has the buffer been filled since last request? + */ + private boolean bufferFilled = false; + + /** + * Create a new block presenting input stream with a dedicated buffer. + * @param dispatcher the block loading messenger. + * @param reader the reader for which to load data. + * @param validate validates the contents read into the buffer against the contents of a Picard BlockCompressedInputStream. + */ + BlockInputStream(final BGZFBlockLoadingDispatcher dispatcher, final SAMReaderID reader, final boolean validate) { + this.reader = reader; + this.length = reader.samFile.length(); + + buffer = ByteBuffer.wrap(new byte[64*1024]); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + // The state of the buffer assumes that the range of data written into the buffer appears in the range + // [position,limit), while extra capacity exists in the range [limit,capacity) + buffer.limit(0); + + this.dispatcher = dispatcher; + // TODO: Kill the region when all we want to do is start at the beginning of the stream and run to the end of the stream. + this.position = new SAMReaderPosition(reader,this,new GATKBAMFileSpan(new GATKChunk(0,Long.MAX_VALUE))); + + try { + if(validate) { + System.out.printf("BlockInputStream %s: BGZF block validation mode activated%n",this); + validatingInputStream = new BlockCompressedInputStream(reader.samFile); + // A bug in ValidatingInputStream means that calling getFilePointer() immediately after initialization will result in an NPE. + // Poke the stream to start reading data. + validatingInputStream.available(); + } + else + validatingInputStream = null; + } + catch(IOException ex) { + throw new ReviewedStingException("Unable to validate against Picard input stream",ex); + } + } + + public long length() { + return length; + } + + public long getFilePointer() { + long filePointer; + synchronized(lock) { + if(buffer.remaining() > 0) { + // If there's data in the buffer, figure out from whence it came. + final long blockAddress = blockPositions.size() > 0 ? blockPositions.get(0) : 0; + final int blockOffset = buffer.position(); + filePointer = blockAddress << 16 | blockOffset; + } + else { + // Otherwise, find the next position to load. + filePointer = position.getBlockAddress() << 16; + } + } + + if(validatingInputStream != null && filePointer != validatingInputStream.getFilePointer()) + throw new ReviewedStingException(String.format("Position of input stream is invalid; expected (block address, block offset) = (%d,%d), got (%d,%d)", + BlockCompressedFilePointerUtil.getBlockAddress(filePointer),BlockCompressedFilePointerUtil.getBlockOffset(filePointer), + BlockCompressedFilePointerUtil.getBlockAddress(validatingInputStream.getFilePointer()),BlockCompressedFilePointerUtil.getBlockOffset(validatingInputStream.getFilePointer()))); + + return filePointer; + } + + public void seek(long target) { + // TODO: Validate the seek point. + //System.out.printf("Thread %s, BlockInputStream %s: seeking to block %d, offset %d%n",Thread.currentThread().getId(),this,BlockCompressedFilePointerUtil.getBlockAddress(target),BlockCompressedFilePointerUtil.getBlockOffset(target)); + synchronized(lock) { + clearBuffers(); + position.advancePosition(BlockCompressedFilePointerUtil.getBlockAddress(target)); + waitForBufferFill(); + buffer.position(BlockCompressedFilePointerUtil.getBlockOffset(target)); + + if(validatingInputStream != null) { + try { + validatingInputStream.seek(target); + } + catch(IOException ex) { + throw new ReviewedStingException("Unable to validate against Picard input stream",ex); + } + } + } + } + + private void clearBuffers() { + this.position.reset(); + + // Buffer semantics say that outside of a lock, buffer should always be prepared for reading. + // Indicate no data to be read. + buffer.clear(); + buffer.limit(0); + + blockOffsets.clear(); + blockPositions.clear(); + } + + public boolean eof() { + synchronized(lock) { + // TODO: Handle multiple empty BGZF blocks at end of the file. + return position != null && position.getBlockAddress() >= length; + } + } + + public void setCheckCrcs(final boolean check) { + // TODO: Implement + } + + /** + * Submits a new access plan for the given dataset. + * @param position The next seek point for BAM data in this reader. + */ + public void submitAccessPlan(final SAMReaderPosition position) { + //System.out.printf("Thread %s: submitting access plan for block at position: %d%n",Thread.currentThread().getId(),position.getBlockAddress()); + synchronized(lock) { + // Assume that the access plan is going to tell us to start where we are and move forward. + // If this isn't the case, we'll soon receive a seek request and the buffer will be forced to reset. + if(this.position != null && position.getBlockAddress() < this.position.getBlockAddress()) + position.advancePosition(this.position.getBlockAddress()); + } + this.position = position; + } + + private void compactBuffer() { + // Compact buffer to maximize storage space. + int bytesToRemove = 0; + + // Look ahead to see if we can compact away the first block in the series. + while(blockOffsets.size() > 1 && buffer.position() < blockOffsets.get(1)) { + bytesToRemove += blockOffsets.remove(); + blockPositions.remove(); + } + + // If we end up with an empty block at the end of the series, compact this as well. + if(buffer.remaining() == 0 && !blockOffsets.isEmpty() && buffer.position() >= blockOffsets.peek()) { + bytesToRemove += buffer.position(); + blockOffsets.remove(); + blockPositions.remove(); + } + + int finalBufferStart = buffer.position() - bytesToRemove; + int finalBufferSize = buffer.remaining(); + + buffer.position(bytesToRemove); + buffer.compact(); + + buffer.position(finalBufferStart); + buffer.limit(finalBufferStart+finalBufferSize); + } + + /** + * Push contents of incomingBuffer into the end of this buffer. + * MUST be called from a thread that is NOT the reader thread. + * @param incomingBuffer The data being pushed into this input stream. + * @param position target position for the data. + */ + public void copyIntoBuffer(final ByteBuffer incomingBuffer, final SAMReaderPosition position, final long filePosition) { + synchronized(lock) { + try { + compactBuffer(); + // Open up the buffer for more reading. + buffer.limit(buffer.capacity()); + + // Advance the position to take the most recent read into account. + long lastReadPosition = position.getBlockAddress(); + + byte[] validBytes = null; + if(validatingInputStream != null) { + validBytes = new byte[incomingBuffer.remaining()]; + + byte[] currentBytes = new byte[incomingBuffer.remaining()]; + int pos = incomingBuffer.position(); + int lim = incomingBuffer.limit(); + incomingBuffer.get(currentBytes); + + incomingBuffer.limit(lim); + incomingBuffer.position(pos); + + long currentFilePointer = validatingInputStream.getFilePointer(); + validatingInputStream.seek(lastReadPosition << 16); + validatingInputStream.read(validBytes); + validatingInputStream.seek(currentFilePointer); + + if(!Arrays.equals(validBytes,currentBytes)) + throw new ReviewedStingException(String.format("Bytes being inserted into BlockInputStream %s are incorrect",this)); + } + + this.position = position; + position.advancePosition(filePosition); + + if(buffer.remaining() < incomingBuffer.remaining()) { + //System.out.printf("Thread %s: waiting for available space in buffer; buffer remaining = %d, incoming buffer remaining = %d%n",Thread.currentThread().getId(),buffer.remaining(),incomingBuffer.remaining()); + lock.wait(); + //System.out.printf("Thread %s: waited for available space in buffer; buffer remaining = %d, incoming buffer remaining = %d%n", Thread.currentThread().getId(), buffer.remaining(), incomingBuffer.remaining()); + } + + // Queue list of block offsets / block positions. + blockOffsets.add(buffer.position()); + blockPositions.add(lastReadPosition); + + buffer.put(incomingBuffer); + + // Set up the buffer for reading. + buffer.flip(); + bufferFilled = true; + + lock.notify(); + } + catch(Exception ex) { + reportException(ex); + lock.notify(); + } + } + } + + void reportException(Throwable t) { + synchronized(lock) { + this.error = t; + lock.notify(); + } + } + + private void checkForErrors() { + synchronized(lock) { + if(error != null) { + ReviewedStingException toThrow = new ReviewedStingException(String.format("Thread %s, BlockInputStream %s: Unable to retrieve BAM data from disk",Thread.currentThread().getId(),this),error); + toThrow.setStackTrace(error.getStackTrace()); + throw toThrow; + } + } + } + + /** + * Reads the next byte of data from the input stream. + * @return Next byte of data, from 0->255, as an int. + */ + @Override + public int read() { + byte[] singleByte = new byte[1]; + read(singleByte); + return singleByte[0]; + } + + /** + * Fills the given byte array to the extent possible. + * @param bytes byte array to be filled. + * @return The number of bytes actually read. + */ + @Override + public int read(byte[] bytes) { + return read(bytes,0,bytes.length); + } + + @Override + public int read(byte[] bytes, final int offset, final int length) { + int remaining = length; + synchronized(lock) { + while(remaining > 0) { + // Check for error conditions during last read. + checkForErrors(); + + // If completely out of space, queue up another buffer fill. + waitForBufferFill(); + + // Couldn't manage to load any data at all; abort and return what's available. + if(buffer.remaining() == 0) + break; + + int numBytesToCopy = Math.min(buffer.remaining(),remaining); + buffer.get(bytes,length-remaining+offset,numBytesToCopy); + remaining -= numBytesToCopy; + + //if(remaining > 0) + // System.out.printf("Thread %s: read the first %d bytes of a %d byte request%n",Thread.currentThread().getId(),length-remaining,length); + // TODO: Assert that we don't copy across a block boundary + } + + // Notify any waiting threads that some of the contents of the buffer were removed. + if(length-remaining > 0) + lock.notify(); + } + + if(validatingInputStream != null) { + byte[] validBytes = new byte[length]; + try { + validatingInputStream.read(validBytes,offset,length); + for(int i = offset; i < offset+length; i++) { + if(bytes[i] != validBytes[i]) { + System.out.printf("Thread %s: preparing to throw an exception because contents don't match%n",Thread.currentThread().getId()); + throw new ReviewedStingException(String.format("Thread %s: blockInputStream %s attempting to return wrong set of bytes; mismatch at offset %d",Thread.currentThread().getId(),this,i)); + } + } + } + catch(IOException ex) { + throw new ReviewedStingException("Unable to validate against Picard input stream",ex); + } + } + + return length - remaining; + } + + public void close() { + if(validatingInputStream != null) { + try { + validatingInputStream.close(); + } + catch(IOException ex) { + throw new ReviewedStingException("Unable to validate against Picard input stream",ex); + } + } + } + + public String getSource() { + return reader.getSamFilePath(); + } + + private void waitForBufferFill() { + synchronized(lock) { + bufferFilled = false; + if(buffer.remaining() == 0 && !eof()) { + //System.out.printf("Thread %s is waiting for a buffer fill from position %d to buffer %s%n",Thread.currentThread().getId(),position.getBlockAddress(),this); + dispatcher.queueBlockLoad(position); + try { + lock.wait(); + } + catch(InterruptedException ex) { + // TODO: handle me. + throw new ReviewedStingException("Interrupt occurred waiting for buffer to fill",ex); + } + + if(bufferFilled && buffer.remaining() == 0) + throw new RuntimeEOFException("No more data left in InputStream"); + } + } + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BlockLoader.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BlockLoader.java new file mode 100644 index 000000000..ab4299802 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/BlockLoader.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2011, 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 org.broad.tribble.util.BlockCompressedStreamConstants; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; + +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +/** + * An engine for loading blocks. + */ +class BlockLoader implements Runnable { + /** + * Coordinates the input queue. + */ + private BGZFBlockLoadingDispatcher dispatcher; + + /** + * A cache from which to retrieve open file handles. + */ + private final FileHandleCache fileHandleCache; + + /** + * Whether asynchronous decompression should happen. + */ + private final boolean decompress; + + /** + * An direct input buffer for incoming data from disk. + */ + private final ByteBuffer inputBuffer; + + public BlockLoader(final BGZFBlockLoadingDispatcher dispatcher, final FileHandleCache fileHandleCache, final boolean decompress) { + this.dispatcher = dispatcher; + this.fileHandleCache = fileHandleCache; + this.decompress = decompress; + + this.inputBuffer = ByteBuffer.allocateDirect(64*1024 + BlockCompressedStreamConstants.EMPTY_GZIP_BLOCK.length); + inputBuffer.order(ByteOrder.LITTLE_ENDIAN); + } + + public void run() { + for(;;) { + SAMReaderPosition readerPosition = null; + try { + readerPosition = dispatcher.claimNextWorkRequest(); + FileInputStream inputStream = fileHandleCache.claimFileInputStream(readerPosition.getReader()); + + long blockAddress = readerPosition.getBlockAddress(); + //System.out.printf("Thread %s: BlockLoader: copying bytes from %s at position %d into %s%n",Thread.currentThread().getId(),inputStream,blockAddress,readerPosition.getInputStream()); + + ByteBuffer compressedBlock = readBGZFBlock(inputStream,readerPosition.getBlockAddress()); + long nextBlockAddress = position(inputStream); + fileHandleCache.releaseFileInputStream(readerPosition.getReader(),inputStream); + + ByteBuffer block = decompress ? decompressBGZFBlock(compressedBlock) : compressedBlock; + int bytesCopied = block.remaining(); + + BlockInputStream bamInputStream = readerPosition.getInputStream(); + bamInputStream.copyIntoBuffer(block,readerPosition,nextBlockAddress); + + //System.out.printf("Thread %s: BlockLoader: copied %d bytes from %s at position %d into %s%n",Thread.currentThread().getId(),bytesCopied,inputStream,blockAddress,readerPosition.getInputStream()); + } + catch(Throwable error) { + if(readerPosition != null && readerPosition.getInputStream() != null) + readerPosition.getInputStream().reportException(error); + } + } + + } + + private ByteBuffer readBGZFBlock(final FileInputStream inputStream, final long blockAddress) throws IOException { + FileChannel channel = inputStream.getChannel(); + + // Read the block header + channel.position(blockAddress); + + int uncompressedDataSize = 0; + int bufferSize = 0; + + do { + inputBuffer.clear(); + inputBuffer.limit(BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH); + channel.read(inputBuffer); + + // Read out the size of the full BGZF block into a two bit short container, then 'or' that + // value into an int buffer to transfer the bitwise contents into an int. + inputBuffer.flip(); + if(inputBuffer.remaining() != BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH) + throw new ReviewedStingException("BUG: unable to read a the complete block header in one pass."); + + // Verify that the file was read at a valid point. + if(unpackUByte8(inputBuffer,0) != BlockCompressedStreamConstants.GZIP_ID1 || + unpackUByte8(inputBuffer,1) != BlockCompressedStreamConstants.GZIP_ID2 || + unpackUByte8(inputBuffer,3) != BlockCompressedStreamConstants.GZIP_FLG || + unpackUInt16(inputBuffer,10) != BlockCompressedStreamConstants.GZIP_XLEN || + unpackUByte8(inputBuffer,12) != BlockCompressedStreamConstants.BGZF_ID1 || + unpackUByte8(inputBuffer,13) != BlockCompressedStreamConstants.BGZF_ID2) { + throw new ReviewedStingException("BUG: Started reading compressed block at incorrect position"); + } + + inputBuffer.position(BlockCompressedStreamConstants.BLOCK_LENGTH_OFFSET); + bufferSize = unpackUInt16(inputBuffer,BlockCompressedStreamConstants.BLOCK_LENGTH_OFFSET)+1; + + // Adjust buffer limits and finish reading the block. Also read the next header, just in case there's a 0-byte block. + inputBuffer.limit(bufferSize); + inputBuffer.position(BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH); + channel.read(inputBuffer); + + // Check the uncompressed length. If 0 and not at EOF, we'll want to check the next block. + uncompressedDataSize = inputBuffer.getInt(inputBuffer.limit()-4); + //System.out.printf("Uncompressed block size of the current block (at position %d) is %d%n",channel.position()-inputBuffer.limit(),uncompressedDataSize); + } + while(uncompressedDataSize == 0 && channel.position() < channel.size()); + + // Prepare the buffer for reading. + inputBuffer.flip(); + + return inputBuffer; + } + + private ByteBuffer decompressBGZFBlock(final ByteBuffer bgzfBlock) throws DataFormatException { + final int compressedBufferSize = bgzfBlock.remaining(); + + // Determine the uncompressed buffer size ( + bgzfBlock.position(bgzfBlock.limit()-4); + int uncompressedBufferSize = bgzfBlock.getInt(); + byte[] uncompressedContent = new byte[uncompressedBufferSize]; + + // Bound the CDATA section of the buffer. + bgzfBlock.limit(compressedBufferSize-BlockCompressedStreamConstants.BLOCK_FOOTER_LENGTH); + bgzfBlock.position(BlockCompressedStreamConstants.BLOCK_HEADER_LENGTH); + byte[] compressedContent = new byte[bgzfBlock.remaining()]; + ByteBuffer.wrap(compressedContent).put(bgzfBlock); + + // Decompress the buffer. + final Inflater inflater = new Inflater(true); + inflater.setInput(compressedContent); + int bytesUncompressed = inflater.inflate(uncompressedContent); + if(bytesUncompressed != uncompressedBufferSize) + throw new ReviewedStingException("Error decompressing block"); + + return ByteBuffer.wrap(uncompressedContent); + } + + private long position(final FileInputStream inputStream) throws IOException { + return inputStream.getChannel().position(); + } + + private int unpackUByte8(final ByteBuffer buffer,final int position) { + return buffer.get(position) & 0xFF; + } + + private int unpackUInt16(final ByteBuffer buffer,final int position) { + // Read out the size of the full BGZF block into a two bit short container, then 'or' that + // value into an int buffer to transfer the bitwise contents into an int. + return buffer.getShort(position) & 0xFFFF; + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/FileHandleCache.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/FileHandleCache.java new file mode 100644 index 000000000..29de6eb37 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/FileHandleCache.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2011, 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 org.broadinstitute.sting.utils.exceptions.ReviewedStingException; +import org.broadinstitute.sting.utils.exceptions.StingException; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; + +/** + * Caches frequently used file handles. Right now, caches only a single file handle. + * TODO: Generalize to support arbitrary file handle caches. + */ +public class FileHandleCache { + /** + * The underlying data structure storing file handles. + */ + private final FileHandleStorage fileHandleStorage; + + /** + * How many file handles should be kept open at once. + */ + private final int cacheSize; + + /** + * A uniquifier: assign a unique ID to every instance of a file handle. + */ + private final Map keyCounter = new HashMap(); + + /** + * A shared lock, private so that outside users cannot notify it. + */ + private final Object lock = new Object(); + + /** + * Indicates how many file handles are outstanding at this point. + */ + private int numOutstandingFileHandles = 0; + + /** + * Create a new file handle cache of the given cache size. + * @param cacheSize how many readers to hold open at once. + */ + public FileHandleCache(final int cacheSize) { + this.cacheSize = cacheSize; + fileHandleStorage = new FileHandleStorage(); + } + + /** + * Retrieves or opens a file handle for the given reader ID. + * @param key The ke + * @return A file input stream from the cache, if available, or otherwise newly opened. + */ + public FileInputStream claimFileInputStream(final SAMReaderID key) { + synchronized(lock) { + FileInputStream inputStream = findExistingEntry(key); + if(inputStream == null) { + try { + // If the cache is maxed out, wait for another file handle to emerge. + if(numOutstandingFileHandles >= cacheSize) + lock.wait(); + } + catch(InterruptedException ex) { + throw new ReviewedStingException("Interrupted while waiting for a file handle"); + } + inputStream = openInputStream(key); + } + numOutstandingFileHandles++; + + //System.out.printf("Handing input stream %s to thread %s%n",inputStream,Thread.currentThread().getId()); + return inputStream; + } + } + + /** + * Releases the current reader and returns it to the cache. + * @param key The reader. + * @param inputStream The stream being used. + */ + public void releaseFileInputStream(final SAMReaderID key, final FileInputStream inputStream) { + synchronized(lock) { + numOutstandingFileHandles--; + UniqueKey newID = allocateKey(key); + fileHandleStorage.put(newID,inputStream); + // Let any listeners know that another file handle has become available. + lock.notify(); + } + } + + /** + * Finds an existing entry in the storage mechanism. + * @param key Reader. + * @return a cached stream, if available. Otherwise, + */ + private FileInputStream findExistingEntry(final SAMReaderID key) { + int existingHandles = getMostRecentUniquifier(key); + + // See if any of the keys currently exist in the repository. + for(int i = 0; i <= existingHandles; i++) { + UniqueKey uniqueKey = new UniqueKey(key,i); + if(fileHandleStorage.containsKey(uniqueKey)) + return fileHandleStorage.remove(uniqueKey); + } + + return null; + } + + /** + * Gets the most recent uniquifier used for the given reader. + * @param reader Reader for which to determine uniqueness. + * @return + */ + private int getMostRecentUniquifier(final SAMReaderID reader) { + if(keyCounter.containsKey(reader)) + return keyCounter.get(reader); + else return -1; + } + + private UniqueKey allocateKey(final SAMReaderID reader) { + int uniquifier = getMostRecentUniquifier(reader)+1; + keyCounter.put(reader,uniquifier); + return new UniqueKey(reader,uniquifier); + } + + private FileInputStream openInputStream(final SAMReaderID reader) { + try { + return new FileInputStream(reader.getSamFilePath()); + } + catch(IOException ex) { + throw new StingException("Unable to open input file"); + } + } + + private void closeInputStream(final FileInputStream inputStream) { + try { + inputStream.close(); + } + catch(IOException ex) { + throw new StingException("Unable to open input file"); + } + } + + /** + * Actually contains the file handles, purging them as they get too old. + */ + private class FileHandleStorage extends LinkedHashMap { + /** + * Remove the oldest entry + * @param entry Entry to consider removing. + * @return True if the cache size has been exceeded. False otherwise. + */ + @Override + protected boolean removeEldestEntry(Map.Entry entry) { + synchronized (lock) { + if(size() > cacheSize) { + keyCounter.put(entry.getKey().key,keyCounter.get(entry.getKey().key)-1); + closeInputStream(entry.getValue()); + + return true; + } + } + return false; + } + } + + /** + * Uniquifies a key by adding a numerical uniquifier. + */ + private class UniqueKey { + /** + * The file handle's key. + */ + private final SAMReaderID key; + + /** + * A uniquifier, so that multiple of the same reader can exist in the cache. + */ + private final int uniqueID; + + public UniqueKey(final SAMReaderID reader, final int uniqueID) { + this.key = reader; + this.uniqueID = uniqueID; + } + + @Override + public boolean equals(Object other) { + if(!(other instanceof UniqueKey)) + return false; + UniqueKey otherUniqueKey = (UniqueKey)other; + return key.equals(otherUniqueKey.key) && this.uniqueID == otherUniqueKey.uniqueID; + } + + @Override + public int hashCode() { + return key.hashCode(); + } + } + + + +} 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 e4141f61c..df7827250 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 @@ -29,6 +29,7 @@ import net.sf.samtools.GATKBAMFileSpan; import net.sf.samtools.SAMFileSpan; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.Utils; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.interval.IntervalMergingRule; import org.broadinstitute.sting.utils.interval.IntervalUtils; @@ -40,28 +41,25 @@ import java.util.*; */ public class FilePointer { protected final SortedMap fileSpans = new TreeMap(); - protected final BAMOverlap overlap; - protected final List locations; + protected final List locations = new ArrayList(); /** * Does this file pointer point into an unmapped region? */ protected final boolean isRegionUnmapped; - public FilePointer() { - this((BAMOverlap)null); - } - - public FilePointer(final GenomeLoc location) { - this.overlap = null; - this.locations = Collections.singletonList(location); - this.isRegionUnmapped = GenomeLoc.isUnmapped(location); - } - - public FilePointer(final BAMOverlap overlap) { - this.overlap = overlap; - this.locations = new ArrayList(); - this.isRegionUnmapped = false; + public FilePointer(final GenomeLoc... locations) { + this.locations.addAll(Arrays.asList(locations)); + boolean foundMapped = false, foundUnmapped = false; + for(GenomeLoc location: locations) { + if(GenomeLoc.isUnmapped(location)) + foundUnmapped = true; + else + foundMapped = true; + } + if(foundMapped && foundUnmapped) + throw new ReviewedStingException("BUG: File pointers cannot be mixed mapped/unmapped."); + this.isRegionUnmapped = foundUnmapped; } /** @@ -217,4 +215,20 @@ public class FilePointer { fileSpan = fileSpan.union((GATKBAMFileSpan)iterators[i].next().getValue()); combined.addFileSpans(initialElement.getKey(),fileSpan); } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("FilePointer:%n"); + builder.append("\tlocations = {"); + builder.append(Utils.join(";",locations)); + builder.append("}%n\tregions = %n"); + for(Map.Entry entry: fileSpans.entrySet()) { + builder.append(entry.getKey()); + builder.append("= {"); + builder.append(entry.getValue()); + builder.append("}"); + } + return builder.toString(); + } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/IntervalSharder.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/IntervalSharder.java index 4ddf28dce..f78693c27 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/IntervalSharder.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/IntervalSharder.java @@ -25,419 +25,58 @@ package org.broadinstitute.sting.gatk.datasources.reads; import net.sf.picard.util.PeekableIterator; -import net.sf.samtools.AbstractBAMFileIndex; -import net.sf.samtools.Bin; -import net.sf.samtools.BrowseableBAMIndex; -import net.sf.samtools.SAMSequenceRecord; -import org.apache.log4j.Logger; -import org.broadinstitute.sting.utils.GenomeLoc; +import net.sf.samtools.SAMSequenceDictionary; +import org.broadinstitute.sting.utils.GenomeLocParser; import org.broadinstitute.sting.utils.GenomeLocSortedSet; -import org.broadinstitute.sting.utils.collections.Pair; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; -import java.util.*; +import java.util.Iterator; /** - * Shard intervals based on position within the BAM file. - * - * @author mhanna - * @version 0.1 + * Handles the process of aggregating BAM intervals into individual shards. + * TODO: The task performed by IntervalSharder is now better performed by LocusShardBalancer. Merge BAMScheduler and IntervalSharder. */ -public class IntervalSharder { - private static Logger logger = Logger.getLogger(IntervalSharder.class); +public class IntervalSharder implements Iterator { + /** + * The iterator actually laying out the data for BAM scheduling. + */ + private final PeekableIterator wrappedIterator; - public static Iterator shardIntervals(final SAMDataSource dataSource, final GenomeLocSortedSet loci) { - return new IntervalSharder.FilePointerIterator(dataSource,loci); + /** + * The parser, for interval manipulation. + */ + private final GenomeLocParser parser; + + public static IntervalSharder shardOverAllReads(final SAMDataSource dataSource, final GenomeLocParser parser) { + return new IntervalSharder(BAMScheduler.createOverAllReads(dataSource,parser),parser); + } + + public static IntervalSharder shardOverMappedReads(final SAMDataSource dataSource, final SAMSequenceDictionary sequenceDictionary, final GenomeLocParser parser) { + return new IntervalSharder(BAMScheduler.createOverMappedReads(dataSource,sequenceDictionary,parser),parser); + } + + public static IntervalSharder shardOverIntervals(final SAMDataSource dataSource, final GenomeLocSortedSet loci) { + return new IntervalSharder(BAMScheduler.createOverIntervals(dataSource,loci),loci.getGenomeLocParser()); + } + + private IntervalSharder(final BAMScheduler scheduler, final GenomeLocParser parser) { + wrappedIterator = new PeekableIterator(scheduler); + this.parser = parser; + } + + public boolean hasNext() { + return wrappedIterator.hasNext(); } /** - * A lazy-loading iterator over file pointers. + * Accumulate shards where there's no additional cost to processing the next shard in the sequence. + * @return The next file pointer to process. */ - private static class FilePointerIterator implements Iterator { - final SAMDataSource dataSource; - final GenomeLocSortedSet loci; - final PeekableIterator locusIterator; - final Queue cachedFilePointers = new LinkedList(); - - public FilePointerIterator(final SAMDataSource dataSource, final GenomeLocSortedSet loci) { - this.dataSource = dataSource; - this.loci = loci; - locusIterator = new PeekableIterator(loci.iterator()); - advance(); - } - - public boolean hasNext() { - return !cachedFilePointers.isEmpty(); - } - - public FilePointer next() { - if(!hasNext()) - throw new NoSuchElementException("FilePointerIterator iteration is complete"); - FilePointer filePointer = cachedFilePointers.remove(); - if(cachedFilePointers.isEmpty()) - advance(); - return filePointer; - } - - public void remove() { - throw new UnsupportedOperationException("Cannot remove from a FilePointerIterator"); - } - - private void advance() { - GenomeLocSortedSet nextBatch = new GenomeLocSortedSet(loci.getGenomeLocParser()); - String contig = null; - - // If the next section of the BAM to be processed is unmapped, handle this region separately. - while(locusIterator.hasNext() && nextBatch.isEmpty()) { - contig = null; - while(locusIterator.hasNext() && (contig == null || (!GenomeLoc.isUnmapped(locusIterator.peek()) && locusIterator.peek().getContig().equals(contig)))) { - GenomeLoc nextLocus = locusIterator.next(); - contig = nextLocus.getContig(); - nextBatch.add(nextLocus); - } - } - - if(nextBatch.size() > 0) { - cachedFilePointers.addAll(shardIntervalsOnContig(dataSource,contig,nextBatch)); - } - } + public FilePointer next() { + FilePointer current = wrappedIterator.next(); + while(wrappedIterator.hasNext() && current.isRegionUnmapped == wrappedIterator.peek().isRegionUnmapped && current.minus(wrappedIterator.peek()) == 0) + current = current.combine(parser,wrappedIterator.next()); + return current; } - /** - * Merge / split intervals based on an awareness of the structure of the BAM file. - * @param dataSource - * @param contig Contig against which to align the intervals. If null, create a file pointer across unmapped reads. - * @param loci - * @return - */ - private static List shardIntervalsOnContig(final SAMDataSource dataSource, final String contig, final GenomeLocSortedSet loci) { - // If the contig is null, eliminate the chopping process and build out a file pointer consisting of the unmapped region of all BAMs. - if(contig == null) { - FilePointer filePointer = new FilePointer(GenomeLoc.UNMAPPED); - for(SAMReaderID id: dataSource.getReaderIDs()) - filePointer.addFileSpans(id,null); - return Collections.singletonList(filePointer); - } - - // Gather bins for the given loci, splitting loci as necessary so that each falls into exactly one lowest-level bin. - List filePointers = new ArrayList(); - FilePointer lastFilePointer = null; - BAMOverlap lastBAMOverlap = null; - - Map readerToIndexMap = new HashMap(); - IntervalSharder.BinMergingIterator binMerger = new IntervalSharder.BinMergingIterator(); - for(SAMReaderID id: dataSource.getReaderIDs()) { - final SAMSequenceRecord referenceSequence = dataSource.getHeader(id).getSequence(contig); - // If this contig can't be found in the reference, skip over it. - if(referenceSequence == null && contig != null) - continue; - final BrowseableBAMIndex index = (BrowseableBAMIndex)dataSource.getIndex(id); - binMerger.addReader(id, - index, - referenceSequence.getSequenceIndex(), - index.getBinsOverlapping(referenceSequence.getSequenceIndex(),1,referenceSequence.getSequenceLength()).iterator()); - // Cache the reader for later data lookup. - readerToIndexMap.put(id,index); - } - - PeekableIterator binIterator = new PeekableIterator(binMerger); - - for(GenomeLoc location: loci) { - if(!location.getContig().equals(contig)) - throw new ReviewedStingException("Location outside bounds of contig"); - - if(!binIterator.hasNext()) - break; - - int locationStart = location.getStart(); - final int locationStop = location.getStop(); - - // Advance to first bin. - while(binIterator.peek().stop < locationStart) - binIterator.next(); - - // Add all relevant bins to a list. If the given bin extends beyond the end of the current interval, make - // sure the extending bin is not pruned from the list. - List bamOverlaps = new ArrayList(); - while(binIterator.hasNext() && binIterator.peek().stop <= locationStop) - bamOverlaps.add(binIterator.next()); - if(binIterator.hasNext() && binIterator.peek().start <= locationStop) - bamOverlaps.add(binIterator.peek()); - - // Bins found; try to match bins with locations. - Iterator bamOverlapIterator = bamOverlaps.iterator(); - - while(locationStop >= locationStart) { - int binStart = lastFilePointer!=null ? lastFilePointer.overlap.start : 0; - int binStop = lastFilePointer!=null ? lastFilePointer.overlap.stop : 0; - - while(binStop < locationStart && bamOverlapIterator.hasNext()) { - if(lastFilePointer != null && lastFilePointer.locations.size() > 0) - filePointers.add(lastFilePointer); - - lastBAMOverlap = bamOverlapIterator.next(); - lastFilePointer = new FilePointer(lastBAMOverlap); - binStart = lastFilePointer.overlap.start; - binStop = lastFilePointer.overlap.stop; - } - - if(locationStart < binStart) { - // The region starts before the first bin in the sequence. Add the region occurring before the sequence. - if(lastFilePointer != null && lastFilePointer.locations.size() > 0) { - filePointers.add(lastFilePointer); - lastFilePointer = null; - lastBAMOverlap = null; - } - - final int regionStop = Math.min(locationStop,binStart-1); - - GenomeLoc subset = loci.getGenomeLocParser().createGenomeLoc(location.getContig(),locationStart,regionStop); - lastFilePointer = new FilePointer(subset); - - locationStart = regionStop + 1; - } - else if(locationStart > binStop) { - // The region starts after the last bin in the sequence. Add the region occurring after the sequence. - if(lastFilePointer != null && lastFilePointer.locations.size() > 0) { - filePointers.add(lastFilePointer); - lastFilePointer = null; - lastBAMOverlap = null; - } - - GenomeLoc subset = loci.getGenomeLocParser().createGenomeLoc(location.getContig(),locationStart,locationStop); - filePointers.add(new FilePointer(subset)); - - locationStart = locationStop + 1; - } - else { - if(lastFilePointer == null) - throw new ReviewedStingException("Illegal state: initializer failed to create cached file pointer."); - - // The start of the region overlaps the bin. Add the overlapping subset. - final int regionStop = Math.min(locationStop,binStop); - lastFilePointer.addLocation(loci.getGenomeLocParser().createGenomeLoc(location.getContig(),locationStart,regionStop)); - locationStart = regionStop + 1; - } - } - } - - if(lastFilePointer != null && lastFilePointer.locations.size() > 0) - filePointers.add(lastFilePointer); - - // Lookup the locations for every file pointer in the index. - for(SAMReaderID id: readerToIndexMap.keySet()) { - BrowseableBAMIndex index = readerToIndexMap.get(id); - for(FilePointer filePointer: filePointers) - filePointer.addFileSpans(id,index.getSpanOverlapping(filePointer.overlap.getBin(id))); - } - - return filePointers; - } - - private static class BinMergingIterator implements Iterator { - private PriorityQueue binQueue = new PriorityQueue(); - private Queue pendingOverlaps = new LinkedList(); - - public void addReader(final SAMReaderID id, final BrowseableBAMIndex index, final int referenceSequence, Iterator bins) { - binQueue.add(new BinQueueState(id,index,referenceSequence,new IntervalSharder.LowestLevelBinFilteringIterator(index,bins))); - } - - public boolean hasNext() { - return pendingOverlaps.size() > 0 || !binQueue.isEmpty(); - } - - public BAMOverlap next() { - if(!hasNext()) - throw new NoSuchElementException("No elements left in merging iterator"); - if(pendingOverlaps.isEmpty()) - advance(); - return pendingOverlaps.remove(); - } - - public void advance() { - List bins = new ArrayList(); - int boundsStart, boundsStop; - - // Prime the pump - if(binQueue.isEmpty()) - return; - bins.add(getNextBin()); - boundsStart = bins.get(0).getStart(); - boundsStop = bins.get(0).getStop(); - - // Accumulate all the bins that overlap the current bin, in sorted order. - while(!binQueue.isEmpty() && peekNextBin().getStart() <= boundsStop) { - ReaderBin bin = getNextBin(); - bins.add(bin); - boundsStart = Math.min(boundsStart,bin.getStart()); - boundsStop = Math.max(boundsStop,bin.getStop()); - } - - List> range = new ArrayList>(); - int start = bins.get(0).getStart(); - int stop = bins.get(0).getStop(); - while(start <= boundsStop) { - // Find the next stopping point. - for(ReaderBin bin: bins) { - stop = Math.min(stop,bin.getStop()); - if(start < bin.getStart()) - stop = Math.min(stop,bin.getStart()-1); - } - - range.add(new Pair(start,stop)); - // If the last entry added included the last element, stop. - if(stop >= boundsStop) - break; - - // Find the next start. - start = stop + 1; - for(ReaderBin bin: bins) { - if(start >= bin.getStart() && start <= bin.getStop()) - break; - else if(start < bin.getStart()) { - start = bin.getStart(); - break; - } - } - } - - // Add the next series of BAM overlaps to the window. - for(Pair window: range) { - BAMOverlap bamOverlap = new BAMOverlap(window.first,window.second); - for(ReaderBin bin: bins) - bamOverlap.addBin(bin.id,bin.bin); - pendingOverlaps.add(bamOverlap); - } - } - - public void remove() { throw new UnsupportedOperationException("Cannot remove from a merging iterator."); } - - private ReaderBin peekNextBin() { - if(binQueue.isEmpty()) - throw new NoSuchElementException("No more bins are available"); - BinQueueState current = binQueue.peek(); - return new ReaderBin(current.getReaderID(),current.getIndex(),current.getReferenceSequence(),current.peekNextBin()); - } - - private ReaderBin getNextBin() { - if(binQueue.isEmpty()) - throw new NoSuchElementException("No more bins are available"); - BinQueueState current = binQueue.remove(); - ReaderBin readerBin = new ReaderBin(current.getReaderID(),current.getIndex(),current.getReferenceSequence(),current.nextBin()); - if(current.hasNextBin()) - binQueue.add(current); - return readerBin; - } - - } - - /** - * Filters out bins not at the lowest level in the tree. - */ - private static class LowestLevelBinFilteringIterator implements Iterator { - private BrowseableBAMIndex index; - private Iterator wrappedIterator; - - private Bin nextBin; - - public LowestLevelBinFilteringIterator(final BrowseableBAMIndex index, Iterator iterator) { - this.index = index; - this.wrappedIterator = iterator; - advance(); - } - - public boolean hasNext() { - return nextBin != null; - } - - public Bin next() { - Bin bin = nextBin; - advance(); - return bin; - } - - public void remove() { throw new UnsupportedOperationException("Remove operation is not supported"); } - - private void advance() { - nextBin = null; - while(wrappedIterator.hasNext() && nextBin == null) { - Bin bin = wrappedIterator.next(); - if(index.getLevelForBin(bin) == AbstractBAMFileIndex.getNumIndexLevels()-1) - nextBin = bin; - } - } - } + public void remove() { throw new UnsupportedOperationException("Unable to remove from an interval sharder."); } } - -class BinQueueState implements Comparable { - private final SAMReaderID id; - private final BrowseableBAMIndex index; - private final int referenceSequence; - private final PeekableIterator bins; - - private int firstLocusInCurrentBin; - private int lastLocusInCurrentBin; - - public BinQueueState(final SAMReaderID id, final BrowseableBAMIndex index, final int referenceSequence, final Iterator bins) { - this.id = id; - this.index = index; - this.referenceSequence = referenceSequence; - this.bins = new PeekableIterator(bins); - refreshLocusInBinCache(); - } - - public SAMReaderID getReaderID() { - return id; - } - - public BrowseableBAMIndex getIndex() { - return index; - } - - public int getReferenceSequence() { - return referenceSequence; - } - - public boolean hasNextBin() { - return bins.hasNext(); - } - - public Bin peekNextBin() { - return bins.peek(); - } - - public Bin nextBin() { - Bin nextBin = bins.next(); - refreshLocusInBinCache(); - return nextBin; - } - - public int compareTo(org.broadinstitute.sting.gatk.datasources.reads.BinQueueState other) { - if(!this.bins.hasNext() && !other.bins.hasNext()) return 0; - if(!this.bins.hasNext()) return -1; - if(!this.bins.hasNext()) return 1; - - // Both BinQueueStates have next bins. Before proceeding, make sure the bin cache is valid. - if(this.firstLocusInCurrentBin <= 0 || this.lastLocusInCurrentBin <= 0 || - other.firstLocusInCurrentBin <= 0 || other.lastLocusInCurrentBin <= 0) { - throw new ReviewedStingException("Sharding mechanism error - bin->locus cache is invalid."); - } - - // Straight integer subtraction works here because lhsStart, rhsStart always positive. - if(this.firstLocusInCurrentBin != other.firstLocusInCurrentBin) - return this.firstLocusInCurrentBin - other.firstLocusInCurrentBin; - - // Straight integer subtraction works here because lhsStop, rhsStop always positive. - return this.lastLocusInCurrentBin - other.lastLocusInCurrentBin; - } - - private void refreshLocusInBinCache() { - firstLocusInCurrentBin = -1; - lastLocusInCurrentBin = -1; - if(bins.hasNext()) { - Bin bin = bins.peek(); - firstLocusInCurrentBin = index.getFirstLocusInBin(bin); - lastLocusInCurrentBin = index.getLastLocusInBin(bin); - } - } -} \ No newline at end of file diff --git a/public/java/src/net/sf/samtools/GATKBinList.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LocusShardBalancer.java similarity index 52% rename from public/java/src/net/sf/samtools/GATKBinList.java rename to public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LocusShardBalancer.java index b53062aaf..585b63457 100644 --- a/public/java/src/net/sf/samtools/GATKBinList.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LocusShardBalancer.java @@ -22,30 +22,34 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -package net.sf.samtools; +package org.broadinstitute.sting.gatk.datasources.reads; -import java.util.BitSet; +import java.util.Iterator; /** - * A temporary solution to work around Java access rights issues: - * override chunk and make it public. - * TODO: Eliminate once we determine the final fate of the BAM index reading code. + * Batch granular file pointers into potentially larger shards. */ -public class GATKBinList extends BinList { +public class LocusShardBalancer extends ShardBalancer { /** - * Create a new BinList over sequenceCount sequences, consisting of the given bins. - * @param referenceSequence Reference sequence to which these bins are relevant. - * @param bins The given bins to include. + * Convert iterators of file pointers into balanced iterators of shards. + * @return An iterator over balanced shards. */ - public GATKBinList(final int referenceSequence, final BitSet bins) { - super(referenceSequence,bins); - } + public Iterator iterator() { + return new Iterator() { + public boolean hasNext() { + return filePointers.hasNext(); + } - /** - * Retrieves the bins stored in this list. - * @return A bitset where a bin is present in the list if the bit is true. - */ - public BitSet getBins() { - return super.getBins(); + public Shard next() { + FilePointer current = filePointers.next(); + while(filePointers.hasNext() && current.minus(filePointers.peek()) == 0) + current = current.combine(parser,filePointers.next()); + return new LocusShard(parser,readsDataSource,current.getLocations(),current.fileSpans); + } + + public void remove() { + throw new UnsupportedOperationException("Unable to remove from shard balancing iterator"); + } + }; } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LocusShardStrategy.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LocusShardStrategy.java deleted file mode 100755 index a5ca07853..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LocusShardStrategy.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (c) 2010, 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.picard.reference.IndexedFastaSequenceFile; -import net.sf.samtools.SAMFileHeader; -import net.sf.samtools.SAMFileSpan; -import net.sf.samtools.SAMSequenceRecord; -import org.broadinstitute.sting.utils.GenomeLoc; -import org.broadinstitute.sting.utils.GenomeLocParser; -import org.broadinstitute.sting.utils.GenomeLocSortedSet; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -/** - * A sharding strategy for loci based on reading of the index. - */ -public class LocusShardStrategy implements ShardStrategy { - /** - * The data source to use when performing this sharding. - */ - private final SAMDataSource reads; - - /** - * the parser for creating shards - */ - private GenomeLocParser genomeLocParser; - - /** - * An iterator through the available file pointers. - */ - private final Iterator filePointerIterator; - - /** - * construct the shard strategy from a seq dictionary, a shard size, and and genomeLocs - * @param reads Data source from which to load index data. - * @param locations List of locations for which to load data. - */ - public LocusShardStrategy(SAMDataSource reads, IndexedFastaSequenceFile reference, GenomeLocParser genomeLocParser, GenomeLocSortedSet locations) { - this.reads = reads; - this.genomeLocParser = genomeLocParser; - - if(!reads.isEmpty()) { - GenomeLocSortedSet intervals; - if(locations == null) { - // If no locations were passed in, shard the entire BAM file. - SAMFileHeader header = reads.getHeader(); - intervals = new GenomeLocSortedSet(genomeLocParser); - - for(SAMSequenceRecord readsSequenceRecord: header.getSequenceDictionary().getSequences()) { - // Check this sequence against the reference sequence dictionary. - // TODO: Do a better job of merging reads + reference. - SAMSequenceRecord refSequenceRecord = reference.getSequenceDictionary().getSequence(readsSequenceRecord.getSequenceName()); - if(refSequenceRecord != null) { - final int length = Math.min(readsSequenceRecord.getSequenceLength(),refSequenceRecord.getSequenceLength()); - intervals.add(genomeLocParser.createGenomeLoc(readsSequenceRecord.getSequenceName(),1,length)); - } - } - } - else - intervals = locations; - - if(reads.isLowMemoryShardingEnabled()) { - /* - Iterator filePointerIterator = new LowMemoryIntervalSharder(this.reads,intervals); - List filePointers = new ArrayList(); - while(filePointerIterator.hasNext()) - filePointers.add(filePointerIterator.next()); - this.filePointerIterator = filePointers.iterator(); - */ - this.filePointerIterator = new LowMemoryIntervalSharder(this.reads,intervals); - } - else - this.filePointerIterator = IntervalSharder.shardIntervals(this.reads,intervals); - } - else { - final int maxShardSize = 100000; - List filePointers = new ArrayList(); - if(locations == null) { - for(SAMSequenceRecord refSequenceRecord: reference.getSequenceDictionary().getSequences()) { - for(int shardStart = 1; shardStart <= refSequenceRecord.getSequenceLength(); shardStart += maxShardSize) { - final int shardStop = Math.min(shardStart+maxShardSize-1, refSequenceRecord.getSequenceLength()); - filePointers.add(new FilePointer(genomeLocParser.createGenomeLoc(refSequenceRecord.getSequenceName(),shardStart,shardStop))); - } - } - } - else { - for(GenomeLoc interval: locations) { - while(interval.size() > maxShardSize) { - filePointers.add(new FilePointer(locations.getGenomeLocParser().createGenomeLoc(interval.getContig(),interval.getStart(),interval.getStart()+maxShardSize-1))); - interval = locations.getGenomeLocParser().createGenomeLoc(interval.getContig(),interval.getStart()+maxShardSize,interval.getStop()); - } - filePointers.add(new FilePointer(interval)); - } - } - filePointerIterator = filePointers.iterator(); - } - - } - - /** - * returns true if there are additional shards - * - * @return false if we're done processing shards - */ - public boolean hasNext() { - return filePointerIterator.hasNext(); - } - - public long shardNumber = 0; - - /** - * gets the next Shard - * - * @return the next shard - */ - public LocusShard next() { - FilePointer nextFilePointer = filePointerIterator.next(); - Map fileSpansBounding = nextFilePointer.fileSpans != null ? nextFilePointer.fileSpans : null; - - /* - System.out.printf("Shard %d: interval = {",++shardNumber); - for(GenomeLoc locus: nextFilePointer.locations) - System.out.printf("%s;",locus); - System.out.printf("}; "); - - if(fileSpansBounding == null) - System.out.printf("no shard data%n"); - else { - SortedMap sortedSpans = new TreeMap(fileSpansBounding); - for(Map.Entry entry: sortedSpans.entrySet()) { - System.out.printf("Shard %d:%s = {%s}%n",shardNumber,entry.getKey().samFile,entry.getValue()); - } - } - */ - - return new LocusShard(genomeLocParser, reads,nextFilePointer.locations,fileSpansBounding); - } - - /** we don't support the remove command */ - public void remove() { - throw new UnsupportedOperationException("ShardStrategies don't support remove()"); - } - - /** - * makes the IntervalShard iterable, i.e. usable in a for loop. - * - * @return - */ - public Iterator iterator() { - return this; - } -} \ No newline at end of file diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LowMemoryIntervalSharder.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LowMemoryIntervalSharder.java deleted file mode 100644 index bf5f33dc3..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/LowMemoryIntervalSharder.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2011, 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.picard.util.PeekableIterator; -import org.broadinstitute.sting.utils.GenomeLocParser; -import org.broadinstitute.sting.utils.GenomeLocSortedSet; - -import java.util.Iterator; - -/** - * Handles the process of aggregating BAM intervals into individual shards. - */ -public class LowMemoryIntervalSharder implements Iterator { - /** - * The iterator actually laying out the data for BAM scheduling. - */ - private final PeekableIterator wrappedIterator; - - /** - * The parser, for interval manipulation. - */ - private final GenomeLocParser parser; - - public LowMemoryIntervalSharder(final SAMDataSource dataSource, final GenomeLocSortedSet loci) { - wrappedIterator = new PeekableIterator(new BAMScheduler(dataSource,loci)); - parser = loci.getGenomeLocParser(); - } - - public boolean hasNext() { - return wrappedIterator.hasNext(); - } - - /** - * Accumulate shards where there's no additional cost to processing the next shard in the sequence. - * @return The next file pointer to process. - */ - public FilePointer next() { - FilePointer current = wrappedIterator.next(); - while(wrappedIterator.hasNext() && current.isRegionUnmapped == wrappedIterator.peek().isRegionUnmapped && current.minus(wrappedIterator.peek()) == 0) - current = current.combine(parser,wrappedIterator.next()); - return current; - } - - public void remove() { throw new UnsupportedOperationException("Unable to remove from an interval sharder."); } -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/MonolithicShard.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/MonolithicShard.java deleted file mode 100644 index 278eeb898..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/MonolithicShard.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.broadinstitute.sting.gatk.datasources.reads; - -import org.broadinstitute.sting.utils.GenomeLoc; -import org.broadinstitute.sting.utils.GenomeLocParser; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; - -import java.util.List; - -/** - * A single, monolithic shard bridging all available data. - * @author mhanna - * @version 0.1 - */ -public class MonolithicShard extends Shard { - /** - * Creates a new monolithic shard of the given type. - * @param shardType Type of the shard. Must be either read or locus; cannot be intervalic. - * @param locs Intervals that this monolithic shard should process. - */ - public MonolithicShard(GenomeLocParser parser, SAMDataSource readsDataSource, ShardType shardType, List locs) { - super(parser, shardType, locs, readsDataSource, null, false); - if(shardType != ShardType.LOCUS && shardType != ShardType.READ) - throw new ReviewedStingException("Invalid shard type for monolithic shard: " + shardType); - } - - /** - * String representation of this shard. - * @return "entire genome". - */ - @Override - public String toString() { - return "entire genome"; - } -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/MonolithicShardStrategy.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/MonolithicShardStrategy.java deleted file mode 100644 index 28b737f28..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/MonolithicShardStrategy.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.broadinstitute.sting.gatk.datasources.reads; - -import org.broadinstitute.sting.utils.GenomeLoc; -import org.broadinstitute.sting.utils.GenomeLocParser; - -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; - -/** - * Create a giant shard representing all the data in the input BAM(s). - * - * @author mhanna - * @version 0.1 - */ -public class MonolithicShardStrategy implements ShardStrategy { - /** - * The single shard associated with this sharding strategy. - */ - private MonolithicShard shard; - - /** - * Create a new shard strategy for shards of the given type. - * @param shardType The shard type. - */ - public MonolithicShardStrategy(final GenomeLocParser parser, final SAMDataSource readsDataSource, final Shard.ShardType shardType, final List region) { - shard = new MonolithicShard(parser,readsDataSource,shardType,region); - } - - /** - * Convenience for using in a foreach loop. Will NOT create a new, reset instance of the iterator; - * will only return another copy of the active iterator. - * @return A copy of this. - */ - public Iterator iterator() { - return this; - } - - /** - * Returns true if the monolithic shard has not yet been consumed, or false otherwise. - * @return True if shard has been consumed, false otherwise. - */ - public boolean hasNext() { - return shard != null; - } - - /** - * Returns the monolithic shard if it has not already been retrieved. - * @return The monolithic shard. - * @throws NoSuchElementException if no such data exists. - */ - public Shard next() { - if(shard == null) - throw new NoSuchElementException("Monolithic shard has already been retrived."); - - Shard working = shard; - shard = null; - return working; - } - - /** - * Mandated by the interface, but is unsupported in this context. Will throw an exception always. - */ - public void remove() { - throw new UnsupportedOperationException("Cannot remove from a shard strategy"); - } - - /** - * Mandated by the interface, but is unsupported in this context. Will throw an exception always. - * @param size adjust the next size to this - */ - public void adjustNextShardSize( long size ) { - throw new UnsupportedOperationException("Cannot adjust the next size of a monolithic shard; there will be no next shard."); - } - -} - diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShard.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShard.java index 4d9c9092d..5f40c0ea5 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShard.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShard.java @@ -35,10 +35,15 @@ import java.util.Map; * @version 0.1 */ public class ReadShard extends Shard { + /** + * What is the maximum number of reads which should go into a read shard. + */ + public static final int MAX_READS = 10000; + /** * The reads making up this shard. */ - private final Collection reads = new ArrayList(ReadShardStrategy.MAX_READS); + private final Collection reads = new ArrayList(MAX_READS); public ReadShard(GenomeLocParser parser, SAMDataSource readsDataSource, Map fileSpans, List loci, boolean isUnmapped) { super(parser, ShardType.READ, loci, readsDataSource, fileSpans, isUnmapped); @@ -66,7 +71,7 @@ public class ReadShard extends Shard { * @return True if this shard's buffer is full (and the shard can buffer reads). */ public boolean isBufferFull() { - return reads.size() > ReadShardStrategy.MAX_READS; + return reads.size() > ReadShard.MAX_READS; } /** diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShardBalancer.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShardBalancer.java new file mode 100644 index 000000000..fa8a7d454 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShardBalancer.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2011, 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.GATKBAMFileSpan; +import net.sf.samtools.SAMFileSpan; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; + +/** + * Divide up large file pointers containing reads into more manageable subcomponents. + */ +public class ReadShardBalancer extends ShardBalancer { + /** + * Convert iterators of file pointers into balanced iterators of shards. + * @return An iterator over balanced shards. + */ + public Iterator iterator() { + return new Iterator() { + /** + * The cached shard to be returned next. Prefetched in the peekable iterator style. + */ + private Shard nextShard = null; + + /** + * The file pointer currently being processed. + */ + private FilePointer currentFilePointer; + + /** + * Ending position of the last shard in the file. + */ + private Map position = readsDataSource.getCurrentPosition(); + + { + if(filePointers.hasNext()) + currentFilePointer = filePointers.next(); + advance(); + } + + public boolean hasNext() { + return nextShard != null; + } + + public Shard next() { + if(!hasNext()) + throw new NoSuchElementException("No next read shard available"); + Shard currentShard = nextShard; + advance(); + return currentShard; + } + + public void remove() { + throw new UnsupportedOperationException("Unable to remove from shard balancing iterator"); + } + + private void advance() { + Map shardPosition; + nextShard = null; + + Map selectedReaders = new HashMap(); + while(selectedReaders.size() == 0 && currentFilePointer != null) { + shardPosition = currentFilePointer.fileSpans; + + for(SAMReaderID id: shardPosition.keySet()) { + SAMFileSpan fileSpan = new GATKBAMFileSpan(shardPosition.get(id).removeContentsBefore(position.get(id))); + if(!fileSpan.isEmpty()) + selectedReaders.put(id,fileSpan); + } + + if(selectedReaders.size() > 0) { + Shard shard = new ReadShard(parser,readsDataSource,selectedReaders,currentFilePointer.locations,currentFilePointer.isRegionUnmapped); + readsDataSource.fillShard(shard); + + if(!shard.isBufferEmpty()) { + nextShard = shard; + break; + } + } + + selectedReaders.clear(); + currentFilePointer = filePointers.hasNext() ? filePointers.next() : null; + } + + position = readsDataSource.getCurrentPosition(); + } + }; + } + +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShardStrategy.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShardStrategy.java deleted file mode 100755 index 5ea75dbb0..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReadShardStrategy.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (c) 2010, 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.SAMFileSpan; -import org.broadinstitute.sting.utils.GenomeLocParser; -import org.broadinstitute.sting.utils.GenomeLocSortedSet; - -import java.util.*; - -/** - * The sharding strategy for reads using a simple counting mechanism. Each read shard - * has a specific number of reads (default to 10K) which is configured in the constructor. - * @author aaron - * @version 1.0 - * @date Apr 14, 2009 - */ -public class ReadShardStrategy implements ShardStrategy { - /** - * What is the maximum number of reads which should go into a read shard. - */ - protected static final int MAX_READS = 10000; - - /** - * The data source used to shard. - */ - private final SAMDataSource dataSource; - - /** - * The intervals to be processed. - */ - private final GenomeLocSortedSet locations; - - /** - * The cached shard to be returned next. Prefetched in the peekable iterator style. - */ - private Shard nextShard = null; - - /** our storage of the genomic locations they'd like to shard over */ - private final List filePointers = new ArrayList(); - - /** - * Iterator over the list of file pointers. - */ - private final Iterator filePointerIterator; - - /** - * The file pointer currently being processed. - */ - private FilePointer currentFilePointer; - - /** - * Ending position of the last shard in the file. - */ - private Map position; - - /** - * An indicator whether the strategy has sharded into the unmapped region. - */ - private boolean isIntoUnmappedRegion = false; - - private final GenomeLocParser parser; - - /** - * Create a new read shard strategy, loading read shards from the given BAM file. - * @param dataSource Data source from which to load shards. - * @param locations intervals to use for sharding. - */ - public ReadShardStrategy(GenomeLocParser parser, SAMDataSource dataSource, GenomeLocSortedSet locations) { - this.dataSource = dataSource; - this.parser = parser; - this.position = this.dataSource.getCurrentPosition(); - this.locations = locations; - - if(locations != null) - filePointerIterator = dataSource.isLowMemoryShardingEnabled() ? new LowMemoryIntervalSharder(this.dataSource,locations) : IntervalSharder.shardIntervals(this.dataSource,locations); - else - filePointerIterator = filePointers.iterator(); - - if(filePointerIterator.hasNext()) - currentFilePointer = filePointerIterator.next(); - - advance(); - } - - /** - * do we have another read shard? - * @return True if any more data is available. False otherwise. - */ - public boolean hasNext() { - return nextShard != null; - } - - /** - * Retrieves the next shard, if available. - * @return The next shard, if available. - * @throws java.util.NoSuchElementException if no such shard is available. - */ - public Shard next() { - if(!hasNext()) - throw new NoSuchElementException("No next read shard available"); - Shard currentShard = nextShard; - advance(); - return currentShard; - } - - public void advance() { - Map shardPosition = new HashMap(); - nextShard = null; - - if(locations != null) { - Map selectedReaders = new HashMap(); - while(selectedReaders.size() == 0 && currentFilePointer != null) { - shardPosition = currentFilePointer.fileSpans; - - for(SAMReaderID id: shardPosition.keySet()) { - SAMFileSpan fileSpan = shardPosition.get(id).removeContentsBefore(position.get(id)); - if(!fileSpan.isEmpty()) - selectedReaders.put(id,fileSpan); - } - - if(selectedReaders.size() > 0) { - Shard shard = new ReadShard(parser, dataSource,selectedReaders,currentFilePointer.locations,currentFilePointer.isRegionUnmapped); - dataSource.fillShard(shard); - - if(!shard.isBufferEmpty()) { - nextShard = shard; - break; - } - } - - selectedReaders.clear(); - currentFilePointer = filePointerIterator.hasNext() ? filePointerIterator.next() : null; - } - } - else { - // todo -- this nulling of intervals is a bit annoying since readwalkers without - // todo -- any -L values need to be special cased throughout the code. - Shard shard = new ReadShard(parser,dataSource,position,null,false); - dataSource.fillShard(shard); - nextShard = !shard.isBufferEmpty() ? shard : null; - } - - this.position = dataSource.getCurrentPosition(); - } - - /** - * @throws UnsupportedOperationException always. - */ - public void remove() { - throw new UnsupportedOperationException("Remove not supported"); - } - - /** - * Convenience method for using ShardStrategy in an foreach loop. - * @return A iterator over shards. - */ - public Iterator iterator() { - return this; - } -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReaderBin.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReaderBin.java deleted file mode 100644 index c76c1d8ae..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ReaderBin.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.broadinstitute.sting.gatk.datasources.reads; - -import net.sf.samtools.Bin; -import net.sf.samtools.BrowseableBAMIndex; - -/** - * Created by IntelliJ IDEA. - * User: mhanna - * Date: Feb 2, 2011 - * Time: 4:36:40 PM - * To change this template use File | Settings | File Templates. - */ -class ReaderBin { - public final SAMReaderID id; - public final BrowseableBAMIndex index; - public final int referenceSequence; - public final Bin bin; - - public ReaderBin(final SAMReaderID id, final BrowseableBAMIndex index, final int referenceSequence, final Bin bin) { - this.id = id; - this.index = index; - this.referenceSequence = referenceSequence; - this.bin = bin; - } - - public int getStart() { - return index.getFirstLocusInBin(bin); - } - - public int getStop() { - return index.getLastLocusInBin(bin); - } -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMDataSource.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMDataSource.java index 8452aadfd..0a1eb0563 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMDataSource.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMDataSource.java @@ -37,8 +37,10 @@ import org.broadinstitute.sting.gatk.arguments.ValidationExclusion; import org.broadinstitute.sting.gatk.filters.CountingFilteringIterator; import org.broadinstitute.sting.gatk.filters.ReadFilter; import org.broadinstitute.sting.gatk.iterators.*; +import org.broadinstitute.sting.gatk.resourcemanagement.ThreadAllocation; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.GenomeLocSortedSet; import org.broadinstitute.sting.utils.baq.BAQ; import org.broadinstitute.sting.utils.baq.BAQSamIterator; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; @@ -71,7 +73,7 @@ public class SAMDataSource { /** * Tools for parsing GenomeLocs, for verifying BAM ordering against general ordering. */ - private final GenomeLocParser genomeLocParser; + protected final GenomeLocParser genomeLocParser; /** * Identifiers for the readers driving this data source. @@ -91,13 +93,18 @@ public class SAMDataSource { /** * How far along is each reader? */ - private final Map readerPositions = new HashMap(); + private final Map readerPositions = new HashMap(); /** * The merged header. */ private final SAMFileHeader mergedHeader; + /** + * The constituent headers of the unmerged files. + */ + private final Map headers = new HashMap(); + /** * The sort order of the BAM files. Files without a sort order tag are assumed to be * in coordinate order. @@ -131,17 +138,24 @@ public class SAMDataSource { private final SAMResourcePool resourcePool; /** - * Whether to enable the new low-memory sharding mechanism. + * Asynchronously loads BGZF blocks. */ - private boolean enableLowMemorySharding = false; + private final BGZFBlockLoadingDispatcher dispatcher; + + /** + * How are threads allocated. + */ + private final ThreadAllocation threadAllocation; /** * Create a new SAM data source given the supplied read metadata. * @param samFiles list of reads files. */ - public SAMDataSource(Collection samFiles,GenomeLocParser genomeLocParser) { + public SAMDataSource(Collection samFiles, ThreadAllocation threadAllocation, Integer numFileHandles, GenomeLocParser genomeLocParser) { this( samFiles, + threadAllocation, + numFileHandles, genomeLocParser, false, SAMFileReader.ValidationStringency.STRICT, @@ -150,8 +164,7 @@ public class SAMDataSource { new ValidationExclusion(), new ArrayList(), false, - false, - true); + false); } /** @@ -159,6 +172,8 @@ public class SAMDataSource { */ public SAMDataSource( Collection samFiles, + ThreadAllocation threadAllocation, + Integer numFileHandles, GenomeLocParser genomeLocParser, boolean useOriginalBaseQualities, SAMFileReader.ValidationStringency strictness, @@ -167,9 +182,10 @@ public class SAMDataSource { ValidationExclusion exclusionList, Collection supplementalFilters, boolean includeReadsWithDeletionAtLoci, - boolean generateExtendedEvents, - boolean enableLowMemorySharding) { + boolean generateExtendedEvents) { this( samFiles, + threadAllocation, + numFileHandles, genomeLocParser, useOriginalBaseQualities, strictness, @@ -182,8 +198,7 @@ public class SAMDataSource { BAQ.CalculationMode.OFF, BAQ.QualityMode.DONT_MODIFY, null, // no BAQ - (byte) -1, - enableLowMemorySharding); + (byte) -1); } /** @@ -205,6 +220,8 @@ public class SAMDataSource { */ public SAMDataSource( Collection samFiles, + ThreadAllocation threadAllocation, + Integer numFileHandles, GenomeLocParser genomeLocParser, boolean useOriginalBaseQualities, SAMFileReader.ValidationStringency strictness, @@ -217,13 +234,19 @@ public class SAMDataSource { BAQ.CalculationMode cmode, BAQ.QualityMode qmode, IndexedFastaSequenceFile refReader, - byte defaultBaseQualities, - boolean enableLowMemorySharding) { - this.enableLowMemorySharding(enableLowMemorySharding); + byte defaultBaseQualities) { this.readMetrics = new ReadMetrics(); this.genomeLocParser = genomeLocParser; readerIDs = samFiles; + + this.threadAllocation = threadAllocation; + // TODO: Consider a borrowed-thread dispatcher implementation. + if(this.threadAllocation.getNumIOThreads() > 0) + dispatcher = new BGZFBlockLoadingDispatcher(this.threadAllocation.getNumIOThreads(), numFileHandles != null ? numFileHandles : 1); + else + dispatcher = null; + validationStringency = strictness; for (SAMReaderID readerID : samFiles) { if (!readerID.samFile.canRead()) @@ -235,10 +258,13 @@ public class SAMDataSource { SAMReaders readers = resourcePool.getAvailableReaders(); // Determine the sort order. - for(SAMFileReader reader: readers.values()) { + for(SAMReaderID readerID: readerIDs) { // Get the sort order, forcing it to coordinate if unsorted. + SAMFileReader reader = readers.getReader(readerID); SAMFileHeader header = reader.getFileHeader(); + headers.put(readerID,header); + if ( header.getReadGroups().isEmpty() ) { throw new UserException.MalformedBAM(readers.getReaderID(reader).samFile, "SAM file doesn't have any read groups defined in the header. The GATK no longer supports SAM files without read groups"); @@ -275,7 +301,7 @@ public class SAMDataSource { qmode, refReader, defaultBaseQualities); - + // cache the read group id (original) -> read group id (merged) // and read group id (merged) -> read group id (original) mappings. for(SAMReaderID id: readerIDs) { @@ -296,12 +322,10 @@ public class SAMDataSource { originalToMergedReadGroupMappings.put(id,mappingToMerged); } - if(enableLowMemorySharding) { - for(SAMReaderID id: readerIDs) { - File indexFile = findIndexFile(id.samFile); - if(indexFile != null) - bamIndices.put(id,new GATKBAMIndex(indexFile)); - } + for(SAMReaderID id: readerIDs) { + File indexFile = findIndexFile(id.samFile); + if(indexFile != null) + bamIndices.put(id,new GATKBAMIndex(indexFile)); } resourcePool.releaseReaders(readers); @@ -314,22 +338,6 @@ public class SAMDataSource { */ public ReadProperties getReadsInfo() { return readProperties; } - /** - * Enable experimental low-memory sharding. - * @param enable True to enable sharding. False otherwise. - */ - public void enableLowMemorySharding(final boolean enable) { - enableLowMemorySharding = enable; - } - - /** - * Returns whether low-memory sharding is enabled. - * @return True if enabled, false otherwise. - */ - public boolean isLowMemoryShardingEnabled() { - return enableLowMemorySharding; - } - /** * Checks to see whether any reads files are supplying data. * @return True if no reads files are supplying data to the traversal; false otherwise. @@ -368,7 +376,7 @@ public class SAMDataSource { * Retrieves the current position within the BAM file. * @return A mapping of reader to current position. */ - public Map getCurrentPosition() { + public Map getCurrentPosition() { return readerPositions; } @@ -381,7 +389,7 @@ public class SAMDataSource { } public SAMFileHeader getHeader(SAMReaderID id) { - return resourcePool.getReadersWithoutLocking().getReader(id).getFileHeader(); + return headers.get(id); } /** @@ -404,45 +412,21 @@ public class SAMDataSource { return mergedToOriginalReadGroupMappings.get(mergedReadGroupId); } - /** - * No read group collisions at this time because only one SAM file is currently supported. - * @return False always. - */ - public boolean hasReadGroupCollisions() { - return hasReadGroupCollisions; - } - /** * True if all readers have an index. * @return True if all readers have an index. */ public boolean hasIndex() { - if(enableLowMemorySharding) - return readerIDs.size() == bamIndices.size(); - else { - for(SAMFileReader reader: resourcePool.getReadersWithoutLocking()) { - if(!reader.hasIndex()) - return false; - } - return true; - } + return readerIDs.size() == bamIndices.size(); } /** * Gets the index for a particular reader. Always preloaded. - * TODO: Should return object of type GATKBAMIndex, but cannot because there - * TODO: is no parent class of both BAMIndex and GATKBAMIndex. Change when new - * TODO: sharding system goes live. * @param id Id of the reader. * @return The index. Will preload the index if necessary. */ - public Object getIndex(final SAMReaderID id) { - if(enableLowMemorySharding) - return bamIndices.get(id); - else { - SAMReaders readers = resourcePool.getReadersWithoutLocking(); - return readers.getReader(id).getBrowseableIndex(); - } + public GATKBAMIndex getIndex(final SAMReaderID id) { + return bamIndices.get(id); } /** @@ -454,7 +438,7 @@ public class SAMDataSource { } /** - * Gets the cumulative read metrics for shards already processed. + * Gets the cumulative read metrics for shards already processed. * @return Cumulative read metrics. */ public ReadMetrics getCumulativeReadMetrics() { @@ -507,10 +491,6 @@ public class SAMDataSource { } public StingSAMIterator seek(Shard shard) { - // todo: refresh monolithic sharding implementation - if(shard instanceof MonolithicShard) - return seekMonolithic(shard); - if(shard.buffersReads()) { return shard.iterator(); } @@ -540,7 +520,7 @@ public class SAMDataSource { */ private void initializeReaderPositions(SAMReaders readers) { for(SAMReaderID id: getReaderIDs()) - readerPositions.put(id,readers.getReader(id).getFilePointerSpanningReads()); + readerPositions.put(id,new GATKBAMFileSpan(readers.getReader(id).getFilePointerSpanningReads())); } /** @@ -548,7 +528,6 @@ public class SAMDataSource { * @param readers Readers from which to load data. * @param shard The shard specifying the data limits. * @param enableVerification True to verify. For compatibility with old sharding strategy. - * TODO: Collapse this flag when the two sharding systems are merged. * @return An iterator over the selected data. */ private StingSAMIterator getIterator(SAMReaders readers, Shard shard, boolean enableVerification) { @@ -559,14 +538,20 @@ public class SAMDataSource { for(SAMReaderID id: getReaderIDs()) { CloseableIterator iterator = null; - if(!shard.isUnmapped() && shard.getFileSpans().get(id) == null) - continue; - iterator = shard.getFileSpans().get(id) != null ? - readers.getReader(id).iterator(shard.getFileSpans().get(id)) : - readers.getReader(id).queryUnmapped(); + + // TODO: null used to be the signal for unmapped, but we've replaced that with a simple index query for the last bin. + // TODO: Kill this check once we've proven that the design elements are gone. + if(shard.getFileSpans().get(id) == null) + throw new ReviewedStingException("SAMDataSource: received null location for reader " + id + ", but null locations are no longer supported."); + + if(threadAllocation.getNumIOThreads() > 0) { + BlockInputStream inputStream = readers.getInputStream(id); + inputStream.submitAccessPlan(new SAMReaderPosition(id,inputStream,(GATKBAMFileSpan)shard.getFileSpans().get(id))); + } + iterator = readers.getReader(id).iterator(shard.getFileSpans().get(id)); if(readProperties.getReadBufferSize() != null) iterator = new BufferingReadIterator(iterator,readProperties.getReadBufferSize()); - if(shard.getGenomeLocs() != null) + if(shard.getGenomeLocs().size() > 0) iterator = new IntervalOverlapFilteringIterator(iterator,shard.getGenomeLocs()); mergingIterator.addIterator(readers.getReader(id),iterator); } @@ -584,33 +569,6 @@ public class SAMDataSource { readProperties.defaultBaseQualities()); } - /** - * A stopgap measure to handle monolithic sharding - * @param shard the (monolithic) shard. - * @return An iterator over the monolithic shard. - */ - private StingSAMIterator seekMonolithic(Shard shard) { - SAMReaders readers = resourcePool.getAvailableReaders(); - - // Set up merging and filtering to dynamically merge together multiple BAMs and filter out records not in the shard set. - SamFileHeaderMerger headerMerger = new SamFileHeaderMerger(SAMFileHeader.SortOrder.coordinate,readers.headers(),true); - MergingSamRecordIterator mergingIterator = new MergingSamRecordIterator(headerMerger,readers.values(),true); - for(SAMReaderID id: getReaderIDs()) - mergingIterator.addIterator(readers.getReader(id),readers.getReader(id).iterator()); - - return applyDecoratingIterators(shard.getReadMetrics(), - shard instanceof ReadShard, - readProperties.useOriginalBaseQualities(), - new ReleasingIterator(readers,StingSAMIteratorAdapter.adapt(mergingIterator)), - readProperties.getDownsamplingMethod().toFraction, - readProperties.getValidationExclusionList().contains(ValidationExclusion.TYPE.NO_READ_ORDER_VERIFICATION), - readProperties.getSupplementalFilters(), - readProperties.getBAQCalculationMode(), - readProperties.getBAQQualityMode(), - readProperties.getRefReader(), - readProperties.defaultBaseQualities()); - } - /** * Adds this read to the given shard. * @param shard The shard to which to add the read. @@ -618,7 +576,7 @@ public class SAMDataSource { * @param read The read to add to the shard. */ private void addReadToBufferingShard(Shard shard,SAMReaderID id,SAMRecord read) { - SAMFileSpan endChunk = read.getFileSource().getFilePointer().getContentsFollowing(); + GATKBAMFileSpan endChunk = new GATKBAMFileSpan(read.getFileSource().getFilePointer().getContentsFollowing()); shard.addRead(read); readerPositions.put(id,endChunk); } @@ -689,19 +647,6 @@ public class SAMDataSource { this.maxEntries = maxEntries; } - /** - * Dangerous internal method; retrieves any set of readers, whether in iteration or not. - * Used to handle non-exclusive, stateless operations, such as index queries. - * @return Any collection of SAMReaders, whether in iteration or not. - */ - protected SAMReaders getReadersWithoutLocking() { - synchronized(this) { - if(allResources.size() == 0) - createNewResource(); - } - return allResources.get(0); - } - /** * Choose a set of readers from the pool to use for this query. When complete, * @return @@ -753,6 +698,11 @@ public class SAMDataSource { */ private final Map readers = new LinkedHashMap(); + /** + * The inptu streams backing + */ + private final Map inputStreams = new LinkedHashMap(); + /** * Derive a new set of readers from the Reads metadata. * @param readerIDs reads to load. @@ -760,12 +710,20 @@ public class SAMDataSource { */ public SAMReaders(Collection readerIDs, SAMFileReader.ValidationStringency validationStringency) { for(SAMReaderID readerID: readerIDs) { - SAMFileReader reader = new SAMFileReader(readerID.samFile); + File indexFile = findIndexFile(readerID.samFile); + + SAMFileReader reader = null; + + if(threadAllocation.getNumIOThreads() > 0) { + BlockInputStream blockInputStream = new BlockInputStream(dispatcher,readerID,false); + reader = new SAMFileReader(blockInputStream,indexFile,false); + inputStreams.put(readerID,blockInputStream); + } + else + reader = new SAMFileReader(readerID.samFile,indexFile,false); reader.setSAMRecordFactory(factory); + reader.enableFileSource(true); - reader.enableIndexMemoryMapping(false); - if(!enableLowMemorySharding) - reader.enableIndexCaching(true); reader.setValidationStringency(validationStringency); final SAMFileHeader header = reader.getFileHeader(); @@ -786,6 +744,15 @@ public class SAMDataSource { return readers.get(id); } + /** + * Retrieve the input stream backing a reader. + * @param id The ID of the reader to retrieve. + * @return the reader associated with the given id. + */ + public BlockInputStream getInputStream(final SAMReaderID id) { + return inputStreams.get(id); + } + /** * Searches for the reader id of this reader. * @param reader Reader for which to search. @@ -883,7 +850,7 @@ public class SAMDataSource { * Filters out reads that do not overlap the current GenomeLoc. * Note the custom implementation: BAM index querying returns all reads that could * possibly overlap the given region (and quite a few extras). In order not to drag - * down performance, this implementation is highly customized to its task. + * down performance, this implementation is highly customized to its task. */ private class IntervalOverlapFilteringIterator implements CloseableIterator { /** @@ -903,7 +870,7 @@ public class SAMDataSource { /** * Custom representation of interval bounds. - * Makes it simpler to track current position. + * Makes it simpler to track current position. */ private int[] intervalContigIndices; private int[] intervalStarts; @@ -941,7 +908,7 @@ public class SAMDataSource { i++; } } - + advance(); } @@ -1070,6 +1037,40 @@ public class SAMDataSource { return indexFile; } + + /** + * Creates a BAM schedule over all reads in the BAM file, both mapped and unmapped. The outgoing stream + * will be as granular as possible given our current knowledge of the best ways to split up BAM files. + * @return An iterator that spans all reads in all BAM files. + */ + public Iterable createShardIteratorOverAllReads(final ShardBalancer shardBalancer) { + shardBalancer.initialize(this,IntervalSharder.shardOverAllReads(this,genomeLocParser),genomeLocParser); + return shardBalancer; + } + + /** + * Creates a BAM schedule over all mapped reads in the BAM file, when a 'mapped' read is defined as any + * read that has been assigned + * @return + */ + public Iterable createShardIteratorOverMappedReads(final SAMSequenceDictionary sequenceDictionary, final ShardBalancer shardBalancer) { + shardBalancer.initialize(this,IntervalSharder.shardOverMappedReads(this,sequenceDictionary,genomeLocParser),genomeLocParser); + return shardBalancer; + } + + /** + * Create a schedule for processing the initialized BAM file using the given interval list. + * The returned schedule should be as granular as possible. + * @param intervals The list of intervals for which to create the schedule. + * @return A granular iterator over file pointers. + */ + public Iterable createShardIteratorOverIntervals(final GenomeLocSortedSet intervals,final ShardBalancer shardBalancer) { + if(intervals == null) + throw new ReviewedStingException("Unable to create schedule from intervals; no intervals were provided."); + shardBalancer.initialize(this,IntervalSharder.shardOverIntervals(SAMDataSource.this,intervals),genomeLocParser); + return shardBalancer; + } } + diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMReaderPosition.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMReaderPosition.java new file mode 100644 index 000000000..f9f6539a7 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/SAMReaderPosition.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2011, 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.picard.util.PeekableIterator; +import net.sf.samtools.GATKBAMFileSpan; +import net.sf.samtools.GATKChunk; +import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; + +import java.util.List; + +/** +* Created by IntelliJ IDEA. +* User: mhanna +* Date: 10/14/11 +* Time: 10:47 PM +* To change this template use File | Settings | File Templates. +*/ +class SAMReaderPosition { + private final SAMReaderID reader; + private final BlockInputStream inputStream; + + private final List positions; + private PeekableIterator positionIterator; + + /** + * Stores the next block address to read, or -1 if no such block is available. + */ + private long nextBlockAddress; + + + SAMReaderPosition(final SAMReaderID reader, final BlockInputStream inputStream, GATKBAMFileSpan fileSpan) { + this.reader = reader; + this.inputStream = inputStream; + + this.positions = fileSpan.getGATKChunks(); + initialize(); + } + + public SAMReaderID getReader() { + return reader; + } + + public BlockInputStream getInputStream() { + return inputStream; + } + + /** + * Retrieves the next block address to be read. + * @return Next block address to be read. + */ + public long getBlockAddress() { + return nextBlockAddress; + } + + public void reset() { + initialize(); + } + + /** + * Resets the SAM reader position to its original state. + */ + private void initialize() { + this.positionIterator = new PeekableIterator(positions.iterator()); + if(positionIterator.hasNext()) + nextBlockAddress = positionIterator.peek().getBlockStart(); + else + nextBlockAddress = -1; + } + + /** + * Advances the current position to the next block to read, given the current position in the file. + * @param filePosition The current position within the file. + */ + void advancePosition(final long filePosition) { + nextBlockAddress = filePosition; + + // Check the current file position against the iterator; if the iterator is before the current file position, + // draw the iterator forward. Remember when performing the check that coordinates are half-open! + try { + while(positionIterator.hasNext() && isFilePositionPastEndOfChunk(filePosition,positionIterator.peek())) { + positionIterator.next(); + // Check to see if the iterator has more data available. + if(positionIterator.hasNext() && filePosition < positionIterator.peek().getBlockStart()) { + nextBlockAddress = positionIterator.peek().getBlockStart(); + break; + } + } + } + catch(Exception ex) { + throw new ReviewedStingException(""); + } + } + + private boolean isFilePositionPastEndOfChunk(final long filePosition, final GATKChunk chunk) { + return (filePosition > chunk.getBlockEnd() || (filePosition == chunk.getBlockEnd() && chunk.getBlockOffsetEnd() == 0)); + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardBalancer.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardBalancer.java new file mode 100644 index 000000000..962208086 --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardBalancer.java @@ -0,0 +1,21 @@ +package org.broadinstitute.sting.gatk.datasources.reads; + +import net.sf.picard.util.PeekableIterator; +import org.broadinstitute.sting.utils.GenomeLocParser; + +import java.util.Iterator; + +/** + * Balances maximally granular file pointers into shards of reasonable size. + */ +public abstract class ShardBalancer implements Iterable { + protected SAMDataSource readsDataSource; + protected PeekableIterator filePointers; + protected GenomeLocParser parser; + + public void initialize(final SAMDataSource readsDataSource, final Iterator filePointers, final GenomeLocParser parser) { + this.readsDataSource = readsDataSource; + this.filePointers = new PeekableIterator(filePointers); + this.parser = parser; + } +} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardStrategy.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardStrategy.java deleted file mode 100644 index 989cf9fce..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardStrategy.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.broadinstitute.sting.gatk.datasources.reads; - -import java.util.Iterator; -/** - * - * User: aaron - * Date: Apr 10, 2009 - * Time: 4:55:37 PM - * - * The Broad Institute - * SOFTWARE COPYRIGHT NOTICE AGREEMENT - * This software and its documentation are copyright 2009 by the - * Broad Institute/Massachusetts Institute of Technology. All rights are reserved. - * - * This software is supplied without any warranty or guaranteed support whatsoever. Neither - * the Broad Institute nor MIT can be responsible for its use, misuse, or functionality. - * - */ - -/** - * @author aaron - * @version 1.0 - * @date Apr 10, 2009 - *

- * Interface ShardStrategy - *

- * The base interface for the sharding strategy; before we had a base abstract - * class, but not this will be an interface to accomidate read based sharding - */ -public interface ShardStrategy extends Iterator, Iterable { -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardStrategyFactory.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardStrategyFactory.java deleted file mode 100644 index 780b41ef7..000000000 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/ShardStrategyFactory.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.broadinstitute.sting.gatk.datasources.reads; - -import net.sf.picard.reference.IndexedFastaSequenceFile; -import net.sf.samtools.SAMSequenceDictionary; -import org.broadinstitute.sting.utils.GenomeLocParser; -import org.broadinstitute.sting.utils.GenomeLocSortedSet; -import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; - -/** - * - * User: aaron - * Date: Apr 6, 2009 - * Time: 7:09:22 PM - * - * The Broad Institute - * SOFTWARE COPYRIGHT NOTICE AGREEMENT - * This software and its documentation are copyright 2009 by the - * Broad Institute/Massachusetts Institute of Technology. All rights are reserved. - * - * This software is supplied without any warranty or guaranteed support whatsoever. Neither - * the Broad Institute nor MIT can be responsible for its use, misuse, or functionality. - * - */ - - -/** - * @author aaron - * @version 1.0 - * @date Apr 6, 2009 - *

- * Class ShardStrategyFactory - *

- * The Shard Strategy Factory, use this class to create and transfer shard strategies - * between different approaches. - */ -public class ShardStrategyFactory { - public enum SHATTER_STRATEGY { - MONOLITHIC, // Put all of the available data into one shard. - LOCUS_EXPERIMENTAL, - READS_EXPERIMENTAL - } - - /** - * get a new shatter strategy - * - * @param readsDataSource File pointer to BAM. - * @param referenceDataSource File pointer to reference. - * @param strat what's our strategy - SHATTER_STRATEGY type - * @param dic the seq dictionary - * @param startingSize the starting size - * @return a shard strategy capable of dividing input data into shards. - */ - static public ShardStrategy shatter(SAMDataSource readsDataSource, IndexedFastaSequenceFile referenceDataSource, SHATTER_STRATEGY strat, SAMSequenceDictionary dic, long startingSize, GenomeLocParser genomeLocParser) { - return ShardStrategyFactory.shatter(readsDataSource, referenceDataSource, strat, dic, startingSize, genomeLocParser, -1L); - } - - /** - * get a new shatter strategy - * - * @param readsDataSource File pointer to BAM. - * @param referenceDataSource File pointer to reference. - * @param strat what's our strategy - SHATTER_STRATEGY type - * @param dic the seq dictionary - * @param startingSize the starting size - * @return a shard strategy capable of dividing input data into shards. - */ - static public ShardStrategy shatter(SAMDataSource readsDataSource, IndexedFastaSequenceFile referenceDataSource, SHATTER_STRATEGY strat, SAMSequenceDictionary dic, long startingSize, GenomeLocParser genomeLocParser, long limitByCount) { - switch (strat) { - case LOCUS_EXPERIMENTAL: - return new LocusShardStrategy(readsDataSource,referenceDataSource,genomeLocParser,null); - case READS_EXPERIMENTAL: - return new ReadShardStrategy(genomeLocParser,readsDataSource,null); - default: - throw new ReviewedStingException("Strategy: " + strat + " isn't implemented for this type of shatter request"); - } - - } - - - /** - * get a new shatter strategy - * - * @param readsDataSource File pointer to BAM. - * @param referenceDataSource File pointer to reference. - * @param strat what's our strategy - SHATTER_STRATEGY type - * @param dic the seq dictionary - * @param startingSize the starting size - * @return a shard strategy capable of dividing input data into shards. - */ - static public ShardStrategy shatter(SAMDataSource readsDataSource, IndexedFastaSequenceFile referenceDataSource, SHATTER_STRATEGY strat, SAMSequenceDictionary dic, long startingSize, GenomeLocParser genomeLocParser, GenomeLocSortedSet lst) { - return ShardStrategyFactory.shatter(readsDataSource, referenceDataSource, strat, dic, startingSize, genomeLocParser, lst, -1l); - - } - - /** - * get a new shatter strategy - * - * @param readsDataSource The reads used to shatter this file. - * @param referenceDataSource The reference used to shatter this file. - * @param strat what's our strategy - SHATTER_STRATEGY type - * @param dic the seq dictionary - * @param startingSize the starting size - * @return A strategy for shattering this data. - */ - static public ShardStrategy shatter(SAMDataSource readsDataSource, IndexedFastaSequenceFile referenceDataSource, SHATTER_STRATEGY strat, SAMSequenceDictionary dic, long startingSize, GenomeLocParser genomeLocParser, GenomeLocSortedSet lst, long limitDataCount) { - switch (strat) { - case LOCUS_EXPERIMENTAL: - return new LocusShardStrategy(readsDataSource,referenceDataSource,genomeLocParser,lst); - case READS_EXPERIMENTAL: - return new ReadShardStrategy(genomeLocParser, readsDataSource,lst); - default: - throw new ReviewedStingException("Strategy: " + strat + " isn't implemented"); - } - - } - -} diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/utilities/FindLargeShards.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/utilities/FindLargeShards.java index 673df6dfa..577db0965 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/utilities/FindLargeShards.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reads/utilities/FindLargeShards.java @@ -30,10 +30,12 @@ import org.apache.log4j.Logger; import org.broadinstitute.sting.commandline.CommandLineProgram; import org.broadinstitute.sting.commandline.Input; import org.broadinstitute.sting.commandline.Output; +import org.broadinstitute.sting.gatk.datasources.reads.BAMScheduler; import org.broadinstitute.sting.gatk.datasources.reads.FilePointer; -import org.broadinstitute.sting.gatk.datasources.reads.LowMemoryIntervalSharder; +import org.broadinstitute.sting.gatk.datasources.reads.IntervalSharder; import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; import org.broadinstitute.sting.gatk.datasources.reads.SAMReaderID; +import org.broadinstitute.sting.gatk.resourcemanagement.ThreadAllocation; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.GenomeLocParser; import org.broadinstitute.sting.utils.GenomeLocSortedSet; @@ -92,7 +94,7 @@ public class FindLargeShards extends CommandLineProgram { // initialize reads List bamReaders = ListFileUtils.unpackBAMFileList(samFiles,parser); - SAMDataSource dataSource = new SAMDataSource(bamReaders,genomeLocParser); + SAMDataSource dataSource = new SAMDataSource(bamReaders,new ThreadAllocation(),null,genomeLocParser); // intervals GenomeLocSortedSet intervalSortedSet = null; @@ -106,7 +108,7 @@ public class FindLargeShards extends CommandLineProgram { logger.info(String.format("PROGRESS: Calculating mean and variance: Contig\tRegion.Start\tRegion.Stop\tSize")); - LowMemoryIntervalSharder sharder = new LowMemoryIntervalSharder(dataSource,intervalSortedSet); + IntervalSharder sharder = IntervalSharder.shardOverIntervals(dataSource,intervalSortedSet); while(sharder.hasNext()) { FilePointer filePointer = sharder.next(); @@ -135,7 +137,7 @@ public class FindLargeShards extends CommandLineProgram { logger.warn(String.format("PROGRESS: Searching for large shards: Contig\tRegion.Start\tRegion.Stop\tSize")); out.printf("Contig\tRegion.Start\tRegion.Stop\tSize%n"); - sharder = new LowMemoryIntervalSharder(dataSource,intervalSortedSet); + sharder = IntervalSharder.shardOverIntervals(dataSource,intervalSortedSet); while(sharder.hasNext()) { FilePointer filePointer = sharder.next(); diff --git a/public/java/src/org/broadinstitute/sting/gatk/datasources/reference/ReferenceDataSource.java b/public/java/src/org/broadinstitute/sting/gatk/datasources/reference/ReferenceDataSource.java index c8c79bb14..2c33a19b8 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/datasources/reference/ReferenceDataSource.java +++ b/public/java/src/org/broadinstitute/sting/gatk/datasources/reference/ReferenceDataSource.java @@ -29,6 +29,14 @@ import net.sf.picard.reference.FastaSequenceIndex; import net.sf.picard.reference.FastaSequenceIndexBuilder; import net.sf.picard.reference.IndexedFastaSequenceFile; import net.sf.picard.sam.CreateSequenceDictionary; +import net.sf.samtools.SAMSequenceRecord; +import org.broadinstitute.sting.gatk.datasources.reads.FilePointer; +import org.broadinstitute.sting.gatk.datasources.reads.LocusShard; +import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; +import org.broadinstitute.sting.gatk.datasources.reads.Shard; +import org.broadinstitute.sting.utils.GenomeLoc; +import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.GenomeLocSortedSet; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; import org.broadinstitute.sting.utils.exceptions.UserException; import org.broadinstitute.sting.utils.fasta.CachingIndexedFastaSequenceFile; @@ -36,13 +44,17 @@ import org.broadinstitute.sting.utils.file.FSLockWithShared; import org.broadinstitute.sting.utils.file.FileSystemInabilityToLockException; import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; /** * Loads reference data from fasta file * Looks for fai and dict files, and tries to create them if they don't exist */ public class ReferenceDataSource { - private IndexedFastaSequenceFile index; + private IndexedFastaSequenceFile reference; /** our log, which we want to capture anything from this class */ protected static org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(ReferenceDataSource.class); @@ -173,7 +185,7 @@ public class ReferenceDataSource { logger.info("Treating existing index file as complete."); } - index = new CachingIndexedFastaSequenceFile(fastaFile); + reference = new CachingIndexedFastaSequenceFile(fastaFile); } catch (IllegalArgumentException e) { throw new UserException.CouldNotReadInputFile(fastaFile, "Could not read reference sequence. The FASTA must have either a .fasta or .fa extension", e); @@ -192,6 +204,52 @@ public class ReferenceDataSource { * @return IndexedFastaSequenceFile that was created from file */ public IndexedFastaSequenceFile getReference() { - return this.index; + return this.reference; + } + + /** + * Creates an iterator for processing the entire reference. + * @param readsDataSource the reads datasource to embed in the locus shard. + * @param parser used to generate/regenerate intervals. TODO: decouple the creation of the shards themselves from the creation of the driving iterator so that datasources need not be passed to datasources. + * @param maxShardSize The maximum shard size which can be used to create this list. + * @return Creates a schedule for performing a traversal over the entire reference. + */ + public Iterable createShardsOverEntireReference(final SAMDataSource readsDataSource, final GenomeLocParser parser, final int maxShardSize) { + List shards = new ArrayList(); + for(SAMSequenceRecord refSequenceRecord: reference.getSequenceDictionary().getSequences()) { + for(int shardStart = 1; shardStart <= refSequenceRecord.getSequenceLength(); shardStart += maxShardSize) { + final int shardStop = Math.min(shardStart+maxShardSize-1, refSequenceRecord.getSequenceLength()); + shards.add(new LocusShard(parser, + readsDataSource, + Collections.singletonList(parser.createGenomeLoc(refSequenceRecord.getSequenceName(),shardStart,shardStop)), + null)); + } + } + return shards; + } + + /** + * Creates an iterator for processing the entire reference. + * @param readsDataSource the reads datasource to embed in the locus shard. TODO: decouple the creation of the shards themselves from the creation of the driving iterator so that datasources need not be passed to datasources. + * @param intervals the list of intervals to use when processing the reference. + * @param maxShardSize The maximum shard size which can be used to create this list. + * @return Creates a schedule for performing a traversal over the entire reference. + */ + public Iterable createShardsOverIntervals(final SAMDataSource readsDataSource, final GenomeLocSortedSet intervals, final int maxShardSize) { + List shards = new ArrayList(); + for(GenomeLoc interval: intervals) { + while(interval.size() > maxShardSize) { + shards.add(new LocusShard(intervals.getGenomeLocParser(), + readsDataSource, + Collections.singletonList(intervals.getGenomeLocParser().createGenomeLoc(interval.getContig(),interval.getStart(),interval.getStart()+maxShardSize-1)), + null)); + interval = intervals.getGenomeLocParser().createGenomeLoc(interval.getContig(),interval.getStart()+maxShardSize,interval.getStop()); + } + shards.add(new LocusShard(intervals.getGenomeLocParser(), + readsDataSource, + Collections.singletonList(interval), + null)); + } + return shards; } } diff --git a/public/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroScheduler.java b/public/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroScheduler.java index 162baed00..b0043e68c 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroScheduler.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/HierarchicalMicroScheduler.java @@ -5,7 +5,6 @@ import org.broad.tribble.TribbleException; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; import org.broadinstitute.sting.gatk.datasources.reads.Shard; -import org.broadinstitute.sting.gatk.datasources.reads.ShardStrategy; import org.broadinstitute.sting.gatk.datasources.rmd.ReferenceOrderedDataSource; import org.broadinstitute.sting.gatk.io.OutputTracker; import org.broadinstitute.sting.gatk.io.ThreadLocalOutputTracker; @@ -88,7 +87,7 @@ public class HierarchicalMicroScheduler extends MicroScheduler implements Hierar this.threadPool = Executors.newFixedThreadPool(nThreadsToUse); } - public Object execute( Walker walker, ShardStrategy shardStrategy ) { + public Object execute( Walker walker, Iterable shardStrategy ) { // Fast fail for walkers not supporting TreeReducible interface. if (!( walker instanceof TreeReducible )) throw new IllegalArgumentException("The GATK can currently run in parallel only with TreeReducible walkers"); diff --git a/public/java/src/org/broadinstitute/sting/gatk/executive/LinearMicroScheduler.java b/public/java/src/org/broadinstitute/sting/gatk/executive/LinearMicroScheduler.java index deafcd0cc..ff5e1064b 100644 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/LinearMicroScheduler.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/LinearMicroScheduler.java @@ -7,7 +7,6 @@ import org.broadinstitute.sting.gatk.datasources.providers.ReadShardDataProvider import org.broadinstitute.sting.gatk.datasources.providers.ShardDataProvider; import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; import org.broadinstitute.sting.gatk.datasources.reads.Shard; -import org.broadinstitute.sting.gatk.datasources.reads.ShardStrategy; import org.broadinstitute.sting.gatk.datasources.rmd.ReferenceOrderedDataSource; import org.broadinstitute.sting.gatk.io.DirectOutputTracker; import org.broadinstitute.sting.gatk.io.OutputTracker; @@ -44,7 +43,7 @@ public class LinearMicroScheduler extends MicroScheduler { * @param walker Computation to perform over dataset. * @param shardStrategy A strategy for sharding the data. */ - public Object execute(Walker walker, ShardStrategy shardStrategy) { + public Object execute(Walker walker, Iterable shardStrategy) { walker.initialize(); Accumulator accumulator = Accumulator.create(engine,walker); 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 e731b9864..d013db7e8 100755 --- a/public/java/src/org/broadinstitute/sting/gatk/executive/MicroScheduler.java +++ b/public/java/src/org/broadinstitute/sting/gatk/executive/MicroScheduler.java @@ -30,11 +30,11 @@ import org.apache.log4j.Logger; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; import org.broadinstitute.sting.gatk.datasources.reads.Shard; -import org.broadinstitute.sting.gatk.datasources.reads.ShardStrategy; import org.broadinstitute.sting.gatk.datasources.rmd.ReferenceOrderedDataSource; import org.broadinstitute.sting.gatk.io.OutputTracker; import org.broadinstitute.sting.gatk.iterators.NullSAMIterator; import org.broadinstitute.sting.gatk.iterators.StingSAMIterator; +import org.broadinstitute.sting.gatk.resourcemanagement.ThreadAllocation; import org.broadinstitute.sting.gatk.traversals.*; import org.broadinstitute.sting.gatk.walkers.*; import org.broadinstitute.sting.utils.exceptions.ReviewedStingException; @@ -87,20 +87,20 @@ public abstract class MicroScheduler implements MicroSchedulerMBean { * @param reads the informations associated with the reads * @param reference the reference file * @param rods the rods to include in the traversal - * @param nThreadsToUse Number of threads to utilize. + * @param threadAllocation Number of threads to utilize. * * @return The best-fit microscheduler. */ - public static MicroScheduler create(GenomeAnalysisEngine engine, Walker walker, SAMDataSource reads, IndexedFastaSequenceFile reference, Collection rods, int nThreadsToUse) { - if (walker instanceof TreeReducible && nThreadsToUse > 1) { + public static MicroScheduler create(GenomeAnalysisEngine engine, Walker walker, SAMDataSource reads, IndexedFastaSequenceFile reference, Collection rods, ThreadAllocation threadAllocation) { + if (walker instanceof TreeReducible && threadAllocation.getNumCPUThreads() > 1) { if(walker.isReduceByInterval()) throw new UserException.BadArgumentValue("nt", String.format("The analysis %s aggregates results by interval. Due to a current limitation of the GATK, analyses of this type do not currently support parallel execution. Please run your analysis without the -nt option.", engine.getWalkerName(walker.getClass()))); if(walker instanceof ReadWalker) throw new UserException.BadArgumentValue("nt", String.format("The analysis %s is a read walker. Due to a current limitation of the GATK, analyses of this type do not currently support parallel execution. Please run your analysis without the -nt option.", engine.getWalkerName(walker.getClass()))); - logger.info(String.format("Running the GATK in parallel mode with %d concurrent threads",nThreadsToUse)); - return new HierarchicalMicroScheduler(engine, walker, reads, reference, rods, nThreadsToUse); + logger.info(String.format("Running the GATK in parallel mode with %d concurrent threads",threadAllocation.getNumCPUThreads())); + return new HierarchicalMicroScheduler(engine, walker, reads, reference, rods, threadAllocation.getNumCPUThreads()); } else { - if(nThreadsToUse > 1) + if(threadAllocation.getNumCPUThreads() > 1) throw new UserException.BadArgumentValue("nt", String.format("The analysis %s currently does not support parallel execution. Please run your analysis without the -nt option.", engine.getWalkerName(walker.getClass()))); return new LinearMicroScheduler(engine, walker, reads, reference, rods); } @@ -156,7 +156,7 @@ public abstract class MicroScheduler implements MicroSchedulerMBean { * * @return the return type of the walker */ - public abstract Object execute(Walker walker, ShardStrategy shardStrategy); + public abstract Object execute(Walker walker, Iterable shardStrategy); /** * Retrieves the object responsible for tracking and managing output. diff --git a/public/java/src/org/broadinstitute/sting/gatk/resourcemanagement/ThreadAllocation.java b/public/java/src/org/broadinstitute/sting/gatk/resourcemanagement/ThreadAllocation.java new file mode 100644 index 000000000..0c81af07b --- /dev/null +++ b/public/java/src/org/broadinstitute/sting/gatk/resourcemanagement/ThreadAllocation.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2011, 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.resourcemanagement; + +import org.broadinstitute.sting.utils.exceptions.UserException; + +/** + * Models how threads are distributed between various components of the GATK. + */ +public class ThreadAllocation { + /** + * The number of CPU threads to be used by the GATK. + */ + private final int numCPUThreads; + + /** + * Number of threads to devote exclusively to IO. Default is 0. + */ + private final int numIOThreads; + + public int getNumCPUThreads() { + return numCPUThreads; + } + + public int getNumIOThreads() { + return numIOThreads; + } + + /** + * Construct the default thread allocation. + */ + public ThreadAllocation() { + this(1,null,null); + } + + /** + * Set up the thread allocation. Default allocation is 1 CPU thread, 0 IO threads. + * (0 IO threads means that no threads are devoted exclusively to IO; they're inline on the CPU thread). + * @param totalThreads Complete number of threads to allocate. + * @param numCPUThreads Total number of threads allocated to the traversal. + * @param numIOThreads Total number of threads allocated exclusively to IO. + */ + public ThreadAllocation(final int totalThreads, final Integer numCPUThreads, final Integer numIOThreads) { + // If no allocation information is present, allocate all threads to CPU + if(numCPUThreads == null && numIOThreads == null) { + this.numCPUThreads = totalThreads; + this.numIOThreads = 0; + } + // If only CPU threads are specified, allocate remainder to IO (minimum 0 dedicated IO threads). + else if(numIOThreads == null) { + if(numCPUThreads > totalThreads) + throw new UserException(String.format("Invalid thread allocation. User requested %d threads in total, but the count of cpu threads (%d) is higher than the total threads",totalThreads,numCPUThreads)); + this.numCPUThreads = numCPUThreads; + this.numIOThreads = totalThreads - numCPUThreads; + } + // If only IO threads are specified, allocate remainder to CPU (minimum 1 dedicated CPU thread). + else if(numCPUThreads == null) { + if(numIOThreads > totalThreads) + throw new UserException(String.format("Invalid thread allocation. User requested %d threads in total, but the count of io threads (%d) is higher than the total threads",totalThreads,numIOThreads)); + this.numCPUThreads = Math.max(1,totalThreads-numIOThreads); + this.numIOThreads = numIOThreads; + } + else { + if(numCPUThreads + numIOThreads != totalThreads) + throw new UserException(String.format("Invalid thread allocation. User requested %d threads in total, but the count of cpu threads (%d) + the count of io threads (%d) does not match",totalThreads,numCPUThreads,numIOThreads)); + this.numCPUThreads = numCPUThreads; + this.numIOThreads = numIOThreads; + } + } + +} diff --git a/public/java/test/org/broadinstitute/sting/gatk/datasources/providers/LocusViewTemplate.java b/public/java/test/org/broadinstitute/sting/gatk/datasources/providers/LocusViewTemplate.java index 8d7dd82ac..17a7d1974 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/datasources/providers/LocusViewTemplate.java +++ b/public/java/test/org/broadinstitute/sting/gatk/datasources/providers/LocusViewTemplate.java @@ -11,6 +11,7 @@ import org.broadinstitute.sting.gatk.executive.WindowMaker; import org.broadinstitute.sting.gatk.datasources.reads.LocusShard; import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; import org.broadinstitute.sting.gatk.iterators.StingSAMIterator; +import org.broadinstitute.sting.gatk.resourcemanagement.ThreadAllocation; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.utils.GenomeLocParser; import org.broadinstitute.sting.utils.sam.GATKSAMRecord; @@ -49,7 +50,7 @@ public abstract class LocusViewTemplate extends BaseTest { SAMRecordIterator iterator = new SAMRecordIterator(); GenomeLoc shardBounds = genomeLocParser.createGenomeLoc("chr1", 1, 5); - Shard shard = new LocusShard(genomeLocParser, new SAMDataSource(Collections.emptyList(),genomeLocParser),Collections.singletonList(shardBounds),Collections.emptyMap()); + Shard shard = new LocusShard(genomeLocParser, new SAMDataSource(Collections.emptyList(),new ThreadAllocation(),null,genomeLocParser),Collections.singletonList(shardBounds),Collections.emptyMap()); WindowMaker windowMaker = new WindowMaker(shard,genomeLocParser,iterator,shard.getGenomeLocs()); WindowMaker.WindowMakerIterator window = windowMaker.next(); LocusShardDataProvider dataProvider = new LocusShardDataProvider(shard, null, genomeLocParser, window.getLocus(), window, null, null); diff --git a/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/MockLocusShard.java b/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/MockLocusShard.java index dc3a6cafe..62c93bddd 100644 --- a/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/MockLocusShard.java +++ b/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/MockLocusShard.java @@ -26,6 +26,7 @@ package org.broadinstitute.sting.gatk.datasources.reads; import org.broadinstitute.sting.gatk.datasources.reads.LocusShard; import org.broadinstitute.sting.gatk.datasources.reads.SAMReaderID; +import org.broadinstitute.sting.gatk.resourcemanagement.ThreadAllocation; import org.broadinstitute.sting.utils.GenomeLoc; import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; import org.broadinstitute.sting.utils.GenomeLocParser; @@ -42,7 +43,7 @@ import java.util.Collections; public class MockLocusShard extends LocusShard { public MockLocusShard(final GenomeLocParser genomeLocParser,final List intervals) { super( genomeLocParser, - new SAMDataSource(Collections.emptyList(),genomeLocParser), + new SAMDataSource(Collections.emptyList(),new ThreadAllocation(),null,genomeLocParser), intervals, null); } diff --git a/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/SAMBAMDataSourceUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/SAMBAMDataSourceUnitTest.java deleted file mode 100755 index e41a6b3b7..000000000 --- a/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/SAMBAMDataSourceUnitTest.java +++ /dev/null @@ -1,223 +0,0 @@ -package org.broadinstitute.sting.gatk.datasources.reads; - -import static org.testng.Assert.fail; -import net.sf.picard.reference.IndexedFastaSequenceFile; -import net.sf.samtools.SAMRecord; -import org.broadinstitute.sting.BaseTest; -import org.broadinstitute.sting.commandline.Tags; -import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; -import org.broadinstitute.sting.gatk.datasources.reads.SAMReaderID; -import org.broadinstitute.sting.gatk.datasources.reads.Shard; -import org.broadinstitute.sting.gatk.datasources.reads.ShardStrategy; -import org.broadinstitute.sting.gatk.datasources.reads.ShardStrategyFactory; -import org.broadinstitute.sting.gatk.iterators.StingSAMIterator; -import org.broadinstitute.sting.utils.GenomeLocParser; -import org.broadinstitute.sting.utils.GenomeLoc; -import org.broadinstitute.sting.utils.fasta.CachingIndexedFastaSequenceFile; -import org.broadinstitute.sting.utils.exceptions.UserException; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; - -import org.testng.annotations.Test; - -import java.io.File; -import java.io.FileNotFoundException; -import java.util.ArrayList; -import java.util.List; - -/** - * - * User: aaron - * Date: Apr 8, 2009 - * Time: 8:14:23 PM - * - * The Broad Institute - * SOFTWARE COPYRIGHT NOTICE AGREEMENT - * This software and its documentation are copyright 2009 by the - * Broad Institute/Massachusetts Institute of Technology. All rights are reserved. - * - * This software is supplied without any warranty or guaranteed support whatsoever. Neither - * the Broad Institute nor MIT can be responsible for its use, misuse, or functionality. - * - */ - - -/** - * @author aaron - * @version 1.0 - * @date Apr 8, 2009 - *

- * Class SAMBAMDataSourceUnitTest - *

- * The test of the SAMBAM simple data source. - */ -public class SAMBAMDataSourceUnitTest extends BaseTest { - - private List readers; - private IndexedFastaSequenceFile seq; - private GenomeLocParser genomeLocParser; - - /** - * This function does the setup of our parser, before each method call. - *

- * Called before every test case method. - */ - @BeforeMethod - public void doForEachTest() throws FileNotFoundException { - readers = new ArrayList(); - - // sequence - seq = new CachingIndexedFastaSequenceFile(new File(hg18Reference)); - genomeLocParser = new GenomeLocParser(seq.getSequenceDictionary()); - } - - /** - * Tears down the test fixture after each call. - *

- * Called after every test case method. - */ - @AfterMethod - public void undoForEachTest() { - seq = null; - readers.clear(); - } - - - /** Test out that we can shard the file and iterate over every read */ - @Test - public void testLinearBreakIterateAll() { - logger.warn("Executing testLinearBreakIterateAll"); - - // setup the data - readers.add(new SAMReaderID(new File(validationDataLocation+"/NA12878.chrom6.SLX.SRP000032.2009_06.selected.bam"),new Tags())); - - // the sharding strat. - SAMDataSource data = new SAMDataSource(readers,genomeLocParser); - ShardStrategy strat = ShardStrategyFactory.shatter(data,seq,ShardStrategyFactory.SHATTER_STRATEGY.LOCUS_EXPERIMENTAL, seq.getSequenceDictionary(), 100000,genomeLocParser); - int count = 0; - - try { - for (Shard sh : strat) { - int readCount = 0; - count++; - - GenomeLoc firstLocus = sh.getGenomeLocs().get(0), lastLocus = sh.getGenomeLocs().get(sh.getGenomeLocs().size()-1); - logger.debug("Start : " + firstLocus.getStart() + " stop : " + lastLocus.getStop() + " contig " + firstLocus.getContig()); - logger.debug("count = " + count); - StingSAMIterator datum = data.seek(sh); - - // for the first couple of shards make sure we can see the reads - if (count < 5) { - for (SAMRecord r : datum) { - } - readCount++; - } - datum.close(); - - // if we're over 100 shards, break out - if (count > 100) { - break; - } - } - } - catch (UserException.CouldNotReadInputFile e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - fail("testLinearBreakIterateAll: We Should get a UserException.CouldNotReadInputFile exception"); - } - } - - - /** Test out that we can shard the file and iterate over every read */ - @Test - public void testMergingTwoBAMFiles() { - logger.warn("Executing testMergingTwoBAMFiles"); - - // setup the test files - readers.add(new SAMReaderID(new File(validationDataLocation + "/NA12878.chrom6.SLX.SRP000032.2009_06.selected.bam"),new Tags())); - - // the sharding strat. - SAMDataSource data = new SAMDataSource(readers,genomeLocParser); - ShardStrategy strat = ShardStrategyFactory.shatter(data,seq,ShardStrategyFactory.SHATTER_STRATEGY.LOCUS_EXPERIMENTAL, seq.getSequenceDictionary(), 100000,genomeLocParser); - - ArrayList readcountPerShard = new ArrayList(); - ArrayList readcountPerShard2 = new ArrayList(); - - // count up the first hundred shards - int shardsToCount = 100; - int count = 0; - - try { - for (Shard sh : strat) { - int readCount = 0; - count++; - if (count > shardsToCount) { - break; - } - - StingSAMIterator datum = data.seek(sh); - - for (SAMRecord r : datum) { - readCount++; - - } - readcountPerShard.add(readCount); - logger.debug("read count = " + readCount); - datum.close(); - } - } - catch (UserException.CouldNotReadInputFile e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - fail("testLinearBreakIterateAll: We Should get a UserException.CouldNotReadInputFile exception"); - } - - - // setup the data and the counter before our second run - readers.clear(); - readers.add(new SAMReaderID(new File(validationDataLocation + "/NA12878.chrom6.SLX.SRP000032.2009_06.selected.bam"),new Tags())); - readers.add(new SAMReaderID(new File(validationDataLocation + "/NA12878.chrom6.SLX.SRP000032.2009_06.selected.bam"),new Tags())); - - count = 0; - // the sharding strat. - data = new SAMDataSource(readers,genomeLocParser); - strat = ShardStrategyFactory.shatter(data,seq,ShardStrategyFactory.SHATTER_STRATEGY.LOCUS_EXPERIMENTAL, seq.getSequenceDictionary(), 100000, genomeLocParser); - - logger.debug("Pile two:"); - try { - for (Shard sh : strat) { - int readCount = 0; - count++; - - // can we leave? - if (count > shardsToCount) { - break; - } - - StingSAMIterator datum = data.seek(sh); - - for (SAMRecord r : datum) { - readCount++; - } - - readcountPerShard2.add(readCount); - logger.debug("read count = " + readCount); - datum.close(); - } - } - catch (UserException.CouldNotReadInputFile e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. - fail("testLinearBreakIterateAll: We Should get a UserException.CouldNotReadInputFile exception"); - } - - /*int pos = 0; - for (; pos < 100; pos++) { - if (!readcountPerShard.get(pos).equals(readcountPerShard2.get(pos))) { - fail("Shard number " + pos + " in the two approaches had different read counts, " + readcountPerShard.get(pos) + " and " + readcountPerShard2.get(pos)); - } - } */ - - } - - - - -} diff --git a/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/SAMDataSourceUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/SAMDataSourceUnitTest.java new file mode 100755 index 000000000..ba2d68ec9 --- /dev/null +++ b/public/java/test/org/broadinstitute/sting/gatk/datasources/reads/SAMDataSourceUnitTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2011, 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 static org.testng.Assert.fail; +import net.sf.picard.reference.IndexedFastaSequenceFile; +import net.sf.samtools.SAMFileReader; +import net.sf.samtools.SAMRecord; +import org.broadinstitute.sting.BaseTest; +import org.broadinstitute.sting.commandline.Tags; +import org.broadinstitute.sting.gatk.arguments.ValidationExclusion; +import org.broadinstitute.sting.gatk.filters.ReadFilter; +import org.broadinstitute.sting.gatk.iterators.StingSAMIterator; +import org.broadinstitute.sting.gatk.resourcemanagement.ThreadAllocation; +import org.broadinstitute.sting.utils.GenomeLocParser; +import org.broadinstitute.sting.utils.GenomeLoc; +import org.broadinstitute.sting.utils.fasta.CachingIndexedFastaSequenceFile; +import org.broadinstitute.sting.utils.exceptions.UserException; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; + +import org.testng.annotations.Test; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * @author aaron + * @version 1.0 + * @date Apr 8, 2009 + *

+ * Class SAMDataSourceUnitTest + *

+ * The test of the SAMBAM simple data source. + */ +public class SAMDataSourceUnitTest extends BaseTest { + + private List readers; + private IndexedFastaSequenceFile seq; + private GenomeLocParser genomeLocParser; + + /** + * This function does the setup of our parser, before each method call. + *

+ * Called before every test case method. + */ + @BeforeMethod + public void doForEachTest() throws FileNotFoundException { + readers = new ArrayList(); + + // sequence + seq = new CachingIndexedFastaSequenceFile(new File(b36KGReference)); + genomeLocParser = new GenomeLocParser(seq.getSequenceDictionary()); + } + + /** + * Tears down the test fixture after each call. + *

+ * Called after every test case method. + */ + @AfterMethod + public void undoForEachTest() { + seq = null; + readers.clear(); + } + + + /** Test out that we can shard the file and iterate over every read */ + @Test + public void testLinearBreakIterateAll() { + logger.warn("Executing testLinearBreakIterateAll"); + + // setup the data + readers.add(new SAMReaderID(new File(validationDataLocation+"/NA12878.chrom6.SLX.SRP000032.2009_06.selected.bam"),new Tags())); + + // the sharding strat. + SAMDataSource data = new SAMDataSource(readers, + new ThreadAllocation(), + null, + genomeLocParser, + false, + SAMFileReader.ValidationStringency.SILENT, + null, + null, + new ValidationExclusion(), + new ArrayList(), + false, + false); + + Iterable strat = data.createShardIteratorOverMappedReads(seq.getSequenceDictionary(),new LocusShardBalancer()); + int count = 0; + + try { + for (Shard sh : strat) { + int readCount = 0; + count++; + + GenomeLoc firstLocus = sh.getGenomeLocs().get(0), lastLocus = sh.getGenomeLocs().get(sh.getGenomeLocs().size()-1); + logger.debug("Start : " + firstLocus.getStart() + " stop : " + lastLocus.getStop() + " contig " + firstLocus.getContig()); + logger.debug("count = " + count); + StingSAMIterator datum = data.seek(sh); + + // for the first couple of shards make sure we can see the reads + if (count < 5) { + for (SAMRecord r : datum) { + } + readCount++; + } + datum.close(); + + // if we're over 100 shards, break out + if (count > 100) { + break; + } + } + } + catch (UserException.CouldNotReadInputFile e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + fail("testLinearBreakIterateAll: We Should get a UserException.CouldNotReadInputFile exception"); + } + } +} diff --git a/public/java/test/org/broadinstitute/sting/gatk/traversals/TraverseReadsUnitTest.java b/public/java/test/org/broadinstitute/sting/gatk/traversals/TraverseReadsUnitTest.java index 7f4d96add..9226f97e2 100755 --- a/public/java/test/org/broadinstitute/sting/gatk/traversals/TraverseReadsUnitTest.java +++ b/public/java/test/org/broadinstitute/sting/gatk/traversals/TraverseReadsUnitTest.java @@ -5,14 +5,13 @@ import net.sf.picard.reference.IndexedFastaSequenceFile; import org.broadinstitute.sting.BaseTest; import org.broadinstitute.sting.commandline.Tags; import org.broadinstitute.sting.gatk.GenomeAnalysisEngine; -import org.broadinstitute.sting.gatk.ReadMetrics; import org.broadinstitute.sting.gatk.datasources.providers.ShardDataProvider; import org.broadinstitute.sting.gatk.datasources.providers.ReadShardDataProvider; +import org.broadinstitute.sting.gatk.datasources.reads.ReadShardBalancer; import org.broadinstitute.sting.gatk.datasources.reads.SAMDataSource; import org.broadinstitute.sting.gatk.datasources.reads.Shard; -import org.broadinstitute.sting.gatk.datasources.reads.ShardStrategy; -import org.broadinstitute.sting.gatk.datasources.reads.ShardStrategyFactory; import org.broadinstitute.sting.gatk.datasources.reads.SAMReaderID; +import org.broadinstitute.sting.gatk.resourcemanagement.ThreadAllocation; import org.broadinstitute.sting.gatk.walkers.qc.CountReadsWalker; import org.broadinstitute.sting.gatk.walkers.Walker; import org.broadinstitute.sting.utils.GenomeLocParser; @@ -66,7 +65,6 @@ public class TraverseReadsUnitTest extends BaseTest { private List bamList; private Walker countReadWalker; private File output; - private long readSize = 100000; private TraverseReads traversalEngine = null; private IndexedFastaSequenceFile ref = null; @@ -117,18 +115,14 @@ public class TraverseReadsUnitTest extends BaseTest { /** Test out that we can shard the file and iterate over every read */ @Test public void testUnmappedReadCount() { - SAMDataSource dataSource = new SAMDataSource(bamList,genomeLocParser); - ShardStrategy shardStrategy = ShardStrategyFactory.shatter(dataSource,ref, ShardStrategyFactory.SHATTER_STRATEGY.READS_EXPERIMENTAL, - ref.getSequenceDictionary(), - readSize, - genomeLocParser); + SAMDataSource dataSource = new SAMDataSource(bamList,new ThreadAllocation(),null,genomeLocParser); + Iterable shardStrategy = dataSource.createShardIteratorOverAllReads(new ReadShardBalancer()); countReadWalker.initialize(); Object accumulator = countReadWalker.reduceInit(); - while (shardStrategy.hasNext()) { + for(Shard shard: shardStrategy) { traversalEngine.startTimersIfNecessary(); - Shard shard = shardStrategy.next(); if (shard == null) { fail("Shard == null"); From 333e5de812884e9a2109cf69e438025474f305ce Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 18 Nov 2011 16:49:59 -0500 Subject: [PATCH 43/44] returning read instead of GATKSAMRecord Do not create new GATKSAMRecord when read has been fully clipped, because it is essentially the same as returning the currently fully clipped read. --- .../org/broadinstitute/sting/utils/clipreads/ReadClipper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java index e4806838d..8c1061494 100644 --- a/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java +++ b/public/java/src/org/broadinstitute/sting/utils/clipreads/ReadClipper.java @@ -121,7 +121,7 @@ public class ReadClipper { public GATKSAMRecord hardClipSoftClippedBases () { if (read.isEmpty()) - return new GATKSAMRecord(read.getHeader()); + return read; int readIndex = 0; int cutLeft = -1; // first position to hard clip (inclusive) From b5de1820145f3b2ca4156454dfff79164e4dde72 Mon Sep 17 00:00:00 2001 From: Mauricio Carneiro Date: Fri, 18 Nov 2011 18:34:05 -0500 Subject: [PATCH 44/44] isEmpty now checks if mReadBases is null Since newly created reads have mReadBases == null. This is an effort to centralize the place to check for empty GATKSAMRecords. --- .../src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java index 6d7c8dad9..d3a52167a 100755 --- a/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java +++ b/public/java/src/org/broadinstitute/sting/utils/sam/GATKSAMRecord.java @@ -261,7 +261,7 @@ public class GATKSAMRecord extends BAMRecord { * @return true if the read has no bases */ public boolean isEmpty() { - return this.getReadLength() == 0; + return super.getReadBases() == null || super.getReadLength() == 0; } /**