From db9ab4042fbcb93ce92cd88348a73d247691b567 Mon Sep 17 00:00:00 2001 From: Egor Dolzhenko Date: Thu, 13 Dec 2018 15:32:52 -0800 Subject: [PATCH 1/2] ExpansionHunter-v3.0.0-rc1 Transer code from internal repository --- CMakeLists.txt | 37 +- COPYRIGHT.txt | 26 + LICENSE.txt | 0 README.md | 42 +- alignment/AlignmentFilters.cpp | 113 + alignment/AlignmentFilters.hh | 53 + alignment/AlignmentTweakers.cpp | 258 + alignment/AlignmentTweakers.hh | 55 + alignment/CMakeLists.txt | 5 + alignment/GraphAlignmentOperations.cpp | 174 + alignment/GraphAlignmentOperations.hh | 56 + alignment/GreedyAlignmentIntersector.cpp | 204 + alignment/GreedyAlignmentIntersector.hh | 69 + alignment/HighQualityBaseRunFinder.cpp | 99 + alignment/HighQualityBaseRunFinder.hh | 42 + alignment/SoftclippingAligner.cpp | 62 + alignment/SoftclippingAligner.hh | 45 + alignment/tests/AlignmentTweakersTest.cpp | 77 + alignment/tests/CMakeLists.txt | 19 + .../tests/GraphAlignmentOperationsTest.cpp | 128 + .../tests/GreedyAlignmentIntersectorTest.cpp | 110 + .../tests/HighQualityBaseRunFinderTest.cpp | 129 + alignment/tests/SoftclippingAlignerTest.cpp | 92 + classification/AlignmentClassifier.cpp | 145 + classification/AlignmentClassifier.hh | 65 + classification/CMakeLists.txt | 4 + .../ClassifierOfAlignmentsToVariant.cpp | 119 + .../ClassifierOfAlignmentsToVariant.hh | 59 + .../tests/AlignmentClassifierTest.cpp | 166 + classification/tests/AlignmentSummaryTest.cpp | 70 + classification/tests/CMakeLists.txt | 11 + .../ClassifierOfAlignmentsToVariantTest.cpp | 83 + cmake/google_test.cmake | 0 common/CMakeLists.txt | 7 +- common/Common.cpp | 87 + common/Common.hh | 109 + common/CountTable.cpp | 87 + common/CountTable.hh | 58 + common/GenomicRegion.cpp | 174 + common/GenomicRegion.hh | 71 + common/Parameters.cpp | 65 + common/Parameters.hh | 190 + common/Reference.cpp | 62 + common/{repeat_spec.h => Reference.hh} | 67 +- .../{ref_genome.h => SequenceOperations.cpp} | 37 +- .../SequenceOperations.hh | 15 +- common/common.h | 109 - common/genomic_region.cc | 130 - common/genomic_region.h | 67 - common/parameters.cc | 215 - common/parameters.h | 102 - common/ref_genome.cc | 58 - common/repeat_spec.cc | 188 - common/tests/CMakeLists.txt | 11 + common/tests/CountTableTest.cpp | 59 + common/tests/GenomicRegionTest.cpp | 93 + common/tests/SequenceOperationsTest.cpp | 35 + data/examples/hg19/README.txt | 16 - data/examples/hg19/bamlets/bamlet.bam | Bin 489606 -> 0 bytes data/examples/hg19/bamlets/bamlet.bam.bai | Bin 633096 -> 0 bytes data/examples/hg19/output/bamlet.json | 266 - data/examples/hg19/output/bamlet.log | 1833 -- data/examples/hg19/output/bamlet.vcf | 38 - data/repeat-specs/grch37/AR.json | 6 - data/repeat-specs/grch37/ATN1.json | 6 - data/repeat-specs/grch37/ATXN1.json | 6 - data/repeat-specs/grch37/ATXN10.json | 6 - data/repeat-specs/grch37/ATXN2.json | 6 - data/repeat-specs/grch37/ATXN3.json | 6 - data/repeat-specs/grch37/ATXN7.json | 6 - data/repeat-specs/grch37/C9ORF72.json | 36 - data/repeat-specs/grch37/CACNA1A.json | 6 - data/repeat-specs/grch37/CBL.json | 6 - data/repeat-specs/grch37/CSTB.json | 6 - data/repeat-specs/grch37/DMPK.json | 6 - data/repeat-specs/grch37/FMR1.json | 27 - data/repeat-specs/grch37/FXN.json | 6 - data/repeat-specs/grch37/HTT.json | 6 - data/repeat-specs/grch37/JPH3.json | 6 - data/repeat-specs/grch37/PPP2R2B.json | 6 - data/repeat-specs/grch38/AR.json | 6 - data/repeat-specs/grch38/ATN1.json | 6 - data/repeat-specs/grch38/ATXN1.json | 6 - data/repeat-specs/grch38/ATXN10.json | 6 - data/repeat-specs/grch38/ATXN2.json | 6 - data/repeat-specs/grch38/ATXN3.json | 6 - data/repeat-specs/grch38/ATXN7.json | 6 - data/repeat-specs/grch38/C9ORF72.json | 82 - data/repeat-specs/grch38/CACNA1A.json | 6 - data/repeat-specs/grch38/CBL.json | 6 - data/repeat-specs/grch38/CSTB.json | 6 - data/repeat-specs/grch38/DMPK.json | 6 - data/repeat-specs/grch38/FMR1.json | 221 - data/repeat-specs/grch38/FXN.json | 6 - data/repeat-specs/grch38/HTT.json | 6 - data/repeat-specs/grch38/JPH3.json | 6 - data/repeat-specs/grch38/PPP2R2B.json | 6 - data/repeat-specs/hg19/AR.json | 6 - data/repeat-specs/hg19/ATN1.json | 6 - data/repeat-specs/hg19/ATXN1.json | 6 - data/repeat-specs/hg19/ATXN10.json | 6 - data/repeat-specs/hg19/ATXN2.json | 6 - data/repeat-specs/hg19/ATXN3.json | 6 - data/repeat-specs/hg19/ATXN7.json | 6 - data/repeat-specs/hg19/C9ORF72.json | 36 - data/repeat-specs/hg19/CACNA1A.json | 6 - data/repeat-specs/hg19/CBL.json | 6 - data/repeat-specs/hg19/CSTB.json | 6 - data/repeat-specs/hg19/DMPK.json | 6 - data/repeat-specs/hg19/FMR1.json | 27 - data/repeat-specs/hg19/FXN.json | 6 - data/repeat-specs/hg19/HTT.json | 6 - data/repeat-specs/hg19/JPH3.json | 6 - data/repeat-specs/hg19/PPP2R2B.json | 6 - data/repeat-specs/hg38/AR.json | 6 - data/repeat-specs/hg38/ATN1.json | 6 - data/repeat-specs/hg38/ATXN1.json | 6 - data/repeat-specs/hg38/ATXN10.json | 6 - data/repeat-specs/hg38/ATXN2.json | 6 - data/repeat-specs/hg38/ATXN3.json | 6 - data/repeat-specs/hg38/ATXN7.json | 6 - data/repeat-specs/hg38/C9ORF72.json | 82 - data/repeat-specs/hg38/CACNA1A.json | 6 - data/repeat-specs/hg38/CBL.json | 6 - data/repeat-specs/hg38/CSTB.json | 6 - data/repeat-specs/hg38/DMPK.json | 6 - data/repeat-specs/hg38/FMR1.json | 221 - data/repeat-specs/hg38/FXN.json | 6 - data/repeat-specs/hg38/HTT.json | 6 - data/repeat-specs/hg38/JPH3.json | 6 - data/repeat-specs/hg38/PPP2R2B.json | 6 - docs/01_Introduction.md | 31 +- docs/02_Installation.md | 21 +- docs/03_Usage.md | 55 +- docs/04_Inputs.md | 52 - docs/04_VariantCatalogFiles.md | 150 + docs/05_OutputJsonFiles.md | 55 + docs/05_Outputs.md | 89 - docs/06_OutputVcfFiles.md | 31 + filtering/CMakeLists.txt | 5 + filtering/OrientationPredictor.cpp | 107 + filtering/OrientationPredictor.hh | 84 + filtering/tests/CMakeLists.txt | 3 + filtering/tests/OrientationPredictorTest.cpp | 54 + genotyping/AllelePresenceChecker.cpp | 70 + genotyping/AllelePresenceChecker.hh | 74 + genotyping/CMakeLists.txt | 5 +- genotyping/RepeatGenotype.cpp | 76 + genotyping/RepeatGenotype.hh | 108 + genotyping/RepeatGenotyper.cpp | 349 + genotyping/RepeatGenotyper.hh | 88 + genotyping/RepeatLength.cpp | 85 + .../version.h => genotyping/RepeatLength.hh | 10 +- genotyping/ShortRepeatGenotyper.cpp | 211 + genotyping/ShortRepeatGenotyper.hh | 104 + .../SmallVariantGenotype.cpp | 44 +- genotyping/SmallVariantGenotype.hh | 60 + genotyping/SmallVariantGenotyper.cpp | 110 + genotyping/SmallVariantGenotyper.hh | 76 + genotyping/repeat_genotyper.cc | 229 - genotyping/repeat_genotyper.h | 42 - genotyping/repeat_length.cc | 88 - genotyping/short_repeat_genotyper.cc | 223 - genotyping/short_repeat_genotyper.h | 97 - .../tests/AllelePresenceCheckerTest.cpp | 76 + genotyping/tests/CMakeLists.txt | 19 + genotyping/tests/GenotypeTest.cpp | 62 + genotyping/tests/RepeatGenotypeTest.cpp | 94 + genotyping/tests/RepeatGenotyperTest.cpp | 51 + genotyping/tests/ShortRepeatGenotyperTest.cpp | 217 + .../tests/SmallVariantGenotyperTest.cpp | 55 + genotyping/unit_tests/CMakeLists.txt | 3 - .../unit_tests/repeat_genotyper_test.cc | 0 .../unit_tests/short_repeat_genotyper_test.cc | 200 - include/bam_file.h | 104 - include/irr_counting.h | 80 - include/json_output.h | 40 - include/read_alignment.h | 105 - include/read_group.h | 69 - include/region_findings.h | 48 - input/CMakeLists.txt | 4 + input/CatalogLoading.cpp | 422 + input/CatalogLoading.hh | 39 + input/GraphBlueprint.cpp | 256 + input/GraphBlueprint.hh | 87 + input/ParameterLoading.cpp | 272 + input/ParameterLoading.hh | 32 + input/RegionGraph.cpp | 118 + input/RegionGraph.hh | 34 + input/SampleStats.cpp | 87 + common/timestamp.cc => input/SampleStats.hh | 26 +- input/tests/CMakeLists.txt | 12 + input/tests/GraphBlueprintTest.cpp | 62 + input/tests/LocusSpecificationTest.cpp | 86 + input/tests/RegionGraphTest.cpp | 73 + output/CMakeLists.txt | 3 + output/JsonWriter.cpp | 169 + output/JsonWriter.hh | 71 + output/VcfHeader.cpp | 199 + output/VcfHeader.hh | 87 + output/VcfWriter.cpp | 330 + output/VcfWriter.hh | 84 + output/VcfWriterHelpers.cpp | 123 + output/VcfWriterHelpers.hh | 68 + purity/CMakeLists.txt | 3 - purity/purity.cc | 177 - purity/purity.h | 49 - purity/unit_tests/CMakeLists.txt | 3 - purity/unit_tests/purity_test.cc | 147 - reads/CMakeLists.txt | 4 + reads/Read.cpp | 67 + reads/Read.hh | 111 + reads/ReadPairs.cpp | 136 + reads/ReadPairs.hh | 85 + reads/tests/CMakeLists.txt | 3 + reads/tests/ReadTest.cpp | 32 + region_analysis/CMakeLists.txt | 4 + region_analysis/Definitions.hh | 33 + region_analysis/RegionAnalyzer.cpp | 288 + region_analysis/RegionAnalyzer.hh | 96 + region_analysis/RepeatAnalyzer.cpp | 189 + region_analysis/RepeatAnalyzer.hh | 86 + region_analysis/SmallVariantAnalyzer.cpp | 100 + region_analysis/SmallVariantAnalyzer.hh | 80 + region_analysis/VariantAnalyzer.cpp | 21 + region_analysis/VariantAnalyzer.hh | 70 + region_analysis/VariantFindings.cpp | 45 + region_analysis/VariantFindings.hh | 126 + region_analysis/tests/CMakeLists.txt | 7 + region_analysis/tests/RegionAnalyzerTest.cpp | 76 + region_analysis/tests/RepeatAnalyzerTest.cpp | 63 + region_spec/CMakeLists.txt | 3 + region_spec/LocusSpecification.cpp | 95 + region_spec/LocusSpecification.hh | 86 + region_spec/VariantSpecification.cpp | 123 + region_spec/VariantSpecification.hh | 115 + rep_align/CMakeLists.txt | 4 - rep_align/rep_align.cc | 404 - rep_align/rep_align.h | 91 - rep_align/unit_tests/CMakeLists.txt | 3 - rep_align/unit_tests/rep_align_test.cc | 318 - sample_analysis/CMakeLists.txt | 3 + sample_analysis/HtsFileSeeker.cpp | 167 + sample_analysis/HtsFileSeeker.hh | 83 + sample_analysis/HtsFileStreamer.cpp | 128 + sample_analysis/HtsFileStreamer.hh | 86 + sample_analysis/HtsHelpers.cpp | 113 + .../HtsHelpers.hh | 47 +- sample_analysis/HtsSeekingSampleAnalyzer.cpp | 203 + sample_analysis/HtsSeekingSampleAnalyzer.hh | 37 + .../HtsStreamingSampleAnalyzer.cpp | 66 + sample_analysis/HtsStreamingSampleAnalyzer.hh | 38 + sample_analysis/IndexBasedDepthEstimate.cpp | 101 + sample_analysis/IndexBasedDepthEstimate.hh | 30 + .../LocationBasedAnalyzerFinder.cpp | 121 + .../LocationBasedAnalyzerFinder.hh | 72 + sample_analysis/LocationBasedDispatcher.cpp | 71 + sample_analysis/LocationBasedDispatcher.hh | 48 + sample_analysis/MateExtractor.cpp | 139 + sample_analysis/MateExtractor.hh | 63 + src/ExpansionHunter.cpp | 117 + common/timestamp.h => src/Version.hh | 8 +- src/bam_file.cc | 532 - src/bam_index.cc | 108 - src/expansion_hunter.cc | 522 - src/irr_counting.cc | 406 - src/json_output.cc | 125 - src/read_alignment.cc | 86 - src/read_group.cc | 441 - src/vcf_output.cc | 167 - stats/CMakeLists.txt | 4 + stats/ReadSupportCalculator.cpp | 57 + stats/ReadSupportCalculator.hh | 58 + stats/WeightedPurityCalculator.cpp | 171 + stats/WeightedPurityCalculator.hh | 40 + stats/tests/CMakeLists.txt | 7 + stats/tests/ReadSupportCalculatorTest.cpp | 53 + stats/tests/WeightedPurityCalculatorTest.cpp | 45 + thirdparty/graph-tools-master/.clang-format | 6 + thirdparty/graph-tools-master/CMakeLists.txt | 66 + thirdparty/graph-tools-master/Dockerfile | 33 + thirdparty/graph-tools-master/README.md | 29 + thirdparty/graph-tools-master/RELEASES.md | 42 + .../cmake/GetGoogleTest.cmake | 44 + .../graph-tools-master/cmake/GetHtslib.cmake | 58 + .../graph-tools-master/docs/alignment.md | 37 + .../graph-tools-master/docs/development.md | 31 + thirdparty/graph-tools-master/docs/graph.md | 53 + .../graph-tools-master/docs/path_families.md | 102 + thirdparty/graph-tools-master/docs/paths.md | 110 + .../docs/query_and_reference_sequences.md | 53 + .../external/googletest-release-1.8.0.tar.gz | Bin 0 -> 1281617 bytes .../external/include/nlohmann/json.hpp | 17300 ++++++++++++++++ .../include/graphIO/BamWriter.hh | 126 + .../include/graphIO/GraphJson.hh | 68 + .../include/graphIO/ReferenceGenome.hh | 60 + .../include/graphalign/DagAlignerAffine.hh | 393 + .../include/graphalign/GaplessAligner.hh | 120 + .../include/graphalign/GappedAligner.hh | 170 + .../include/graphalign/GraphAligner.hh | 47 + .../include/graphalign/GraphAlignment.hh | 103 + .../graphalign/GraphAlignmentOperations.hh | 75 + .../include/graphalign/KmerIndex.hh | 74 + .../include/graphalign/KmerIndexOperations.hh | 50 + .../include/graphalign/LinearAlignment.hh | 108 + .../graphalign/LinearAlignmentOperations.hh | 93 + .../graphalign/LinearAlignmentParameters.hh | 57 + .../include/graphalign/Operation.hh | 76 + .../include/graphalign/OperationOperations.hh | 50 + .../include/graphalign/PinnedAligner.hh | 71 + .../include/graphalign/PinnedDagAligner.hh | 627 + .../include/graphalign/PinnedPathAligner.hh | 125 + .../include/graphalign/TracebackMatrix.hh | 112 + .../include/graphalign/TracebackRunner.hh | 68 + .../dagAligner/AffineAlignMatrix.hh | 278 + .../dagAligner/AffineAlignMatrixVectorized.hh | 333 + .../dagAligner/BaseMatchingPenaltyMatrix.hh | 127 + .../include/graphalign/dagAligner/Details.hh | 506 + .../graphalign/dagAligner/PenaltyMatrix.hh | 236 + .../include/graphcore/Graph.hh | 149 + .../include/graphcore/GraphBuilders.hh | 112 + .../include/graphcore/GraphCoordinates.hh | 100 + .../include/graphcore/GraphOperations.hh | 42 + .../graphcore/GraphReferenceMapping.hh | 136 + .../include/graphcore/Path.hh | 196 + .../include/graphcore/PathFamily.hh | 85 + .../include/graphcore/PathFamilyOperations.hh | 100 + .../include/graphcore/PathOperations.hh | 186 + .../include/graphutils/BaseMatching.hh | 172 + .../include/graphutils/DepthTest.hh | 62 + .../include/graphutils/IntervalBuffer.hh | 85 + .../include/graphutils/IntervalList.hh | 286 + .../include/graphutils/PairHashing.hh | 57 + .../include/graphutils/SequenceOperations.hh | 90 + .../src/graphIO/BamWriter.cpp | 181 + .../src/graphIO/CMakeLists.txt | 10 + .../src/graphIO/GraphJson.cpp | 160 + .../src/graphIO/ReferenceGenome.cpp | 64 + .../src/graphalign/GaplessAligner.cpp | 155 + .../src/graphalign/GappedAligner.cpp | 256 + .../src/graphalign/GraphAlignment.cpp | 229 + .../graphalign/GraphAlignmentOperations.cpp | 236 + .../src/graphalign/KmerIndex.cpp | 263 + .../src/graphalign/KmerIndexOperations.cpp | 121 + .../src/graphalign/LinearAlignment.cpp | 230 + .../graphalign/LinearAlignmentOperations.cpp | 234 + .../src/graphalign/Operation.cpp | 155 + .../src/graphalign/OperationOperations.cpp | 123 + .../src/graphalign/PinnedAligner.cpp | 139 + .../src/graphalign/TracebackMatrix.cpp | 186 + .../src/graphalign/TracebackRunner.cpp | 144 + .../graphalign/dagAligner/PenaltyMatrix.cpp | 68 + .../src/graphcore/Graph.cpp | 201 + .../src/graphcore/GraphBuilders.cpp | 134 + .../src/graphcore/GraphCoordinates.cpp | 237 + .../src/graphcore/GraphOperations.cpp | 64 + .../src/graphcore/GraphReferenceMapping.cpp | 151 + .../graph-tools-master/src/graphcore/Path.cpp | 524 + .../src/graphcore/PathFamily.cpp | 149 + .../src/graphcore/PathFamilyOperations.cpp | 213 + .../src/graphcore/PathOperations.cpp | 797 + .../src/graphutils/DepthTest.cpp | 55 + .../src/graphutils/IntervalBuffer.cpp | 184 + .../src/graphutils/SequenceOperations.cpp | 221 + .../graph-tools-master/src/sh/check-format.sh | 23 + .../graph-tools-master/src/sh/docker-build.sh | 62 + .../src/sh/docker-check-format.sh | 25 + .../src/sh/docker-cppcheck.sh | 35 + .../src/sh/docker-format-everything.sh | 25 + .../src/sh/format-everything.sh | 27 + .../src/sh/valgrind-check.py | 18 + .../tests/BaseMatchingTest.cpp | 60 + .../graph-tools-master/tests/CMakeLists.txt | 122 + .../tests/DagAlignerTest.cpp | 783 + .../tests/DepthTestTest.cpp | 46 + .../tests/GaplessAlignerTest.cpp | 190 + .../tests/GappedAlignerTest.cpp | 259 + .../tests/GraphAlignmentOperationsTest.cpp | 158 + .../tests/GraphAlignmentTest.cpp | 259 + .../tests/GraphBuildersTest.cpp | 148 + .../tests/GraphCoordinatesTest.cpp | 135 + .../graph-tools-master/tests/GraphIOTest.cpp | 327 + .../tests/GraphOperationsTest.cpp | 87 + .../tests/GraphReferenceMappingTest.cpp | 82 + .../graph-tools-master/tests/GraphTest.cpp | 184 + .../tests/IntervalBufferTest.cpp | 257 + .../tests/IntervalListTest.cpp | 258 + .../tests/KmerIndexOperationsTest.cpp | 47 + .../tests/KmerIndexTest.cpp | 149 + .../tests/LinearAlignmentOperationsTest.cpp | 240 + .../tests/LinearAlignmentTest.cpp | 131 + .../tests/OperationOperationsTest.cpp | 219 + .../tests/OperationTest.cpp | 101 + .../tests/PathFamilyOperationsTest.cpp | 259 + .../tests/PathFamilyTest.cpp | 142 + .../tests/PathOperationsTest.cpp | 684 + .../graph-tools-master/tests/PathTest.cpp | 592 + .../tests/PinnedAlignerTest.cpp | 110 + .../tests/PinnedDagAlignerTest.cpp | 83 + .../tests/SequenceOperationsTest.cpp | 100 + .../tests/TracebackMatrixTest.cpp | 92 + .../tests/TracebackRunnerTest.cpp | 78 + thirdparty/intervaltree/IntervalTree.h | 337 + {third_party => thirdparty}/json/json.hpp | 0 thirdparty/spdlog/async_logger.h | 82 + thirdparty/spdlog/common.h | 161 + thirdparty/spdlog/details/async_log_helper.h | 399 + thirdparty/spdlog/details/async_logger_impl.h | 107 + thirdparty/spdlog/details/file_helper.h | 145 + thirdparty/spdlog/details/log_msg.h | 50 + thirdparty/spdlog/details/logger_impl.h | 373 + thirdparty/spdlog/details/mpmc_bounded_q.h | 176 + thirdparty/spdlog/details/null_mutex.h | 45 + thirdparty/spdlog/details/os.h | 479 + .../spdlog/details/pattern_formatter_impl.h | 686 + thirdparty/spdlog/details/registry.h | 225 + thirdparty/spdlog/details/spdlog_impl.h | 268 + thirdparty/spdlog/fmt/bundled/LICENSE.rst | 23 + thirdparty/spdlog/fmt/bundled/format.cc | 535 + thirdparty/spdlog/fmt/bundled/format.h | 4659 +++++ thirdparty/spdlog/fmt/bundled/ostream.cc | 35 + thirdparty/spdlog/fmt/bundled/ostream.h | 114 + thirdparty/spdlog/fmt/bundled/posix.cc | 241 + thirdparty/spdlog/fmt/bundled/posix.h | 424 + thirdparty/spdlog/fmt/bundled/printf.cc | 32 + thirdparty/spdlog/fmt/bundled/printf.h | 712 + thirdparty/spdlog/fmt/bundled/time.h | 183 + thirdparty/spdlog/fmt/fmt.h | 34 + thirdparty/spdlog/fmt/ostr.h | 17 + thirdparty/spdlog/formatter.h | 47 + thirdparty/spdlog/logger.h | 110 + thirdparty/spdlog/sinks/android_sink.h | 90 + thirdparty/spdlog/sinks/ansicolor_sink.h | 133 + thirdparty/spdlog/sinks/base_sink.h | 51 + thirdparty/spdlog/sinks/dist_sink.h | 72 + thirdparty/spdlog/sinks/file_sinks.h | 253 + thirdparty/spdlog/sinks/msvc_sink.h | 51 + thirdparty/spdlog/sinks/null_sink.h | 34 + thirdparty/spdlog/sinks/ostream_sink.h | 47 + thirdparty/spdlog/sinks/sink.h | 53 + thirdparty/spdlog/sinks/stdout_sinks.h | 77 + thirdparty/spdlog/sinks/syslog_sink.h | 81 + thirdparty/spdlog/sinks/wincolor_sink.h | 121 + thirdparty/spdlog/sinks/windebug_sink.h | 29 + thirdparty/spdlog/spdlog.h | 192 + thirdparty/spdlog/tweakme.h | 160 + variant_catalog/variant_catalog_grch37.json | 231 + variant_catalog/variant_catalog_grch38.json | 471 + variant_catalog/variant_catalog_hg19.json | 231 + variant_catalog/variant_catalog_hg38.json | 471 + 450 files changed, 63355 insertions(+), 9365 deletions(-) mode change 100644 => 100755 CMakeLists.txt mode change 100644 => 100755 COPYRIGHT.txt mode change 100644 => 100755 LICENSE.txt mode change 100644 => 100755 README.md create mode 100755 alignment/AlignmentFilters.cpp create mode 100755 alignment/AlignmentFilters.hh create mode 100755 alignment/AlignmentTweakers.cpp create mode 100755 alignment/AlignmentTweakers.hh create mode 100755 alignment/CMakeLists.txt create mode 100755 alignment/GraphAlignmentOperations.cpp create mode 100755 alignment/GraphAlignmentOperations.hh create mode 100755 alignment/GreedyAlignmentIntersector.cpp create mode 100755 alignment/GreedyAlignmentIntersector.hh create mode 100755 alignment/HighQualityBaseRunFinder.cpp create mode 100755 alignment/HighQualityBaseRunFinder.hh create mode 100755 alignment/SoftclippingAligner.cpp create mode 100755 alignment/SoftclippingAligner.hh create mode 100755 alignment/tests/AlignmentTweakersTest.cpp create mode 100755 alignment/tests/CMakeLists.txt create mode 100755 alignment/tests/GraphAlignmentOperationsTest.cpp create mode 100755 alignment/tests/GreedyAlignmentIntersectorTest.cpp create mode 100755 alignment/tests/HighQualityBaseRunFinderTest.cpp create mode 100755 alignment/tests/SoftclippingAlignerTest.cpp create mode 100755 classification/AlignmentClassifier.cpp create mode 100755 classification/AlignmentClassifier.hh create mode 100755 classification/CMakeLists.txt create mode 100755 classification/ClassifierOfAlignmentsToVariant.cpp create mode 100755 classification/ClassifierOfAlignmentsToVariant.hh create mode 100755 classification/tests/AlignmentClassifierTest.cpp create mode 100755 classification/tests/AlignmentSummaryTest.cpp create mode 100755 classification/tests/CMakeLists.txt create mode 100755 classification/tests/ClassifierOfAlignmentsToVariantTest.cpp mode change 100644 => 100755 cmake/google_test.cmake mode change 100644 => 100755 common/CMakeLists.txt create mode 100755 common/Common.cpp create mode 100755 common/Common.hh create mode 100755 common/CountTable.cpp create mode 100755 common/CountTable.hh create mode 100755 common/GenomicRegion.cpp create mode 100755 common/GenomicRegion.hh create mode 100755 common/Parameters.cpp create mode 100755 common/Parameters.hh create mode 100755 common/Reference.cpp rename common/{repeat_spec.h => Reference.hh} (52%) mode change 100644 => 100755 rename common/{ref_genome.h => SequenceOperations.cpp} (61%) mode change 100644 => 100755 rename genotyping/repeat_length.h => common/SequenceOperations.hh (70%) mode change 100644 => 100755 delete mode 100644 common/common.h delete mode 100644 common/genomic_region.cc delete mode 100644 common/genomic_region.h delete mode 100644 common/parameters.cc delete mode 100644 common/parameters.h delete mode 100644 common/ref_genome.cc delete mode 100644 common/repeat_spec.cc create mode 100755 common/tests/CMakeLists.txt create mode 100755 common/tests/CountTableTest.cpp create mode 100755 common/tests/GenomicRegionTest.cpp create mode 100755 common/tests/SequenceOperationsTest.cpp delete mode 100644 data/examples/hg19/README.txt delete mode 100644 data/examples/hg19/bamlets/bamlet.bam delete mode 100644 data/examples/hg19/bamlets/bamlet.bam.bai delete mode 100644 data/examples/hg19/output/bamlet.json delete mode 100644 data/examples/hg19/output/bamlet.log delete mode 100644 data/examples/hg19/output/bamlet.vcf delete mode 100644 data/repeat-specs/grch37/AR.json delete mode 100644 data/repeat-specs/grch37/ATN1.json delete mode 100644 data/repeat-specs/grch37/ATXN1.json delete mode 100755 data/repeat-specs/grch37/ATXN10.json delete mode 100755 data/repeat-specs/grch37/ATXN2.json delete mode 100644 data/repeat-specs/grch37/ATXN3.json delete mode 100755 data/repeat-specs/grch37/ATXN7.json delete mode 100644 data/repeat-specs/grch37/C9ORF72.json delete mode 100755 data/repeat-specs/grch37/CACNA1A.json delete mode 100755 data/repeat-specs/grch37/CBL.json delete mode 100755 data/repeat-specs/grch37/CSTB.json delete mode 100644 data/repeat-specs/grch37/DMPK.json delete mode 100644 data/repeat-specs/grch37/FMR1.json delete mode 100644 data/repeat-specs/grch37/FXN.json delete mode 100644 data/repeat-specs/grch37/HTT.json delete mode 100755 data/repeat-specs/grch37/JPH3.json delete mode 100755 data/repeat-specs/grch37/PPP2R2B.json delete mode 100644 data/repeat-specs/grch38/AR.json delete mode 100644 data/repeat-specs/grch38/ATN1.json delete mode 100644 data/repeat-specs/grch38/ATXN1.json delete mode 100755 data/repeat-specs/grch38/ATXN10.json delete mode 100755 data/repeat-specs/grch38/ATXN2.json delete mode 100644 data/repeat-specs/grch38/ATXN3.json delete mode 100755 data/repeat-specs/grch38/ATXN7.json delete mode 100644 data/repeat-specs/grch38/C9ORF72.json delete mode 100755 data/repeat-specs/grch38/CACNA1A.json delete mode 100755 data/repeat-specs/grch38/CBL.json delete mode 100755 data/repeat-specs/grch38/CSTB.json delete mode 100644 data/repeat-specs/grch38/DMPK.json delete mode 100644 data/repeat-specs/grch38/FMR1.json delete mode 100644 data/repeat-specs/grch38/FXN.json delete mode 100644 data/repeat-specs/grch38/HTT.json delete mode 100755 data/repeat-specs/grch38/JPH3.json delete mode 100755 data/repeat-specs/grch38/PPP2R2B.json delete mode 100644 data/repeat-specs/hg19/AR.json delete mode 100644 data/repeat-specs/hg19/ATN1.json delete mode 100644 data/repeat-specs/hg19/ATXN1.json delete mode 100755 data/repeat-specs/hg19/ATXN10.json delete mode 100755 data/repeat-specs/hg19/ATXN2.json delete mode 100644 data/repeat-specs/hg19/ATXN3.json delete mode 100755 data/repeat-specs/hg19/ATXN7.json delete mode 100644 data/repeat-specs/hg19/C9ORF72.json delete mode 100755 data/repeat-specs/hg19/CACNA1A.json delete mode 100755 data/repeat-specs/hg19/CBL.json delete mode 100755 data/repeat-specs/hg19/CSTB.json delete mode 100644 data/repeat-specs/hg19/DMPK.json delete mode 100644 data/repeat-specs/hg19/FMR1.json delete mode 100644 data/repeat-specs/hg19/FXN.json delete mode 100644 data/repeat-specs/hg19/HTT.json delete mode 100755 data/repeat-specs/hg19/JPH3.json delete mode 100755 data/repeat-specs/hg19/PPP2R2B.json delete mode 100644 data/repeat-specs/hg38/AR.json delete mode 100644 data/repeat-specs/hg38/ATN1.json delete mode 100644 data/repeat-specs/hg38/ATXN1.json delete mode 100755 data/repeat-specs/hg38/ATXN10.json delete mode 100755 data/repeat-specs/hg38/ATXN2.json delete mode 100644 data/repeat-specs/hg38/ATXN3.json delete mode 100755 data/repeat-specs/hg38/ATXN7.json delete mode 100644 data/repeat-specs/hg38/C9ORF72.json delete mode 100755 data/repeat-specs/hg38/CACNA1A.json delete mode 100755 data/repeat-specs/hg38/CBL.json delete mode 100755 data/repeat-specs/hg38/CSTB.json delete mode 100644 data/repeat-specs/hg38/DMPK.json delete mode 100644 data/repeat-specs/hg38/FMR1.json delete mode 100644 data/repeat-specs/hg38/FXN.json delete mode 100644 data/repeat-specs/hg38/HTT.json delete mode 100755 data/repeat-specs/hg38/JPH3.json delete mode 100755 data/repeat-specs/hg38/PPP2R2B.json mode change 100644 => 100755 docs/01_Introduction.md mode change 100644 => 100755 docs/02_Installation.md mode change 100644 => 100755 docs/03_Usage.md delete mode 100644 docs/04_Inputs.md create mode 100755 docs/04_VariantCatalogFiles.md create mode 100755 docs/05_OutputJsonFiles.md delete mode 100644 docs/05_Outputs.md create mode 100755 docs/06_OutputVcfFiles.md create mode 100755 filtering/CMakeLists.txt create mode 100755 filtering/OrientationPredictor.cpp create mode 100755 filtering/OrientationPredictor.hh create mode 100755 filtering/tests/CMakeLists.txt create mode 100755 filtering/tests/OrientationPredictorTest.cpp create mode 100755 genotyping/AllelePresenceChecker.cpp create mode 100755 genotyping/AllelePresenceChecker.hh mode change 100644 => 100755 genotyping/CMakeLists.txt create mode 100755 genotyping/RepeatGenotype.cpp create mode 100755 genotyping/RepeatGenotype.hh create mode 100755 genotyping/RepeatGenotyper.cpp create mode 100755 genotyping/RepeatGenotyper.hh create mode 100755 genotyping/RepeatLength.cpp rename include/version.h => genotyping/RepeatLength.hh (82%) mode change 100644 => 100755 create mode 100755 genotyping/ShortRepeatGenotyper.cpp create mode 100755 genotyping/ShortRepeatGenotyper.hh rename include/vcf_output.h => genotyping/SmallVariantGenotype.cpp (58%) mode change 100644 => 100755 create mode 100755 genotyping/SmallVariantGenotype.hh create mode 100755 genotyping/SmallVariantGenotyper.cpp create mode 100755 genotyping/SmallVariantGenotyper.hh delete mode 100644 genotyping/repeat_genotyper.cc delete mode 100644 genotyping/repeat_genotyper.h delete mode 100644 genotyping/repeat_length.cc delete mode 100644 genotyping/short_repeat_genotyper.cc delete mode 100644 genotyping/short_repeat_genotyper.h create mode 100755 genotyping/tests/AllelePresenceCheckerTest.cpp create mode 100755 genotyping/tests/CMakeLists.txt create mode 100755 genotyping/tests/GenotypeTest.cpp create mode 100755 genotyping/tests/RepeatGenotypeTest.cpp create mode 100755 genotyping/tests/RepeatGenotyperTest.cpp create mode 100755 genotyping/tests/ShortRepeatGenotyperTest.cpp create mode 100755 genotyping/tests/SmallVariantGenotyperTest.cpp delete mode 100644 genotyping/unit_tests/CMakeLists.txt delete mode 100644 genotyping/unit_tests/repeat_genotyper_test.cc delete mode 100644 genotyping/unit_tests/short_repeat_genotyper_test.cc delete mode 100644 include/bam_file.h delete mode 100644 include/irr_counting.h delete mode 100644 include/json_output.h delete mode 100644 include/read_alignment.h delete mode 100644 include/read_group.h delete mode 100644 include/region_findings.h create mode 100755 input/CMakeLists.txt create mode 100755 input/CatalogLoading.cpp create mode 100755 input/CatalogLoading.hh create mode 100755 input/GraphBlueprint.cpp create mode 100755 input/GraphBlueprint.hh create mode 100755 input/ParameterLoading.cpp create mode 100755 input/ParameterLoading.hh create mode 100755 input/RegionGraph.cpp create mode 100755 input/RegionGraph.hh create mode 100755 input/SampleStats.cpp rename common/timestamp.cc => input/SampleStats.hh (64%) mode change 100644 => 100755 create mode 100755 input/tests/CMakeLists.txt create mode 100755 input/tests/GraphBlueprintTest.cpp create mode 100755 input/tests/LocusSpecificationTest.cpp create mode 100755 input/tests/RegionGraphTest.cpp create mode 100755 output/CMakeLists.txt create mode 100755 output/JsonWriter.cpp create mode 100755 output/JsonWriter.hh create mode 100755 output/VcfHeader.cpp create mode 100755 output/VcfHeader.hh create mode 100755 output/VcfWriter.cpp create mode 100755 output/VcfWriter.hh create mode 100755 output/VcfWriterHelpers.cpp create mode 100755 output/VcfWriterHelpers.hh delete mode 100644 purity/CMakeLists.txt delete mode 100644 purity/purity.cc delete mode 100644 purity/purity.h delete mode 100644 purity/unit_tests/CMakeLists.txt delete mode 100644 purity/unit_tests/purity_test.cc create mode 100755 reads/CMakeLists.txt create mode 100755 reads/Read.cpp create mode 100755 reads/Read.hh create mode 100755 reads/ReadPairs.cpp create mode 100755 reads/ReadPairs.hh create mode 100755 reads/tests/CMakeLists.txt create mode 100755 reads/tests/ReadTest.cpp create mode 100755 region_analysis/CMakeLists.txt create mode 100755 region_analysis/Definitions.hh create mode 100755 region_analysis/RegionAnalyzer.cpp create mode 100755 region_analysis/RegionAnalyzer.hh create mode 100755 region_analysis/RepeatAnalyzer.cpp create mode 100755 region_analysis/RepeatAnalyzer.hh create mode 100755 region_analysis/SmallVariantAnalyzer.cpp create mode 100755 region_analysis/SmallVariantAnalyzer.hh create mode 100755 region_analysis/VariantAnalyzer.cpp create mode 100755 region_analysis/VariantAnalyzer.hh create mode 100755 region_analysis/VariantFindings.cpp create mode 100755 region_analysis/VariantFindings.hh create mode 100755 region_analysis/tests/CMakeLists.txt create mode 100755 region_analysis/tests/RegionAnalyzerTest.cpp create mode 100755 region_analysis/tests/RepeatAnalyzerTest.cpp create mode 100755 region_spec/CMakeLists.txt create mode 100755 region_spec/LocusSpecification.cpp create mode 100755 region_spec/LocusSpecification.hh create mode 100755 region_spec/VariantSpecification.cpp create mode 100755 region_spec/VariantSpecification.hh delete mode 100644 rep_align/CMakeLists.txt delete mode 100644 rep_align/rep_align.cc delete mode 100644 rep_align/rep_align.h delete mode 100644 rep_align/unit_tests/CMakeLists.txt delete mode 100644 rep_align/unit_tests/rep_align_test.cc create mode 100755 sample_analysis/CMakeLists.txt create mode 100755 sample_analysis/HtsFileSeeker.cpp create mode 100755 sample_analysis/HtsFileSeeker.hh create mode 100755 sample_analysis/HtsFileStreamer.cpp create mode 100755 sample_analysis/HtsFileStreamer.hh create mode 100755 sample_analysis/HtsHelpers.cpp rename include/bam_index.h => sample_analysis/HtsHelpers.hh (53%) mode change 100644 => 100755 create mode 100755 sample_analysis/HtsSeekingSampleAnalyzer.cpp create mode 100755 sample_analysis/HtsSeekingSampleAnalyzer.hh create mode 100755 sample_analysis/HtsStreamingSampleAnalyzer.cpp create mode 100755 sample_analysis/HtsStreamingSampleAnalyzer.hh create mode 100755 sample_analysis/IndexBasedDepthEstimate.cpp create mode 100755 sample_analysis/IndexBasedDepthEstimate.hh create mode 100755 sample_analysis/LocationBasedAnalyzerFinder.cpp create mode 100755 sample_analysis/LocationBasedAnalyzerFinder.hh create mode 100755 sample_analysis/LocationBasedDispatcher.cpp create mode 100755 sample_analysis/LocationBasedDispatcher.hh create mode 100755 sample_analysis/MateExtractor.cpp create mode 100755 sample_analysis/MateExtractor.hh create mode 100755 src/ExpansionHunter.cpp rename common/timestamp.h => src/Version.hh (91%) mode change 100644 => 100755 delete mode 100644 src/bam_file.cc delete mode 100644 src/bam_index.cc delete mode 100644 src/expansion_hunter.cc delete mode 100644 src/irr_counting.cc delete mode 100644 src/json_output.cc delete mode 100644 src/read_alignment.cc delete mode 100644 src/read_group.cc delete mode 100644 src/vcf_output.cc create mode 100755 stats/CMakeLists.txt create mode 100755 stats/ReadSupportCalculator.cpp create mode 100755 stats/ReadSupportCalculator.hh create mode 100755 stats/WeightedPurityCalculator.cpp create mode 100755 stats/WeightedPurityCalculator.hh create mode 100755 stats/tests/CMakeLists.txt create mode 100755 stats/tests/ReadSupportCalculatorTest.cpp create mode 100755 stats/tests/WeightedPurityCalculatorTest.cpp create mode 100755 thirdparty/graph-tools-master/.clang-format create mode 100755 thirdparty/graph-tools-master/CMakeLists.txt create mode 100755 thirdparty/graph-tools-master/Dockerfile create mode 100755 thirdparty/graph-tools-master/README.md create mode 100755 thirdparty/graph-tools-master/RELEASES.md create mode 100755 thirdparty/graph-tools-master/cmake/GetGoogleTest.cmake create mode 100755 thirdparty/graph-tools-master/cmake/GetHtslib.cmake create mode 100755 thirdparty/graph-tools-master/docs/alignment.md create mode 100755 thirdparty/graph-tools-master/docs/development.md create mode 100755 thirdparty/graph-tools-master/docs/graph.md create mode 100755 thirdparty/graph-tools-master/docs/path_families.md create mode 100755 thirdparty/graph-tools-master/docs/paths.md create mode 100755 thirdparty/graph-tools-master/docs/query_and_reference_sequences.md create mode 100755 thirdparty/graph-tools-master/external/googletest-release-1.8.0.tar.gz create mode 100755 thirdparty/graph-tools-master/external/include/nlohmann/json.hpp create mode 100755 thirdparty/graph-tools-master/include/graphIO/BamWriter.hh create mode 100755 thirdparty/graph-tools-master/include/graphIO/GraphJson.hh create mode 100755 thirdparty/graph-tools-master/include/graphIO/ReferenceGenome.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/DagAlignerAffine.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/GaplessAligner.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/GappedAligner.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/GraphAligner.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/GraphAlignment.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/GraphAlignmentOperations.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/KmerIndex.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/KmerIndexOperations.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/LinearAlignment.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/LinearAlignmentOperations.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/LinearAlignmentParameters.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/Operation.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/OperationOperations.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/PinnedAligner.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/PinnedDagAligner.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/PinnedPathAligner.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/TracebackMatrix.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/TracebackRunner.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/dagAligner/AffineAlignMatrix.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/dagAligner/AffineAlignMatrixVectorized.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/dagAligner/BaseMatchingPenaltyMatrix.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/dagAligner/Details.hh create mode 100755 thirdparty/graph-tools-master/include/graphalign/dagAligner/PenaltyMatrix.hh create mode 100755 thirdparty/graph-tools-master/include/graphcore/Graph.hh create mode 100755 thirdparty/graph-tools-master/include/graphcore/GraphBuilders.hh create mode 100755 thirdparty/graph-tools-master/include/graphcore/GraphCoordinates.hh create mode 100755 thirdparty/graph-tools-master/include/graphcore/GraphOperations.hh create mode 100755 thirdparty/graph-tools-master/include/graphcore/GraphReferenceMapping.hh create mode 100755 thirdparty/graph-tools-master/include/graphcore/Path.hh create mode 100755 thirdparty/graph-tools-master/include/graphcore/PathFamily.hh create mode 100755 thirdparty/graph-tools-master/include/graphcore/PathFamilyOperations.hh create mode 100755 thirdparty/graph-tools-master/include/graphcore/PathOperations.hh create mode 100755 thirdparty/graph-tools-master/include/graphutils/BaseMatching.hh create mode 100755 thirdparty/graph-tools-master/include/graphutils/DepthTest.hh create mode 100755 thirdparty/graph-tools-master/include/graphutils/IntervalBuffer.hh create mode 100755 thirdparty/graph-tools-master/include/graphutils/IntervalList.hh create mode 100755 thirdparty/graph-tools-master/include/graphutils/PairHashing.hh create mode 100755 thirdparty/graph-tools-master/include/graphutils/SequenceOperations.hh create mode 100755 thirdparty/graph-tools-master/src/graphIO/BamWriter.cpp create mode 100755 thirdparty/graph-tools-master/src/graphIO/CMakeLists.txt create mode 100755 thirdparty/graph-tools-master/src/graphIO/GraphJson.cpp create mode 100755 thirdparty/graph-tools-master/src/graphIO/ReferenceGenome.cpp create mode 100755 thirdparty/graph-tools-master/src/graphalign/GaplessAligner.cpp create mode 100755 thirdparty/graph-tools-master/src/graphalign/GappedAligner.cpp create mode 100755 thirdparty/graph-tools-master/src/graphalign/GraphAlignment.cpp create mode 100755 thirdparty/graph-tools-master/src/graphalign/GraphAlignmentOperations.cpp create mode 100755 thirdparty/graph-tools-master/src/graphalign/KmerIndex.cpp create mode 100755 thirdparty/graph-tools-master/src/graphalign/KmerIndexOperations.cpp create mode 100755 thirdparty/graph-tools-master/src/graphalign/LinearAlignment.cpp create mode 100755 thirdparty/graph-tools-master/src/graphalign/LinearAlignmentOperations.cpp create mode 100755 thirdparty/graph-tools-master/src/graphalign/Operation.cpp create mode 100755 thirdparty/graph-tools-master/src/graphalign/OperationOperations.cpp create mode 100755 thirdparty/graph-tools-master/src/graphalign/PinnedAligner.cpp create mode 100755 thirdparty/graph-tools-master/src/graphalign/TracebackMatrix.cpp create mode 100755 thirdparty/graph-tools-master/src/graphalign/TracebackRunner.cpp create mode 100755 thirdparty/graph-tools-master/src/graphalign/dagAligner/PenaltyMatrix.cpp create mode 100755 thirdparty/graph-tools-master/src/graphcore/Graph.cpp create mode 100755 thirdparty/graph-tools-master/src/graphcore/GraphBuilders.cpp create mode 100755 thirdparty/graph-tools-master/src/graphcore/GraphCoordinates.cpp create mode 100755 thirdparty/graph-tools-master/src/graphcore/GraphOperations.cpp create mode 100755 thirdparty/graph-tools-master/src/graphcore/GraphReferenceMapping.cpp create mode 100755 thirdparty/graph-tools-master/src/graphcore/Path.cpp create mode 100755 thirdparty/graph-tools-master/src/graphcore/PathFamily.cpp create mode 100755 thirdparty/graph-tools-master/src/graphcore/PathFamilyOperations.cpp create mode 100755 thirdparty/graph-tools-master/src/graphcore/PathOperations.cpp create mode 100755 thirdparty/graph-tools-master/src/graphutils/DepthTest.cpp create mode 100755 thirdparty/graph-tools-master/src/graphutils/IntervalBuffer.cpp create mode 100755 thirdparty/graph-tools-master/src/graphutils/SequenceOperations.cpp create mode 100755 thirdparty/graph-tools-master/src/sh/check-format.sh create mode 100755 thirdparty/graph-tools-master/src/sh/docker-build.sh create mode 100755 thirdparty/graph-tools-master/src/sh/docker-check-format.sh create mode 100755 thirdparty/graph-tools-master/src/sh/docker-cppcheck.sh create mode 100755 thirdparty/graph-tools-master/src/sh/docker-format-everything.sh create mode 100755 thirdparty/graph-tools-master/src/sh/format-everything.sh create mode 100755 thirdparty/graph-tools-master/src/sh/valgrind-check.py create mode 100755 thirdparty/graph-tools-master/tests/BaseMatchingTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/CMakeLists.txt create mode 100755 thirdparty/graph-tools-master/tests/DagAlignerTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/DepthTestTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/GaplessAlignerTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/GappedAlignerTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/GraphAlignmentOperationsTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/GraphAlignmentTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/GraphBuildersTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/GraphCoordinatesTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/GraphIOTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/GraphOperationsTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/GraphReferenceMappingTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/GraphTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/IntervalBufferTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/IntervalListTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/KmerIndexOperationsTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/KmerIndexTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/LinearAlignmentOperationsTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/LinearAlignmentTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/OperationOperationsTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/OperationTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/PathFamilyOperationsTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/PathFamilyTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/PathOperationsTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/PathTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/PinnedAlignerTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/PinnedDagAlignerTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/SequenceOperationsTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/TracebackMatrixTest.cpp create mode 100755 thirdparty/graph-tools-master/tests/TracebackRunnerTest.cpp create mode 100755 thirdparty/intervaltree/IntervalTree.h rename {third_party => thirdparty}/json/json.hpp (100%) mode change 100644 => 100755 create mode 100755 thirdparty/spdlog/async_logger.h create mode 100755 thirdparty/spdlog/common.h create mode 100755 thirdparty/spdlog/details/async_log_helper.h create mode 100755 thirdparty/spdlog/details/async_logger_impl.h create mode 100755 thirdparty/spdlog/details/file_helper.h create mode 100755 thirdparty/spdlog/details/log_msg.h create mode 100755 thirdparty/spdlog/details/logger_impl.h create mode 100755 thirdparty/spdlog/details/mpmc_bounded_q.h create mode 100755 thirdparty/spdlog/details/null_mutex.h create mode 100755 thirdparty/spdlog/details/os.h create mode 100755 thirdparty/spdlog/details/pattern_formatter_impl.h create mode 100755 thirdparty/spdlog/details/registry.h create mode 100755 thirdparty/spdlog/details/spdlog_impl.h create mode 100755 thirdparty/spdlog/fmt/bundled/LICENSE.rst create mode 100755 thirdparty/spdlog/fmt/bundled/format.cc create mode 100755 thirdparty/spdlog/fmt/bundled/format.h create mode 100755 thirdparty/spdlog/fmt/bundled/ostream.cc create mode 100755 thirdparty/spdlog/fmt/bundled/ostream.h create mode 100755 thirdparty/spdlog/fmt/bundled/posix.cc create mode 100755 thirdparty/spdlog/fmt/bundled/posix.h create mode 100755 thirdparty/spdlog/fmt/bundled/printf.cc create mode 100755 thirdparty/spdlog/fmt/bundled/printf.h create mode 100755 thirdparty/spdlog/fmt/bundled/time.h create mode 100755 thirdparty/spdlog/fmt/fmt.h create mode 100755 thirdparty/spdlog/fmt/ostr.h create mode 100755 thirdparty/spdlog/formatter.h create mode 100755 thirdparty/spdlog/logger.h create mode 100755 thirdparty/spdlog/sinks/android_sink.h create mode 100755 thirdparty/spdlog/sinks/ansicolor_sink.h create mode 100755 thirdparty/spdlog/sinks/base_sink.h create mode 100755 thirdparty/spdlog/sinks/dist_sink.h create mode 100755 thirdparty/spdlog/sinks/file_sinks.h create mode 100755 thirdparty/spdlog/sinks/msvc_sink.h create mode 100755 thirdparty/spdlog/sinks/null_sink.h create mode 100755 thirdparty/spdlog/sinks/ostream_sink.h create mode 100755 thirdparty/spdlog/sinks/sink.h create mode 100755 thirdparty/spdlog/sinks/stdout_sinks.h create mode 100755 thirdparty/spdlog/sinks/syslog_sink.h create mode 100755 thirdparty/spdlog/sinks/wincolor_sink.h create mode 100755 thirdparty/spdlog/sinks/windebug_sink.h create mode 100755 thirdparty/spdlog/spdlog.h create mode 100755 thirdparty/spdlog/tweakme.h create mode 100755 variant_catalog/variant_catalog_grch37.json create mode 100755 variant_catalog/variant_catalog_grch38.json create mode 100755 variant_catalog/variant_catalog_hg19.json create mode 100755 variant_catalog/variant_catalog_hg38.json diff --git a/CMakeLists.txt b/CMakeLists.txt old mode 100644 new mode 100755 index dec2917..28c414d --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,7 @@ project(ExpansionHunter CXX) list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) enable_testing() +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) include(ExternalProject) @@ -26,9 +27,10 @@ endif() # Add googletest directly to our build. This defines # the gtest and gtest_main targets. add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src - ${CMAKE_BINARY_DIR}/googletest-build) + ${CMAKE_BINARY_DIR}/googletest-build) ################################################################## + ExternalProject_Add(zlib PREFIX ${CMAKE_BINARY_DIR}/thirdparty/zlib GIT_REPOSITORY "https://github.com/madler/zlib.git" @@ -53,8 +55,6 @@ ExternalProject_Add(htslib LOG_DOWNLOAD 1 ) -#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wall -Wextra -pedantic-errors -std=c++11") - include_directories(${CMAKE_BINARY_DIR}/thirdparty/zlib/include) set(zlib_static ${CMAKE_BINARY_DIR}/thirdparty/zlib/lib/libz.a) set(htslib_static ${CMAKE_BINARY_DIR}/thirdparty/htslib/lib/libhts.a) @@ -62,23 +62,36 @@ set(htslib_static ${CMAKE_BINARY_DIR}/thirdparty/htslib/lib/libhts.a) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(Boost 1.4 COMPONENTS program_options filesystem regex date_time system REQUIRED) +set(Boost_USE_STATIC_LIBS ON) +find_package(Boost 1.4 REQUIRED COMPONENTS program_options filesystem regex date_time system) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${Boost_INCLUDE_DIR}) +include_directories(SYSTEM ${Boost_INCLUDE_DIR}) include_directories(${CMAKE_BINARY_DIR}/thirdparty/htslib/include) +include_directories(thirdparty/graph-tools-GT-506/include) -add_subdirectory(genotyping) -add_subdirectory(purity) -add_subdirectory(rep_align) -add_subdirectory(common) +add_subdirectory(thirdparty/graph-tools-master) -file(GLOB SOURCES "src/*.cc") +add_compile_options(-Werror -pedantic -Wall -Wextra) +add_subdirectory(common) +add_subdirectory(genotyping) +add_subdirectory(reads) +add_subdirectory(classification) +add_subdirectory(region_spec) +add_subdirectory(region_analysis) +add_subdirectory(sample_analysis) +add_subdirectory(input) +add_subdirectory(output) +add_subdirectory(alignment) +add_subdirectory(stats) +add_subdirectory(filtering) + +file(GLOB SOURCES "src/*.cpp") add_executable(ExpansionHunter ${SOURCES}) target_compile_features(ExpansionHunter PRIVATE cxx_range_for) +target_link_libraries(ExpansionHunter graphtools common genotyping region_analysis region_spec sample_analysis input output alignment filtering stats pthread ${Boost_LIBRARIES}) +install (TARGETS ExpansionHunter DESTINATION bin) add_dependencies(htslib zlib) add_dependencies(common htslib) - -target_link_libraries(ExpansionHunter common genotyping purity rep_align pthread ${htslib_static} ${zlib_static} ${Boost_LIBRARIES}) \ No newline at end of file diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt old mode 100644 new mode 100755 index 2a46adb..f3ce4a1 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -233,3 +233,29 @@ 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. + +****************************************************************** + +spdlog: Super fast C++ logging library https://github.com/gabime/spdlog + +The MIT License (MIT) + +Copyright (c) 2016 Gabi Melman. + +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. \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 index b4910ad..f7e219d --- a/README.md +++ b/README.md @@ -1,34 +1,38 @@ -Expansion Hunter: a tool for estimating repeat sizes ----------------------------------------------------- +# Expansion Hunter: a tool for estimating repeat sizes -There are a number of regions in the human genome consisting of repetitions of short unit sequence (commonly a trimer). -Such repeat regions can expand to a size much larger than the read length and thereby cause a disease. +There are a number of regions in the human genome consisting of repetitions of +short unit sequence (commonly a trimer). Such repeat regions can expand to a +size much larger than the read length and thereby cause a disease. [Fragile X Syndrome](https://en.wikipedia.org/wiki/Fragile_X_syndrome), [ALS](https://en.wikipedia.org/wiki/Amyotrophic_lateral_sclerosis), and -[Huntington's Disease](https://en.wikipedia.org/wiki/Huntington%27s_disease) are well known examples. +[Huntington's Disease](https://en.wikipedia.org/wiki/Huntington%27s_disease) +are well known examples. -Expansion Hunter aims to estimate sizes of such repeats by performing a targeted search through a BAM/CRAM file for -reads that span, flank, and are fully contained in each repeat. +Expansion Hunter aims to estimate sizes of such repeats by performing a targeted +search through a BAM/CRAM file for reads that span, flank, and are fully +contained in each repeat. Linux and macOS operating systems are currently supported. -License -------- -Expansion Hunter is provided under the terms and conditions of the [GPLv3 license](LICENSE.txt). It relies on several -third party packages provided under other open source licenses, please see [COPYRIGHT.txt](COPYRIGHT.txt) for additional -details. +## License -Documentation -------------- +Expansion Hunter is provided under the terms and conditions of the +[GPLv3 license](LICENSE.txt). It relies on several third party packages provided +under other open source licenses, please see [COPYRIGHT.txt](COPYRIGHT.txt) for +additional details. -Installation instructions, usage guide, and description of file formats are contained in the [docs folder](docs/01_Introduction.md). +## Documentation -Method ------- +Installation instructions, usage guide, and description of file formats are +contained in the [docs folder](docs/01_Introduction.md). + + +## Method The detailed description of the method can be found here: -Dolzhenko et al., [Detection of long repeat expansions from PCR-free whole-genome sequence -data](http://genome.cshlp.org/content/27/11/1895), Genome Research 2017 +Dolzhenko and others, [Detection of long repeat expansions from PCR-free +whole-genome sequence data](http://genome.cshlp.org/content/27/11/1895), Genome +Research 2017 diff --git a/alignment/AlignmentFilters.cpp b/alignment/AlignmentFilters.cpp new file mode 100755 index 0000000..20f8f46 --- /dev/null +++ b/alignment/AlignmentFilters.cpp @@ -0,0 +1,113 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "alignment/AlignmentFilters.hh" + +#include +#include + +#include "graphalign/GaplessAligner.hh" +#include "graphalign/GraphAlignmentOperations.hh" +#include "graphalign/LinearAlignmentOperations.hh" +#include "graphcore/PathOperations.hh" + +#include "alignment/GraphAlignmentOperations.hh" + +using graphtools::GraphAlignment; +using graphtools::NodeId; +using graphtools::Path; +using std::list; +using std::string; +using std::vector; + +namespace ehunter +{ + +bool checkIfLocallyPlacedReadPair( + boost::optional readAlignment, boost::optional mateAlignment, + int kMinNonRepeatAlignmentScore) +{ + int nonRepeatAlignmentScore = 0; + + if (readAlignment) + { + nonRepeatAlignmentScore += scoreAlignmentToNonloopNodes(*readAlignment); + } + + if (mateAlignment) + { + nonRepeatAlignmentScore += scoreAlignmentToNonloopNodes(*mateAlignment); + } + + if (nonRepeatAlignmentScore < kMinNonRepeatAlignmentScore) + { + return false; + } + + return true; +} + +bool checkIfUpstreamAlignmentIsGood(NodeId nodeId, GraphAlignment alignment) +{ + const list repeatNodeIndexes = alignment.getIndexesOfNode(nodeId); + + if (repeatNodeIndexes.empty()) + { + return false; + } + + const int firstRepeatNodeIndex = repeatNodeIndexes.front(); + int score = 0; + LinearAlignmentParameters parameters; + for (int nodeIndex = 0; nodeIndex != firstRepeatNodeIndex; ++nodeIndex) + { + score += scoreAlignment( + alignment[nodeIndex], parameters.matchScore, parameters.mismatchScore, parameters.gapOpenScore); + } + + const int kScoreCutoff = parameters.matchScore * 8; + + return score >= kScoreCutoff; +} + +bool checkIfDownstreamAlignmentIsGood(NodeId nodeId, GraphAlignment alignment) +{ + const list repeatNodeIndexes = alignment.getIndexesOfNode(nodeId); + + if (repeatNodeIndexes.empty()) + { + return false; + } + + const int lastRepeatNodeIndex = repeatNodeIndexes.back(); + int score = 0; + LinearAlignmentParameters parameters; + for (int nodeIndex = lastRepeatNodeIndex + 1; nodeIndex != static_cast(alignment.size()); ++nodeIndex) + { + score += scoreAlignment( + alignment[nodeIndex], parameters.matchScore, parameters.mismatchScore, parameters.gapOpenScore); + } + + const int kScoreCutoff = parameters.matchScore * 8; + + return score >= kScoreCutoff; +} + +} diff --git a/alignment/AlignmentFilters.hh b/alignment/AlignmentFilters.hh new file mode 100755 index 0000000..83a4712 --- /dev/null +++ b/alignment/AlignmentFilters.hh @@ -0,0 +1,53 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include + +#include + +#include "graphalign/GraphAlignment.hh" + +namespace ehunter +{ + +/** + * Checks if a read pair is likely to have originated in the alignment region + * + * The check is performed by verifying that the alignment score to non-repeat nodes (combined for both mates) is + * sufficiently high. + * + * @param readAlignment: Alignment of a read + * @param mateAlignment: Alignment of read's mate + * @param kMinNonRepeatAlignmentScore: Score threshold + * @return true if the alignment score to non-repeat nodes exceeds the threshold + */ +bool checkIfLocallyPlacedReadPair( + boost::optional readAlignment, + boost::optional mateAlignment, int kMinNonRepeatAlignmentScore); + +// Checks if alignment upstream of a given node is high quality +bool checkIfUpstreamAlignmentIsGood(graphtools::NodeId nodeId, graphtools::GraphAlignment alignment); + +// Checks if alignment downstream of a given node is high quality +bool checkIfDownstreamAlignmentIsGood(graphtools::NodeId nodeId, graphtools::GraphAlignment alignment); + +} diff --git a/alignment/AlignmentTweakers.cpp b/alignment/AlignmentTweakers.cpp new file mode 100755 index 0000000..136318d --- /dev/null +++ b/alignment/AlignmentTweakers.cpp @@ -0,0 +1,258 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "alignment/AlignmentTweakers.hh" + +#include + +#include "graphalign/GaplessAligner.hh" +#include "graphalign/LinearAlignmentOperations.hh" +#include "graphalign/LinearAlignmentParameters.hh" +#include "graphcore/PathOperations.hh" + +using graphtools::GraphAlignment; +using graphtools::NodeId; +using graphtools::Path; +using std::list; +using std::string; +using std::vector; + +namespace ehunter +{ + +static void shrinkPrefixUntilNodeBoundary(Path& path, int maxShrinkLength) +{ + int accumulatedLength = 0; + + while (path.numNodes() > 1 && accumulatedLength + (int)path.getNodeOverlapLengthByIndex(0) <= maxShrinkLength) + { + accumulatedLength += path.getNodeOverlapLengthByIndex(0); + path.removeStartNode(); + } +} + +static void shrinkSuffixUntilNodeBoundary(Path& path, int maxShrinkLength) +{ + int accumulatedLength = 0; + int nodeIndex = path.numNodes() - 1; + + while (path.numNodes() > 1 + && accumulatedLength + (int)path.getNodeOverlapLengthByIndex(nodeIndex) <= maxShrinkLength) + { + accumulatedLength += path.getNodeOverlapLengthByIndex(nodeIndex); + path.removeEndNode(); + --nodeIndex; + } +} + +static list computeAlternatePrefixes(Path path, int endLength) +{ + path.shrinkEndBy(path.length()); + return extendPathStart(path, endLength); +} + +static list computeAlternateSuffixes(Path path, int endLength) +{ + path.shrinkStartBy(path.length()); + return extendPathEnd(path, endLength); +} + +static list getHighScoringPaths(const list& paths, const string& query, int lowScoreCutoff) +{ + LinearAlignmentParameters parameters; + list highScoringPaths; + + for (const auto& path : paths) + { + const auto alignment = graphtools::alignWithoutGaps(0, path.seq(), query); + const int score + = scoreAlignment(alignment, parameters.matchScore, parameters.mismatchScore, parameters.gapOpenScore); + + if (score >= lowScoreCutoff) + { + highScoringPaths.push_back(path); + } + } + + return highScoringPaths; +} + +static std::size_t getSmallestNumberOfNodes(const list& paths) +{ + assert(!paths.empty()); + + std::size_t minNodes = paths.front().numNodes(); + for (const auto& path : paths) + { + minNodes = std::min(minNodes, path.numNodes()); + } + return minNodes; +} + +static int computeCommonPrefixLength(const list& paths) +{ + const int numNodes = getSmallestNumberOfNodes(paths); + const Path& firstPath = paths.front(); + + int prefixLength = 0; + + for (int nodeIndex = 0; nodeIndex != numNodes; ++nodeIndex) + { + const int firstPathNodeId = firstPath.getNodeIdByIndex(nodeIndex); + + for (const Path& path : paths) + { + int pathNodeId = path.getNodeIdByIndex(nodeIndex); + if (firstPathNodeId != pathNodeId) + { + return prefixLength; + } + } + + prefixLength += firstPath.getNodeOverlapLengthByIndex(nodeIndex); + } + + return prefixLength; +} + +static int computeCommonSuffixLength(const list& paths) +{ + const int numNodes = getSmallestNumberOfNodes(paths); + const Path& firstPath = paths.front(); + + int suffixLength = 0; + + for (int nodeIndex = 0; nodeIndex != numNodes; ++nodeIndex) + { + const int firstPathReverseNodeIndex = firstPath.numNodes() - nodeIndex - 1; + const int firstPathNodeId = firstPath.getNodeIdByIndex(firstPathReverseNodeIndex); + + for (const Path& path : paths) + { + const int reverseNodeIndex = path.numNodes() - nodeIndex - 1; + int pathNodeId = path.getNodeIdByIndex(reverseNodeIndex); + if (firstPathNodeId != pathNodeId) + { + return suffixLength; + } + } + + suffixLength += firstPath.getNodeOverlapLengthByIndex(firstPathReverseNodeIndex); + } + + return suffixLength; +} + +static int computeQueryLengthUpToNode(const GraphAlignment& alignment, int terminalNodeIndex) +{ + assert(terminalNodeIndex <= (int)alignment.size()); + + int queryLength = 0; + for (int nodeIndex = 0; nodeIndex != terminalNodeIndex; ++nodeIndex) + { + queryLength += alignment[nodeIndex].queryLength(); + } + + return queryLength; +} + +void shrinkUncertainPrefix(int referenceLength, const string& query, GraphAlignment& alignment) +{ + Path shrunkPath = alignment.path(); + shrinkPrefixUntilNodeBoundary(shrunkPath, referenceLength); + const int prefixReferenceLength = alignment.referenceLength() - shrunkPath.length(); + + if (prefixReferenceLength == 0) + { + return; + } + + const int numPrefixNodesRemoved = alignment.path().numNodes() - shrunkPath.numNodes(); + const int prefixQueryLength = computeQueryLengthUpToNode(alignment, numPrefixNodesRemoved); + + if (prefixQueryLength != prefixReferenceLength) + { + alignment.shrinkStart(prefixReferenceLength); + return; + } + + string trimmedQueryPrefix = query.substr(0, prefixQueryLength); + trimmedQueryPrefix + = trimmedQueryPrefix.substr(trimmedQueryPrefix.length() - prefixReferenceLength, prefixReferenceLength); + + const list alternatePrefixes = computeAlternatePrefixes(shrunkPath, prefixReferenceLength); + assert(!alternatePrefixes.empty()); + + LinearAlignmentParameters parameters; + const int lowScoreCutoff_ = (prefixReferenceLength / 2) * parameters.matchScore; + const list highScoringPrefixes = getHighScoringPaths(alternatePrefixes, trimmedQueryPrefix, lowScoreCutoff_); + + if (highScoringPrefixes.empty()) + { + alignment.shrinkStart(prefixReferenceLength); + return; + } + + const int lengthSharedByPrefixes = computeCommonSuffixLength(highScoringPrefixes); + + alignment.shrinkStart(prefixReferenceLength - lengthSharedByPrefixes); +} + +void shrinkUncertainSuffix(int referenceLength, const string& query, GraphAlignment& alignment) +{ + Path shrunkPath = alignment.path(); + shrinkSuffixUntilNodeBoundary(shrunkPath, referenceLength); + const int suffixReferenceLength = alignment.referenceLength() - shrunkPath.length(); + + if (suffixReferenceLength == 0) + { + return; + } + + const int prefixQueryLength = computeQueryLengthUpToNode(alignment, shrunkPath.numNodes()); + const int suffixQueryLength = alignment.queryLength() - prefixQueryLength; + + if (suffixQueryLength != suffixReferenceLength) + { + alignment.shrinkEnd(suffixReferenceLength); + return; + } + + const string trimmedQuerySuffix = query.substr(prefixQueryLength, suffixReferenceLength); + + const list alternateSuffixes = computeAlternateSuffixes(shrunkPath, suffixReferenceLength); + assert(!alternateSuffixes.empty()); + + LinearAlignmentParameters parameters; + const int lowScoreCutoff_ = (suffixReferenceLength / 2) * parameters.matchScore; + const list highScoringSuffixes = getHighScoringPaths(alternateSuffixes, trimmedQuerySuffix, lowScoreCutoff_); + + if (highScoringSuffixes.empty()) + { + alignment.shrinkEnd(suffixReferenceLength); + return; + } + + const int lengthSharedBySuffixes = computeCommonPrefixLength(highScoringSuffixes); + + alignment.shrinkEnd(suffixReferenceLength - lengthSharedBySuffixes); +} + +} diff --git a/alignment/AlignmentTweakers.hh b/alignment/AlignmentTweakers.hh new file mode 100755 index 0000000..d703daf --- /dev/null +++ b/alignment/AlignmentTweakers.hh @@ -0,0 +1,55 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include + +#include "graphalign/GraphAlignment.hh" + +namespace ehunter +{ + +/** + * Softclips unreliable prefix of an alignment + * + * To determine if a prefix of the alignment is unreliable, the prefix is realigned along all valid alternate paths. The + * alignment is then shrank to the point where high-scoring prefix alignments diverge. + * + * @param referenceLength: Reference length of the prefix to evaluate + * @param query: Query sequence corresponding to the alignment + * @param alignment: Any graph alignment + */ +void shrinkUncertainPrefix(int referenceLength, const std::string& query, graphtools::GraphAlignment& alignment); + +/** + * Softclips unreliable suffix of an alignment + * + * Works identically to shrinkUncertainPrefix but for suffixes + * + * @param referenceLength: Reference length of the suffix to evaluate + * @param query: Query sequence corresponding to the alignment + * @param alignment: Any graph alignment + */ +void shrinkUncertainSuffix(int referenceLength, const std::string& query, graphtools::GraphAlignment& alignment); + +} diff --git a/alignment/CMakeLists.txt b/alignment/CMakeLists.txt new file mode 100755 index 0000000..02ef287 --- /dev/null +++ b/alignment/CMakeLists.txt @@ -0,0 +1,5 @@ +file(GLOB SOURCES "*.cpp") +add_library(alignment ${SOURCES}) +target_link_libraries(alignment common graphtools input region_spec) + +add_subdirectory(tests) diff --git a/alignment/GraphAlignmentOperations.cpp b/alignment/GraphAlignmentOperations.cpp new file mode 100755 index 0000000..26f06d1 --- /dev/null +++ b/alignment/GraphAlignmentOperations.cpp @@ -0,0 +1,174 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "alignment/GraphAlignmentOperations.hh" + +#include +#include + +#include + +#include "graphalign/GraphAlignmentOperations.hh" +#include "graphalign/LinearAlignmentOperations.hh" +#include "graphcore/Graph.hh" +#include "graphcore/GraphBuilders.hh" + +#include "alignment/GreedyAlignmentIntersector.hh" + +using graphtools::Alignment; +using graphtools::decodeGraphAlignment; +using graphtools::Graph; +using graphtools::GraphAlignment; +using graphtools::makeStrGraph; +using graphtools::mergeAlignments; +using graphtools::NodeId; +using graphtools::Path; +using std::list; +using std::string; +using std::to_string; + +namespace ehunter +{ + +GraphAlignment extendWithSoftclip(const GraphAlignment& graphAlignment, int leftSoftclipLen, int rightSoftclipLen) +{ + auto sequenceAlignments = graphAlignment.alignments(); + + if (leftSoftclipLen) + { + auto& firstAlignment = sequenceAlignments.front(); + int leftSoftclipReferenceStart = firstAlignment.referenceStart(); + Alignment leftSoftclip(leftSoftclipReferenceStart, to_string(leftSoftclipLen) + "S"); + firstAlignment = mergeAlignments(leftSoftclip, firstAlignment); + } + + if (rightSoftclipLen) + { + auto& lastAlignment = sequenceAlignments.back(); + int rightSoftclipReferenceStart = lastAlignment.referenceStart() + lastAlignment.referenceLength(); + Alignment rightSoftclip(rightSoftclipReferenceStart, to_string(rightSoftclipLen) + "S"); + lastAlignment = mergeAlignments(lastAlignment, rightSoftclip); + } + + return GraphAlignment(graphAlignment.path(), sequenceAlignments); +} + +int getNumNonrepeatMatchesUpstream(NodeId nodeId, GraphAlignment alignment) +{ + const list repeatNodeIndexes = alignment.getIndexesOfNode(nodeId); + + if (repeatNodeIndexes.empty()) + { + return 0; + } + + const int firstRepeatNodeIndex = repeatNodeIndexes.front(); + int numMatches = 0; + + for (int nodeIndex = 0; nodeIndex != firstRepeatNodeIndex; ++nodeIndex) + { + numMatches += alignment[nodeIndex].numMatched(); + } + + return numMatches; +} + +int getNumNonrepeatMatchesDownstream(NodeId nodeId, GraphAlignment alignment) +{ + const list repeatNodeIndexes = alignment.getIndexesOfNode(nodeId); + + if (repeatNodeIndexes.empty()) + { + return 0; + } + + const int lastRepeatNodeIndex = repeatNodeIndexes.back(); + int numMatches = 0; + + for (int nodeIndex = lastRepeatNodeIndex + 1; nodeIndex != static_cast(alignment.size()); ++nodeIndex) + { + numMatches += alignment[nodeIndex].numMatched(); + } + + return numMatches; +} + +int scoreAlignmentToNonloopNodes(graphtools::GraphAlignment alignment, LinearAlignmentParameters parameters) +{ + int score = 0; + const Graph& graph = *alignment.path().graphRawPtr(); + for (int nodeIndex = 0; nodeIndex != (int)alignment.size(); ++nodeIndex) + { + NodeId nodeId = alignment.path().getNodeIdByIndex(nodeIndex); + if (graph.successors(nodeId).find(nodeId) == graph.successors(nodeId).end()) + { + score += scoreAlignment( + alignment[nodeIndex], parameters.matchScore, parameters.mismatchScore, parameters.gapOpenScore); + } + } + + return score; +} + +int countFullOverlaps(NodeId nodeId, GraphAlignment alignment) +{ + const list repeatNodeIndexes = alignment.getIndexesOfNode(nodeId); + + const graphtools::Graph& graph = *alignment.path().graphRawPtr(); + const std::size_t nodeLength = graph.nodeSeq(nodeId).length(); + + int numFullOverlaps = 0; + for (auto nodeIndex : repeatNodeIndexes) + { + if (alignment[nodeIndex].referenceLength() == nodeLength) + { + ++numFullOverlaps; + } + } + + return numFullOverlaps; +} + +GraphAlignment computeCanonicalAlignment(const list& alignments) +{ + assert(!alignments.empty()); + + if (alignments.size() == 1) + { + return alignments.front(); + } + + boost::optional canonicalAlignment = alignments.front(); + + for (const auto& alignment : alignments) + { + GreedyAlignmentIntersector alignmentIntersector(*canonicalAlignment, alignment); + canonicalAlignment = alignmentIntersector.intersect(); + + if (!canonicalAlignment) + { + return alignments.front(); + } + } + + return *canonicalAlignment; +} + +} diff --git a/alignment/GraphAlignmentOperations.hh b/alignment/GraphAlignmentOperations.hh new file mode 100755 index 0000000..1a98e31 --- /dev/null +++ b/alignment/GraphAlignmentOperations.hh @@ -0,0 +1,56 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include "graphalign/GraphAlignment.hh" +#include "graphalign/LinearAlignmentParameters.hh" + +namespace ehunter +{ + +/** + * Adds softclips to the ends of the alignment + * + * @param alignment: any alignment + * @param leftSoftclipLen: length of the left softclip to add + * @param rightSoftclipLen: length of the right softclip to add + * @return extended alignment + * + * Example: + * GraphAlignment alignment = decodeGraphAlignment(1, "0[3M]1[3M]", &graph); + * GraphAlignment extendedAlignment = extendWithSoftclip(alignment, 5, 4); + * // extendedAlignment == decodeGraphAlignment(1, "0[5S3M]1[3M4S]", &graph); + */ +graphtools::GraphAlignment +extendWithSoftclip(const graphtools::GraphAlignment& alignment, int leftSoftclipLen, int rightSoftclipLen); + +int getNumNonrepeatMatchesUpstream(graphtools::NodeId nodeId, graphtools::GraphAlignment alignment); + +int getNumNonrepeatMatchesDownstream(graphtools::NodeId nodeId, graphtools::GraphAlignment alignment); + +int scoreAlignmentToNonloopNodes( + graphtools::GraphAlignment alignment, LinearAlignmentParameters parameters = LinearAlignmentParameters()); + +int countFullOverlaps(graphtools::NodeId nodeId, graphtools::GraphAlignment alignment); + +graphtools::GraphAlignment computeCanonicalAlignment(const std::list& alignments); + +} diff --git a/alignment/GreedyAlignmentIntersector.cpp b/alignment/GreedyAlignmentIntersector.cpp new file mode 100755 index 0000000..4f399d6 --- /dev/null +++ b/alignment/GreedyAlignmentIntersector.cpp @@ -0,0 +1,204 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "alignment/GreedyAlignmentIntersector.hh" + +#include + +using graphtools::Graph; +using graphtools::GraphAlignment; +using graphtools::NodeId; + +namespace ehunter +{ + +boost::optional GreedyAlignmentIntersector::intersect() +{ + initialize(); + + if (!tryAdvancingIndexesToCommonNode()) + { + return boost::optional(); + } + + if (checkIfCommonNodeIsLoop()) + { + advanceIndexesToMatchRemainingIterations(); + } + + advanceIndexesToLastCommonNode(); + computeIntersectionEnds(); + + if (!checkIfIntersectionIsConsistent()) + { + return boost::optional(); + } + + return softclipFirstAlignmentToIntersection(); +} + +void GreedyAlignmentIntersector::initialize() +{ + nodeIndexOfIntersectionStartOnFirstPath_ = 0; + nodeIndexOfIntersectionStartOnSecondPath_ = 0; +} + +bool GreedyAlignmentIntersector::checkIfAlignmentEndReached(int firstPathIndex, int secondPathIndex) +{ + return firstPathIndex == (int)firstAlignment_.size() || secondPathIndex == (int)secondAlignment_.size(); +} + +bool GreedyAlignmentIntersector::tryAdvancingIndexesToCommonNode() +{ + while (!checkIfAlignmentEndReached( + nodeIndexOfIntersectionStartOnFirstPath_, nodeIndexOfIntersectionStartOnSecondPath_)) + { + const NodeId firstPathNode = firstPath_.getNodeIdByIndex(nodeIndexOfIntersectionStartOnFirstPath_); + const NodeId secondPathNode = secondPath_.getNodeIdByIndex(nodeIndexOfIntersectionStartOnSecondPath_); + + if (firstPathNode < secondPathNode) + { + ++nodeIndexOfIntersectionStartOnFirstPath_; + } + else if (secondPathNode < firstPathNode) + { + ++nodeIndexOfIntersectionStartOnSecondPath_; + } + else + { + break; + } + } + + return !checkIfAlignmentEndReached( + nodeIndexOfIntersectionStartOnFirstPath_, nodeIndexOfIntersectionStartOnSecondPath_); +} + +bool GreedyAlignmentIntersector::checkIfCommonNodeIsLoop() const +{ + const NodeId firstPathNode = firstPath_.getNodeIdByIndex(nodeIndexOfIntersectionStartOnFirstPath_); + assert(firstPathNode == secondPath_.getNodeIdByIndex(nodeIndexOfIntersectionStartOnSecondPath_)); + + const Graph& graph = *firstPath_.graphRawPtr(); + return graph.hasEdge(firstPathNode, firstPathNode); +} + +void GreedyAlignmentIntersector::advanceIndexesToMatchRemainingIterations() +{ + const NodeId loopNodeId = firstPath_.getNodeIdByIndex(nodeIndexOfIntersectionStartOnFirstPath_); + // TODO: Move getIndexesOfNode to path class; + const int numIterationsMadeByFirstPath = firstAlignment_.getIndexesOfNode(loopNodeId).size(); + const int numIterationsMadeBySecondPath = secondAlignment_.getIndexesOfNode(loopNodeId).size(); + + if (numIterationsMadeByFirstPath < numIterationsMadeBySecondPath) + { + nodeIndexOfIntersectionStartOnSecondPath_ += numIterationsMadeBySecondPath - numIterationsMadeByFirstPath; + } + else if (numIterationsMadeBySecondPath < numIterationsMadeByFirstPath) + { + nodeIndexOfIntersectionStartOnFirstPath_ += numIterationsMadeByFirstPath - numIterationsMadeBySecondPath; + } +} + +void GreedyAlignmentIntersector::advanceIndexesToLastCommonNode() +{ + nodeIndexOfIntersectionEndOnFirstPath_ = nodeIndexOfIntersectionStartOnFirstPath_; + nodeIndexOfIntersectionEndOnSecondPath_ = nodeIndexOfIntersectionStartOnSecondPath_; + + while (!checkIfAlignmentEndReached( + nodeIndexOfIntersectionEndOnFirstPath_ + 1, nodeIndexOfIntersectionEndOnSecondPath_ + 1)) + { + const NodeId firstPathNode = firstPath_.getNodeIdByIndex(nodeIndexOfIntersectionEndOnFirstPath_ + 1); + const NodeId secondPathNode = secondPath_.getNodeIdByIndex(nodeIndexOfIntersectionEndOnSecondPath_ + 1); + + if (firstPathNode == secondPathNode) + { + ++nodeIndexOfIntersectionEndOnFirstPath_; + ++nodeIndexOfIntersectionEndOnSecondPath_; + } + else + { + break; + } + } +} + +void GreedyAlignmentIntersector::computeIntersectionEnds() +{ + const int firstPathStartPosition + = firstPath_.getStartPositionOnNodeByIndex(nodeIndexOfIntersectionStartOnFirstPath_); + const int secondPathStartPosition + = secondPath_.getStartPositionOnNodeByIndex(nodeIndexOfIntersectionStartOnSecondPath_); + + intersectionStart_ = std::max(firstPathStartPosition, secondPathStartPosition); + + const int firstPathEndPosition = firstPath_.getEndPositionOnNodeByIndex(nodeIndexOfIntersectionEndOnFirstPath_); + const int secondPathEndPosition = secondPath_.getEndPositionOnNodeByIndex(nodeIndexOfIntersectionEndOnSecondPath_); + + intersectionEnd_ = std::min(firstPathEndPosition, secondPathEndPosition); +} + +boost::optional GreedyAlignmentIntersector::softclipFirstAlignmentToIntersection() const +{ + GraphAlignment shrankAlignment = firstAlignment_; + + int leftoverPrefixReferenceLength = 0; + for (int nodeIndex = 0; nodeIndex != nodeIndexOfIntersectionStartOnFirstPath_; ++nodeIndex) + { + leftoverPrefixReferenceLength += firstPath_.getNodeOverlapLengthByIndex(nodeIndex); + } + + const int originalStartPosition + = firstPath_.getStartPositionOnNodeByIndex(nodeIndexOfIntersectionStartOnFirstPath_); + leftoverPrefixReferenceLength += intersectionStart_ - originalStartPosition; + + if (leftoverPrefixReferenceLength) + { + shrankAlignment.shrinkStart(leftoverPrefixReferenceLength); + } + + const int numNodes = firstPath_.numNodes(); + int leftoverSuffixReferenceLength = 0; + for (int nodeIndex = nodeIndexOfIntersectionEndOnFirstPath_ + 1; nodeIndex != numNodes; ++nodeIndex) + { + leftoverSuffixReferenceLength += firstPath_.getNodeOverlapLengthByIndex(nodeIndex); + } + + const int originalEndPosition = firstPath_.getEndPositionOnNodeByIndex(nodeIndexOfIntersectionEndOnFirstPath_); + leftoverSuffixReferenceLength += originalEndPosition - intersectionEnd_; + + if (leftoverSuffixReferenceLength) + { + shrankAlignment.shrinkEnd(leftoverSuffixReferenceLength); + } + + return shrankAlignment; +} + +bool GreedyAlignmentIntersector::checkIfIntersectionIsConsistent() const +{ + if (nodeIndexOfIntersectionStartOnFirstPath_ == nodeIndexOfIntersectionEndOnFirstPath_) + { + return intersectionStart_ < intersectionEnd_; + } + return true; +} + +} diff --git a/alignment/GreedyAlignmentIntersector.hh b/alignment/GreedyAlignmentIntersector.hh new file mode 100755 index 0000000..5450040 --- /dev/null +++ b/alignment/GreedyAlignmentIntersector.hh @@ -0,0 +1,69 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include + +#include "graphalign/GraphAlignment.hh" + +namespace ehunter +{ + +class GreedyAlignmentIntersector +{ +public: + GreedyAlignmentIntersector( + const graphtools::GraphAlignment& firstAlignment, const graphtools::GraphAlignment& secondAlignment) + : firstAlignment_(firstAlignment) + , secondAlignment_(secondAlignment) + , firstPath_(firstAlignment.path()) + , secondPath_(secondAlignment.path()) + { + initialize(); + } + + boost::optional intersect(); + +private: + void initialize(); + bool tryAdvancingIndexesToCommonNode(); + bool checkIfAlignmentEndReached(int firstPathIndex, int secondPathIndex); + bool checkIfCommonNodeIsLoop() const; + void advanceIndexesToMatchRemainingIterations(); + void advanceIndexesToLastCommonNode(); + void computeIntersectionEnds(); + bool checkIfIntersectionIsConsistent() const; + boost::optional softclipFirstAlignmentToIntersection() const; + + const graphtools::GraphAlignment& firstAlignment_; + const graphtools::GraphAlignment& secondAlignment_; + const graphtools::Path& firstPath_; + const graphtools::Path& secondPath_; + + int nodeIndexOfIntersectionStartOnFirstPath_; + int nodeIndexOfIntersectionStartOnSecondPath_; + int nodeIndexOfIntersectionEndOnFirstPath_ = -1; + int nodeIndexOfIntersectionEndOnSecondPath_ = -1; + int intersectionStart_ = -1; + int intersectionEnd_ = -1; +}; + +} diff --git a/alignment/HighQualityBaseRunFinder.cpp b/alignment/HighQualityBaseRunFinder.cpp new file mode 100755 index 0000000..4d1afac --- /dev/null +++ b/alignment/HighQualityBaseRunFinder.cpp @@ -0,0 +1,99 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "alignment/HighQualityBaseRunFinder.hh" + +#include +#include + +using std::string; + +namespace ehunter +{ + +template static int countGoodBases(Iter begin, Iter end) +{ + int numGoodBases = 0; + for (; begin != end; ++begin) + { + if (isupper(*begin)) + { + ++numGoodBases; + } + } + + return numGoodBases; +} + +template static double calculateBaseRunProb(double goodBaseProb, Iter begin, Iter end) +{ + const int runLength = end - begin; + const int numGoodBasesInRun = countGoodBases(begin, end); + const int numBadBasesInRun = runLength - numGoodBasesInRun; + return numGoodBasesInRun * goodBaseProb + numBadBasesInRun * (1.0 - goodBaseProb); +} + +template +static double calculateRunProbability( + double probOfGoodBaseInFirstRun, double probOfGoodBaseInSecondRun, Iter start, Iter changePoint, Iter end) +{ + const double firstRunProb = calculateBaseRunProb(probOfGoodBaseInFirstRun, start, changePoint); + const double secondRunProb = calculateBaseRunProb(probOfGoodBaseInSecondRun, changePoint, end); + + return firstRunProb + secondRunProb; +} + +template +static Iter findTopChangePoint(double probOfGoodBaseInFirstRun, double probOfGoodBaseInSecondRun, Iter start, Iter end) +{ + double topRunProb = 0; + auto topChangePoint = start; + + for (auto changePoint = start; changePoint != end; ++changePoint) + { + const double currentRunProb + = calculateRunProbability(probOfGoodBaseInFirstRun, probOfGoodBaseInSecondRun, start, changePoint, end); + + if (topRunProb < currentRunProb) + { + topRunProb = currentRunProb; + topChangePoint = changePoint; + } + } + + return topChangePoint; +} + +StringIterPair +findHighQualityBaseRun(const string& query, double probOfGoodBaseInBadRun, double probOfGoodBaseInGoodRun) +{ + const auto middleBaseIter = query.begin() + query.length() / 2; + const auto startOfGoodRun + = findTopChangePoint(probOfGoodBaseInBadRun, probOfGoodBaseInGoodRun, query.begin(), middleBaseIter); + + const auto middleBaseReverseIter = query.rbegin() + query.length() / 2; + const auto topEndingChangePointReverseIter + = findTopChangePoint(probOfGoodBaseInBadRun, probOfGoodBaseInGoodRun, query.rbegin(), middleBaseReverseIter); + const auto endOfGoodRun = topEndingChangePointReverseIter.base(); + + return std::make_pair(startOfGoodRun, endOfGoodRun); +} + +} diff --git a/alignment/HighQualityBaseRunFinder.hh b/alignment/HighQualityBaseRunFinder.hh new file mode 100755 index 0000000..721441c --- /dev/null +++ b/alignment/HighQualityBaseRunFinder.hh @@ -0,0 +1,42 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include + +namespace ehunter +{ + +using StringIterPair = std::pair; + +/** + * Searches for the first sufficiently-long run of high-quality bases + * + * @param query: any query sequence + * @param probOfGoodBaseInBadRun: probability of observing a high-quality base in a low quality stretch of bases + * @param probOfGoodBaseInGoodRun: probability of observing a high-quality base in a good quality stretch of bases + * @return pair of iterators delineating a substring consisting of high quality bases + */ +StringIterPair findHighQualityBaseRun( + const std::string& query, double probOfGoodBaseInBadRun = 0.1, double probOfGoodBaseInGoodRun = 0.8); + +} diff --git a/alignment/SoftclippingAligner.cpp b/alignment/SoftclippingAligner.cpp new file mode 100755 index 0000000..7436f6b --- /dev/null +++ b/alignment/SoftclippingAligner.cpp @@ -0,0 +1,62 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "alignment/SoftclippingAligner.hh" + +#include "alignment/GraphAlignmentOperations.hh" +#include "alignment/HighQualityBaseRunFinder.hh" + +using graphtools::Graph; +using graphtools::GraphAlignment; +using std::list; +using std::string; + +namespace ehunter +{ + +SoftclippingAligner::SoftclippingAligner( + const Graph* graphPtr, const std::string& alignerName, int kmerLenForAlignment, int paddingLength, + int seedAffixTrimLength) + : aligner_(graphPtr, kmerLenForAlignment, paddingLength, seedAffixTrimLength, alignerName) +{ +} + +list SoftclippingAligner::align(const string& query) const +{ + /* + HighQualityBaseRunFinder goodBaseFinder(6, 2, query.size() / 2); + const auto goodBasesRange = goodBaseFinder.find(query); + const auto goodBases = string(goodBasesRange.first, goodBasesRange.second); + + int numBasesTrimmedFromLeft = goodBasesRange.first - query.begin(); + int numBasesTrimmedFromRight = query.end() - goodBasesRange.second; + + list extendedAlignments; + for (const auto& alignment : aligner_.align(goodBases)) + { + const auto extendedAlignment = extendWithSoftclip(alignment, numBasesTrimmedFromLeft, numBasesTrimmedFromRight); + extendedAlignments.push_back(extendedAlignment); + } + */ + + return aligner_.align(query); +} + +} diff --git a/alignment/SoftclippingAligner.hh b/alignment/SoftclippingAligner.hh new file mode 100755 index 0000000..a2d4179 --- /dev/null +++ b/alignment/SoftclippingAligner.hh @@ -0,0 +1,45 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include + +#include "graphalign/GappedAligner.hh" +#include "graphalign/GraphAlignment.hh" +#include "graphcore/Graph.hh" + +namespace ehunter +{ + +class SoftclippingAligner +{ +public: + SoftclippingAligner( + const graphtools::Graph* graphPtr, const std::string& alignerName, int kmerLenForAlignment, int paddingLength, + int seedAffixTrimLength); + std::list align(const std::string& query) const; + +private: + graphtools::GappedGraphAligner aligner_; +}; + +} diff --git a/alignment/tests/AlignmentTweakersTest.cpp b/alignment/tests/AlignmentTweakersTest.cpp new file mode 100755 index 0000000..29ac73d --- /dev/null +++ b/alignment/tests/AlignmentTweakersTest.cpp @@ -0,0 +1,77 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "alignment/AlignmentTweakers.hh" + +#include "gtest/gtest.h" + +#include "graphalign/GraphAlignmentOperations.hh" + +#include "input/GraphBlueprint.hh" +#include "input/RegionGraph.hh" +#include "region_spec/LocusSpecification.hh" + +using graphtools::decodeGraphAlignment; +using graphtools::Graph; +using graphtools::GraphAlignment; +using std::string; + +using namespace ehunter; + +TEST(ShrinkingAlignmentPrefix, AlignmentWithUncertainPrefix_Shrank) +{ + Graph graph = makeRegionGraph(decodeFeaturesFromRegex("CATGGTGA(A)*(GAA)*TAACTACT")); + + // --22222233333 + const string query = "TTGAAGAATAACT"; + + { + GraphAlignment alignment = decodeGraphAlignment(0, "2[2S3M]2[3M]3[5M]", &graph); + shrinkUncertainPrefix(4, query, alignment); + + GraphAlignment expectedAlignment = decodeGraphAlignment(0, "2[5S3M]3[5M]", &graph); + + EXPECT_EQ(expectedAlignment, alignment); + } + + { + GraphAlignment alignment = decodeGraphAlignment(0, "2[2S3M]2[3M]3[5M]", &graph); + shrinkUncertainPrefix(8, query, alignment); + + GraphAlignment expectedAlignment = decodeGraphAlignment(0, "3[8S5M]", &graph); + + EXPECT_EQ(expectedAlignment, alignment); + } +} + +TEST(DISABLED_ShrinkingAlignmentSuffix, AlignmentWithUncertainSuffix_Shrank) +{ + Graph graph = makeRegionGraph(decodeFeaturesFromRegex("CATGGTGA(A)*(GAA)*TAACTACT")); + + // 0000011333-- + const string query = "GGTGAAATAAGG"; + GraphAlignment alignment = decodeGraphAlignment(3, "0[5M]1[1M]1[1M]3[3M2S]", &graph); + + shrinkUncertainSuffix(4, query, alignment); + + const GraphAlignment expectedAlignment = decodeGraphAlignment(3, "0[5M]1[1M]1[1M5S]", &graph); + + EXPECT_EQ(expectedAlignment, alignment); +} diff --git a/alignment/tests/CMakeLists.txt b/alignment/tests/CMakeLists.txt new file mode 100755 index 0000000..658e643 --- /dev/null +++ b/alignment/tests/CMakeLists.txt @@ -0,0 +1,19 @@ +add_executable(HighQualityBaseRunFinderTest HighQualityBaseRunFinderTest.cpp) +target_link_libraries(HighQualityBaseRunFinderTest alignment gtest gmock_main) +add_test(NAME HighQualityBaseRunFinderTest COMMAND HighQualityBaseRunFinderTest) + +add_executable(GraphAlignmentOperationsTest GraphAlignmentOperationsTest.cpp) +target_link_libraries(GraphAlignmentOperationsTest alignment gtest gmock_main) +add_test(NAME GraphAlignmentOperationsTest COMMAND GraphAlignmentOperationsTest) + +add_executable(SoftclippingAlignerTest SoftclippingAlignerTest.cpp) +target_link_libraries(SoftclippingAlignerTest alignment gtest gmock_main) +add_test(NAME SoftclippingAlignerTest COMMAND SoftclippingAlignerTest) + +add_executable(GreedyAlignmentIntersectorTest GreedyAlignmentIntersectorTest.cpp) +target_link_libraries(GreedyAlignmentIntersectorTest alignment gtest gmock_main) +add_test(NAME GreedyAlignmentIntersectorTest COMMAND GreedyAlignmentIntersectorTest) + +add_executable(AlignmentTweakersTest AlignmentTweakersTest.cpp) +target_link_libraries(AlignmentTweakersTest alignment gtest gmock_main) +add_test(NAME AlignmentTweakersTest COMMAND AlignmentTweakersTest) diff --git a/alignment/tests/GraphAlignmentOperationsTest.cpp b/alignment/tests/GraphAlignmentOperationsTest.cpp new file mode 100755 index 0000000..be4cb98 --- /dev/null +++ b/alignment/tests/GraphAlignmentOperationsTest.cpp @@ -0,0 +1,128 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "alignment/GraphAlignmentOperations.hh" + +#include "gtest/gtest.h" + +#include "graphalign/GraphAlignmentOperations.hh" +#include "graphcore/Graph.hh" +#include "graphcore/GraphBuilders.hh" +#include "input/RegionGraph.hh" +#include "region_spec/LocusSpecification.hh" + +using graphtools::decodeGraphAlignment; +using graphtools::Graph; +using graphtools::GraphAlignment; +using graphtools::makeStrGraph; +using graphtools::NodeId; +using graphtools::Path; +using std::string; + +using namespace ehunter; + +TEST(ExtendingAlignmentWithSoftclip, TypicalAlignment_Extended) +{ + Graph graph = makeRegionGraph(decodeFeaturesFromRegex("TAAT(CCG)*CCTT")); + + const GraphAlignment alignment = decodeGraphAlignment(1, "0[3M]1[3M]", &graph); + + { + GraphAlignment extendedAlignment = extendWithSoftclip(alignment, 5, 4); + + const GraphAlignment expectedAlignment = decodeGraphAlignment(1, "0[5S3M]1[3M4S]", &graph); + EXPECT_EQ(expectedAlignment, extendedAlignment); + } + + { + GraphAlignment extendedAlignment = extendWithSoftclip(alignment, 5, 0); + + const GraphAlignment expectedAlignment = decodeGraphAlignment(1, "0[5S3M]1[3M]", &graph); + EXPECT_EQ(expectedAlignment, extendedAlignment); + } + + { + GraphAlignment extendedAlignment = extendWithSoftclip(alignment, 0, 4); + + const GraphAlignment expectedAlignment = decodeGraphAlignment(1, "0[3M]1[3M4S]", &graph); + EXPECT_EQ(expectedAlignment, extendedAlignment); + } + + { + GraphAlignment extendedAlignment = extendWithSoftclip(alignment, 0, 0); + + const GraphAlignment expectedAlignment = decodeGraphAlignment(1, "0[3M]1[3M]", &graph); + EXPECT_EQ(expectedAlignment, extendedAlignment); + } +} + +TEST(CalculatingNumberOfNonRepeatMatchesAroundNode, TypicalAlignment_Calculated) +{ + Graph graph = makeRegionGraph(decodeFeaturesFromRegex("TAAT(CAG)*CAACAG(CCG)*CCTT")); + + const GraphAlignment alignment = decodeGraphAlignment(1, "0[3M]1[1M1I2M]1[3M]2[6M]3[3M]3[3M]4[4M]", &graph); + + EXPECT_EQ(15, getNumNonrepeatMatchesUpstream(3, alignment)); + EXPECT_EQ(0, getNumNonrepeatMatchesUpstream(0, alignment)); + EXPECT_EQ(16, getNumNonrepeatMatchesDownstream(1, alignment)); + EXPECT_EQ(0, getNumNonrepeatMatchesDownstream(4, alignment)); +} + +TEST(CalculatingNumberOfNonRepeatMatchesAroundNode, AlignmentNotPassingThroughRepeat_ZeroMatches) +{ + Graph graph = makeRegionGraph(decodeFeaturesFromRegex("TAAT(CAG)*CAACAG(CCG)*CCTT")); + + const GraphAlignment alignment = decodeGraphAlignment(1, "0[3M]1[1M1I2M]1[3M]", &graph); + + EXPECT_EQ(0, getNumNonrepeatMatchesUpstream(3, alignment)); + EXPECT_EQ(0, getNumNonrepeatMatchesDownstream(3, alignment)); +} + +TEST(StrOverlapQuantification, TypicalReads_StrOverlapComputed) +{ + Graph graph = makeRegionGraph(decodeFeaturesFromRegex("ATAT(CCG)*ATTT")); + + const NodeId repeatNodeId = 1; + + { + GraphAlignment alignment = decodeGraphAlignment(0, "0[4M]", &graph); + ASSERT_EQ(0, countFullOverlaps(repeatNodeId, alignment)); + } + + { + GraphAlignment alignment = decodeGraphAlignment(2, "0[2M]1[3M]1[3M]2[2M]", &graph); + ASSERT_EQ(2, countFullOverlaps(repeatNodeId, alignment)); + } + + { + GraphAlignment alignment = decodeGraphAlignment(2, "0[2M]1[3M]1[3M]1[2M]", &graph); + ASSERT_EQ(2, countFullOverlaps(repeatNodeId, alignment)); + } + + { + GraphAlignment alignment = decodeGraphAlignment(0, "1[3M]1[3M]1[3M]1[2M]", &graph); + ASSERT_EQ(3, countFullOverlaps(repeatNodeId, alignment)); + } + + { + GraphAlignment alignment = decodeGraphAlignment(1, "1[1S2M]1[1M2D]1[3M]1[2M]", &graph); + ASSERT_EQ(2, countFullOverlaps(repeatNodeId, alignment)); + } +} diff --git a/alignment/tests/GreedyAlignmentIntersectorTest.cpp b/alignment/tests/GreedyAlignmentIntersectorTest.cpp new file mode 100755 index 0000000..0ed0187 --- /dev/null +++ b/alignment/tests/GreedyAlignmentIntersectorTest.cpp @@ -0,0 +1,110 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "alignment/GreedyAlignmentIntersector.hh" + +#include "gtest/gtest.h" + +#include "graphalign/GraphAlignmentOperations.hh" +#include "graphcore/Graph.hh" +#include "graphcore/Path.hh" + +#include "input/RegionGraph.hh" +#include "region_spec/LocusSpecification.hh" + +using graphtools::Graph; +using graphtools::Path; + +using namespace ehunter; + +TEST(IntersectingPaths, IntersectionStartsAtLoopNode_PathsIntersected) +{ + Graph graph = makeRegionGraph(decodeFeaturesFromRegex("TAAT(CAG)*CAACAG(CCG)*CCTT")); + + { + const auto firstAlignment = decodeGraphAlignment(2, "0[2M]1[3M]1[3M]2[2M]", &graph); + const auto secondAlignment = decodeGraphAlignment(1, "1[2M]1[3M]2[1M]", &graph); + + GreedyAlignmentIntersector alignmentIntersector(firstAlignment, secondAlignment); + const auto intersection = alignmentIntersector.intersect(); + + const auto expectedAlignment = decodeGraphAlignment(1, "1[3S2M]1[3M]2[1M1S]", &graph); + EXPECT_EQ(expectedAlignment, *intersection); + } + + { + const auto firstAlignment = decodeGraphAlignment(1, "1[2M]1[3M]2[1M]", &graph); + const auto secondAlignment = decodeGraphAlignment(2, "0[2M]1[3M]1[3M]2[3M]", &graph); + + GreedyAlignmentIntersector alignmentIntersector(firstAlignment, secondAlignment); + const auto intersection = alignmentIntersector.intersect(); + + const auto expectedAlignment = decodeGraphAlignment(1, "1[2M]1[3M]2[1M]", &graph); + EXPECT_EQ(expectedAlignment, *intersection); + } + + { + const auto firstAlignment = decodeGraphAlignment(2, "0[2M]1[1M]", &graph); + const auto secondAlignment = decodeGraphAlignment(2, "1[1M]1[3M]2[2M]", &graph); + + GreedyAlignmentIntersector alignmentIntersector(firstAlignment, secondAlignment); + const auto intersection = alignmentIntersector.intersect(); + + const auto expectedAlignment = decodeGraphAlignment(0, "1[2S1M]", &graph); + EXPECT_EQ(expectedAlignment, *intersection); + } +} + +TEST(IntersectingPaths, IntersectionStartsAtRegularNode_PathsIntersected) +{ + Graph graph = makeRegionGraph(decodeFeaturesFromRegex("TAAT(CAG)*CAACAG(CCG)*CCTT")); + + const auto firstAlignment = decodeGraphAlignment(1, "0[3M]1[3M]1[3M]1[3M]1[3M]", &graph); + const auto secondAlignment = decodeGraphAlignment(2, "0[2M]1[3M]1[3M]1[2M]", &graph); + + GreedyAlignmentIntersector alignmentIntersector(firstAlignment, secondAlignment); + const auto intersection = alignmentIntersector.intersect(); + + const auto expectedAlignment = decodeGraphAlignment(2, "0[1S2M]1[3M]1[3M]1[2M4S]", &graph); + EXPECT_EQ(expectedAlignment, *intersection); +} + +TEST(IntersectingPaths, NonintersectingPaths_HandleledProperly) +{ + Graph graph = makeRegionGraph(decodeFeaturesFromRegex("TAAT(CAG)*CAACAG(CCG)*CCTT")); + + { + const auto firstAlignment = decodeGraphAlignment(1, "0[3M]1[3M]", &graph); + const auto secondAlignment = decodeGraphAlignment(0, "3[3M]4[2M]", &graph); + + GreedyAlignmentIntersector alignmentIntersector(firstAlignment, secondAlignment); + + EXPECT_FALSE(alignmentIntersector.intersect()); + } + + { + const auto firstAlignment = decodeGraphAlignment(1, "0[3M]1[3M]2[2M]", &graph); + const auto secondAlignment = decodeGraphAlignment(2, "2[4M]3[3M]4[2M]", &graph); + + GreedyAlignmentIntersector alignmentIntersector(firstAlignment, secondAlignment); + + EXPECT_FALSE(alignmentIntersector.intersect()); + } +} diff --git a/alignment/tests/HighQualityBaseRunFinderTest.cpp b/alignment/tests/HighQualityBaseRunFinderTest.cpp new file mode 100755 index 0000000..73605e7 --- /dev/null +++ b/alignment/tests/HighQualityBaseRunFinderTest.cpp @@ -0,0 +1,129 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "alignment/HighQualityBaseRunFinder.hh" + +#include + +#include "gtest/gtest.h" + +using std::make_pair; +using std::string; + +using namespace ehunter; + +TEST(SearchingForHighQualityBaseRuns, AllBasesHighQuality_FullRangeReturned) +{ + string sequence = "ATCGATCG"; + const auto goodBasesRange = findHighQualityBaseRun(sequence); + string goodBases = string(goodBasesRange.first, goodBasesRange.second); + + string expectedBases = string(sequence.begin(), sequence.end()); + ASSERT_EQ(expectedBases, goodBases); +} + +TEST(SearchingForHighQualityBaseRuns, SequenceEndingInLowQualityBases_CorrectRangeReturned) +{ + string sequence = "ATCGATCgaTcg"; + const auto goodBasesRange = findHighQualityBaseRun(sequence); + string goodBases = string(goodBasesRange.first, goodBasesRange.second); + + string expectedBases = string(sequence.begin(), sequence.end() - 5); + ASSERT_EQ(expectedBases, goodBases); +} + +TEST(SearchingForHighQualityBaseRuns, SequenceStartingWithLowQualityBases_CorrectRangeReturned) +{ + string sequence = "gaTcgaTCGATC"; + const auto goodBasesRange = findHighQualityBaseRun(sequence, 0.1, 0.8); + string goodBases = string(goodBasesRange.first, goodBasesRange.second); + + string expectedBases = string(sequence.begin() + 5, sequence.end()); + ASSERT_EQ(expectedBases, goodBases); +} + +TEST(SearchingForHighQualityBaseRuns, SequenceFlankedByLowQualityBasesOnBothSides_CorrectRangeReturned) +{ + string sequence = "gaTcgaTCGATCgaTcg"; + const auto goodBasesRange = findHighQualityBaseRun(sequence); + string goodBases = string(goodBasesRange.first, goodBasesRange.second); + + string expectedBases = string(sequence.begin() + 6, sequence.end() - 5); + ASSERT_EQ(expectedBases, goodBases); +} + +TEST(SearchingForHighQualityBaseRuns, SequenceComprisedOfLowQualityBases_EmptyRangeReturned) +{ + string sequence = "gaTcgaatgtTCatg"; + const auto goodBasesRange = findHighQualityBaseRun(sequence); + string goodBases = string(goodBasesRange.first, goodBasesRange.second); + + string expectedBases = string(sequence.begin() + 6, sequence.end() - 3); + ASSERT_EQ(expectedBases, goodBases); +} + +TEST(SearchingForHighQualityBaseRuns, RealReadEndingInManyLowQualityBases_CorrectRangeReturned) +{ + const string sequence = "CCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAACaGCCGCCACCGCCGCCGCCGCCGCC" + "GCCGCCtCCgCAGCCtCCtCaGCCGCCGCCGCCgcCgCaGCCGCcGCcgCCgCcgcCgcc"; + + const auto goodBasesRange = findHighQualityBaseRun(sequence); + string goodBases = string(goodBasesRange.first, goodBasesRange.second); + + string expectedBases = string(sequence.begin(), sequence.end() - 7); + ASSERT_EQ(expectedBases, goodBases); +} + +TEST(SearchingForHighQualityBaseRuns, QueryWithLowQualityStart_CorrectRangeReturned) +{ + const string sequence = "GcgggggGcGgcggcggcGggggcgcgggggccgGggggcGtGCGGcgggggggcGGcGGcGGCGGggGCGGcGGcGGcGGCGGcGgCGG" + "CGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGG"; + + const auto goodBasesRange = findHighQualityBaseRun(sequence); + string goodBases = string(goodBasesRange.first, goodBasesRange.second); + + string expectedBases = string(sequence.begin() + 55, sequence.end()); + ASSERT_EQ(expectedBases, goodBases); +} + +TEST(SearchingForHighQualityBaseRuns, QueryWithLowQualityEnd_CorrectRangeReturned) +{ + const string sequence = "GGcGGcGGCGGggGCGGcGGcGGcGGCGGcGgCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGC" + "GGCGGGcgggggGcGgcggcggcGggggcgcgggggccgGggggcGtGCGGcgggggggc"; + + const auto goodBasesRange = findHighQualityBaseRun(sequence); + string goodBases = string(goodBasesRange.first, goodBasesRange.second); + + string expectedBases = string(sequence.begin(), sequence.end() - 54); + ASSERT_EQ(expectedBases, goodBases); +} + +TEST(SearchingForHighQualityBaseRuns, QueryWithLowQualityEnds_CorrectRangeReturned) +{ + const string sequence + = "cgggggccgGggggcGtGCGGcgggggGGcGGcGGCGGggGCGGcGGcGGcGGCGGcGgCGGCGGCGGCGGCGGCGGCGGCGGGGGCGGGA" + "cgggggGcGgcggcggcGggggcgcgggggccgGggggcGtGCGGcgggggggc"; + + const auto goodBasesRange = findHighQualityBaseRun(sequence); + string goodBases = string(goodBasesRange.first, goodBasesRange.second); + + string expectedBases = string(sequence.begin() + 27, sequence.end() - 54); + ASSERT_EQ(expectedBases, goodBases); +} diff --git a/alignment/tests/SoftclippingAlignerTest.cpp b/alignment/tests/SoftclippingAlignerTest.cpp new file mode 100755 index 0000000..5c03818 --- /dev/null +++ b/alignment/tests/SoftclippingAlignerTest.cpp @@ -0,0 +1,92 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "alignment/SoftclippingAligner.hh" + +#include + +#include "gtest/gtest.h" + +#include "graphalign/GappedAligner.hh" +#include "graphalign/GraphAlignment.hh" +#include "graphalign/GraphAlignmentOperations.hh" +#include "graphcore/Graph.hh" + +#include "input/GraphBlueprint.hh" +#include "input/RegionGraph.hh" +#include "region_spec/LocusSpecification.hh" + +using graphtools::decodeGraphAlignment; +using graphtools::GappedGraphAligner; +using graphtools::Graph; +using graphtools::GraphAlignment; +using std::list; +using std::string; + +using namespace ehunter; + +/* +class AligningReads : public ::testing::TestWithParam +{ +}; + +// TODO: Throw an error if there are no valid extensions? +TEST_P(AligningReads, ReadFlankedByLowQualityBases_Aligned) +{ + Graph graph = makeRegionGraph(decodeFeaturesFromRegex("ATCGATCG", "(CAG)CAACAG(CCG)", "GCTAGCTA")); + GraphAlignmentHeuristicsParameters alignmentHeuristicsParams(14, 2, 5); + SoftclippingAligner aligner(&graph, GetParam(), alignmentHeuristicsParams); + + const string query = "gatcgCAgCAGCAACAGCCGCCGCCGCCGgcta"; + const list alignments = aligner.align(query); + + const list expectedAlignments + = { decodeGraphAlignment(6, "0[3S2M]1[3M]1[3M]2[6M]3[3M]3[3M]3[3M]3[1M6S]", &graph) }; + ASSERT_EQ(expectedAlignments, alignments); +} + +TEST_P(AligningReads, RealReadEndingInManyLowQualityBases_Aligned) +{ + const string leftFlank = "AGCCCCATTCATTGCCCCGGTGCTGAGCGGCGCCGCGAGTCGGCCCGAGGCCTCCGGGGACTGCCGTGCCGGGCGGGAGACCGCCATGG" + "CGACCCTGGAAAAGCTGATGAAGGCCTTCGAGTCCCTCAAGTCCTTC"; + const string rightFlank = "CCTCCTCAGCTTCCTCAGCCGCCGCCGCAGGCACAGCCGCTGCTGCCTCAGCCGCAGCCGCCCCCGCCGCCGCCCCCGCCGCCACCCG" + "GCCCGGCTGTGGCTGAGGAGCCGCTGCACCGACCGTGAGTTTGGGCC"; + + Graph graph = makeRegionGraph(decodeFeaturesFromRegex(leftFlank, "(CAG)CAACAG(CCG)", rightFlank)); + + GraphAlignmentHeuristicsParameters alignmentHeuristicsParams(14, 2, 5); + SoftclippingAligner aligner(&graph, GetParam(), alignmentHeuristicsParams); + + const string query = "CCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAGCAACaGCCGCCACCGCCGCCGCCGCCGCCGCC" + "GCCtCCgCAGCCtCCtCaGCCGCCGCCGCCgcCgCaGCCGCcGCcgCCgCcgcCgcc"; + + const list alignments = aligner.align(query); + + const list expectedAlignments = { decodeGraphAlignment( + 135, + "0[1M]1[3M]1[3M]1[3M]1[3M]1[3M]1[3M]1[3M]1[3M]1[3M]1[3M]1[3M]1[3M]1[3M]1[3M]1[3M]1[3M]1[3M]1[3M]1[3M]1[3M]2[6M]" + "3[3M]3[2M1X]3[3M]3[3M]3[3M]3[3M]3[3M]3[3M]3[3M]4[5M1X4M1X17M28S]", + &graph) }; + ASSERT_EQ(expectedAlignments, alignments); +} + +INSTANTIATE_TEST_CASE_P( + AlignerTestsInst, AligningReads, ::testing::Values(std::string("path-aligner"), std::string("dag-aligner")), ); +*/ \ No newline at end of file diff --git a/classification/AlignmentClassifier.cpp b/classification/AlignmentClassifier.cpp new file mode 100755 index 0000000..13301fd --- /dev/null +++ b/classification/AlignmentClassifier.cpp @@ -0,0 +1,145 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include +#include +#include + +#include "classification/AlignmentClassifier.hh" + +using graphtools::NodeId; +using graphtools::Path; +using std::list; +using std::ostream; +using std::string; +using std::unordered_set; +using std::vector; + +namespace ehunter +{ + +ostream& operator<<(ostream& os, const AlignmentType& read_class) +{ + switch (read_class) + { + case AlignmentType::kSpansRepeat: + os << "kSpansRepeat"; + break; + case AlignmentType::kFlanksRepeat: + os << "kFlanksRepeat"; + break; + case AlignmentType::kInsideRepeat: + os << "kInsideRepeat"; + break; + case AlignmentType::kOutsideRepeat: + os << "kOutsideRepeat"; + break; + case AlignmentType::kUnableToAlign: + os << "kUnableToAlign"; + break; + case AlignmentType::kUnprocessed: + os << "kUnprocessed"; + break; + default: + throw std::logic_error("Encountered unknown read class type"); + } + return os; +} + +RepeatAlignmentClassifier::RepeatAlignmentClassifier(const graphtools::Graph& graph, int32_t repeat_node_id) + : repeat_node_id_(repeat_node_id) +{ + left_flank_node_ids_ = graph.predecessors(repeat_node_id_); + left_flank_node_ids_.erase(repeat_node_id_); + + right_flank_node_ids_ = graph.successors(repeat_node_id_); + right_flank_node_ids_.erase(repeat_node_id_); +} + +GraphAlignment RepeatAlignmentClassifier::GetCanonicalAlignment(const list& Alignments) const +{ + const GraphAlignment* canonical_alignment_ptr = nullptr; + for (const GraphAlignment& alignment : Alignments) + { + AlignmentType alignment_type = Classify(alignment); + if (!canonical_alignment_ptr) + { + canonical_alignment_ptr = &alignment; + } + + if (alignment_type == AlignmentType::kInsideRepeat) + { + return alignment; + } + else if (alignment_type == AlignmentType::kFlanksRepeat) + { + canonical_alignment_ptr = &alignment; + } + } + return *canonical_alignment_ptr; +} + +AlignmentType RepeatAlignmentClassifier::Classify(const GraphAlignment& alignment) const +{ + bool overlaps_left_flank = false; + bool overlaps_right_flank = false; + + for (auto node_id : alignment.path().nodeIds()) + { + if (left_flank_node_ids_.find(node_id) != left_flank_node_ids_.end()) + { + overlaps_left_flank = true; + } + + if (right_flank_node_ids_.find(node_id) != right_flank_node_ids_.end()) + { + overlaps_right_flank = true; + } + } + + const bool overlaps_both_flanks = overlaps_left_flank && overlaps_right_flank; + const bool overlaps_either_flank = overlaps_left_flank || overlaps_right_flank; + + if (overlaps_both_flanks) + { + return AlignmentType::kSpansRepeat; + } + + const bool overlaps_repeat = alignment.overlapsNode(repeat_node_id_); + if (overlaps_either_flank && overlaps_repeat) + { + return AlignmentType::kFlanksRepeat; + } + + if (overlaps_repeat) + { + return AlignmentType::kInsideRepeat; + } + + return AlignmentType::kOutsideRepeat; +} + +bool RepeatAlignmentClassifier::operator==(const RepeatAlignmentClassifier& other) const +{ + return repeat_node_id_ == other.repeat_node_id_ && left_flank_node_ids_ == other.left_flank_node_ids_ + && right_flank_node_ids_ == other.right_flank_node_ids_; +} + +} diff --git a/classification/AlignmentClassifier.hh b/classification/AlignmentClassifier.hh new file mode 100755 index 0000000..473e825 --- /dev/null +++ b/classification/AlignmentClassifier.hh @@ -0,0 +1,65 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include +#include +#include + +#include "graphalign/GraphAlignment.hh" + +using graphtools::GraphAlignment; + +namespace ehunter +{ + +enum class AlignmentType +{ + kSpansRepeat, + kFlanksRepeat, + kInsideRepeat, + kOutsideRepeat, + kUnableToAlign, + kUnprocessed +}; + +std::ostream& operator<<(std::ostream& os, const AlignmentType& read_class); + +class RepeatAlignmentClassifier +{ +public: + RepeatAlignmentClassifier(const graphtools::Graph& graph, int32_t repeat_node_id); + AlignmentType Classify(const GraphAlignment& alignment) const; + GraphAlignment GetCanonicalAlignment(const std::list& alignments) const; + const std::set& leftFlankNodeIds() const { return left_flank_node_ids_; } + const std::set& rightFlankNodeIds() const { return right_flank_node_ids_; } + + bool operator==(const RepeatAlignmentClassifier& other) const; + +private: + int32_t repeat_node_id_; + std::set left_flank_node_ids_; + std::set right_flank_node_ids_; +}; + +} diff --git a/classification/CMakeLists.txt b/classification/CMakeLists.txt new file mode 100755 index 0000000..406c9e7 --- /dev/null +++ b/classification/CMakeLists.txt @@ -0,0 +1,4 @@ +file(GLOB SOURCES "*.cpp") +add_library(classification ${SOURCES}) +target_link_libraries(classification region_spec input graphtools reads) +add_subdirectory(tests) \ No newline at end of file diff --git a/classification/ClassifierOfAlignmentsToVariant.cpp b/classification/ClassifierOfAlignmentsToVariant.cpp new file mode 100755 index 0000000..2715750 --- /dev/null +++ b/classification/ClassifierOfAlignmentsToVariant.cpp @@ -0,0 +1,119 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "classification/ClassifierOfAlignmentsToVariant.hh" + +#include +#include +#include +#include + +#include + +using graphtools::NodeId; +using std::string; +using std::vector; + +namespace ehunter +{ + +static string encode(const vector& nodeIds) +{ + vector encoding; + for (auto nodeId : nodeIds) + { + encoding.push_back(std::to_string(nodeId)); + } + + return boost::algorithm::join(encoding, ", "); +} + +const NodeId ClassifierOfAlignmentsToVariant::kInvalidNodeId = std::numeric_limits::max(); + +ClassifierOfAlignmentsToVariant::ClassifierOfAlignmentsToVariant(vector targetNodes) + : targetNodes_(std::move(targetNodes)) +{ + if (targetNodes_.empty()) + { + throw std::logic_error("Cannot create a node bundle without nodes"); + } + + for (int index = 1; index != static_cast(targetNodes_.size()); ++index) + { + if (targetNodes_[index] != targetNodes_[index - 1] + 1) + { + throw std::logic_error("Bundle " + encode(targetNodes_) + " must contain ordered and consecutive nodes"); + } + } + + firstBundleNode_ = targetNodes_.front(); + lastBundleNode_ = targetNodes_.back(); +} + +void ClassifierOfAlignmentsToVariant::classify(const graphtools::GraphAlignment& graphAlignment) +{ + bool pathStartsUpstream = false; + bool pathEndsDownstream = false; + bool pathOverlapsTargetNode = false; + NodeId targetNodeOverlapped = kInvalidNodeId; + + for (auto pathNode : graphAlignment.path().nodeIds()) + { + if (pathNode < firstBundleNode_) + { + pathStartsUpstream = true; + } + else if (lastBundleNode_ < pathNode) + { + pathEndsDownstream = true; + } + else if (firstBundleNode_ <= pathNode && pathNode <= lastBundleNode_) + { + pathOverlapsTargetNode = true; + targetNodeOverlapped = pathNode; + } + } + + const bool spanningRead = pathStartsUpstream && pathEndsDownstream; + const bool upstreamFlankingRead = pathStartsUpstream && pathOverlapsTargetNode; + const bool downstreamFlankingRead = pathOverlapsTargetNode && pathEndsDownstream; + + if (spanningRead) + { + if (targetNodeOverlapped == kInvalidNodeId) + { + numBypassingReads_ += 1; + } + else + { + countsOfSpanningReads_.incrementCountOf(targetNodeOverlapped); + } + } + else if (upstreamFlankingRead) + { + countsOfReadsFlankingUpstream_.incrementCountOf(targetNodeOverlapped); + } + else if (downstreamFlankingRead) + { + countsOfReadsFlankingDownstream_.incrementCountOf(targetNodeOverlapped); + } +} + +} diff --git a/classification/ClassifierOfAlignmentsToVariant.hh b/classification/ClassifierOfAlignmentsToVariant.hh new file mode 100755 index 0000000..bd8112f --- /dev/null +++ b/classification/ClassifierOfAlignmentsToVariant.hh @@ -0,0 +1,59 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include + +#include "graphalign/GraphAlignment.hh" +#include "graphcore/Graph.hh" + +#include "common/CountTable.hh" + +namespace ehunter { + + +class ClassifierOfAlignmentsToVariant +{ +public: + static const graphtools::NodeId kInvalidNodeId; + + ClassifierOfAlignmentsToVariant(std::vector targetNodes); + + void classify(const graphtools::GraphAlignment& graphAlignment); + + const CountTable& countsOfReadsFlankingUpstream() const { return countsOfReadsFlankingUpstream_; } + const CountTable& countsOfReadsFlankingDownstream() const { return countsOfReadsFlankingDownstream_; } + const CountTable& countsOfSpanningReads() const { return countsOfSpanningReads_; } + int numBypassingReads() const { return numBypassingReads_; } + +private: + std::vector targetNodes_; + graphtools::NodeId firstBundleNode_; + graphtools::NodeId lastBundleNode_; + + CountTable countsOfReadsFlankingUpstream_; + CountTable countsOfReadsFlankingDownstream_; + CountTable countsOfSpanningReads_; + int numBypassingReads_ = 0; +}; + +} diff --git a/classification/tests/AlignmentClassifierTest.cpp b/classification/tests/AlignmentClassifierTest.cpp new file mode 100755 index 0000000..b078bcb --- /dev/null +++ b/classification/tests/AlignmentClassifierTest.cpp @@ -0,0 +1,166 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "classification/AlignmentClassifier.hh" + +#include +#include +#include + +#include "gtest/gtest.h" + +#include "graphalign/GraphAlignment.hh" +#include "graphalign/GraphAlignmentOperations.hh" +#include "graphcore/Graph.hh" +#include "graphcore/GraphBuilders.hh" + +using graphtools::Graph; +using graphtools::makeStrGraph; +using graphtools::NodeId; +using std::list; +using std::string; + +using namespace ehunter; + +TEST(InitializingAlignmentClassifier, SingleUnitRepeatGraph_RepeatFlanksDetermined) +{ + Graph graph = makeStrGraph("AAAACC", "CCG", "ATTT"); + RepeatAlignmentClassifier alignment_classifier(graph, 1); + + const std::set expected_left_flank_ids = { 0 }; + const std::set expected_right_flank_ids = { 2 }; + + EXPECT_EQ(expected_left_flank_ids, alignment_classifier.leftFlankNodeIds()); + EXPECT_EQ(expected_right_flank_ids, alignment_classifier.rightFlankNodeIds()); +} + +TEST(AlignmentClassificaton, SpanningAlignment_Classified) +{ + Graph graph = makeStrGraph("AAAACC", "CCG", "ATTT"); + RepeatAlignmentClassifier alignment_classifier(graph, 1); + + { // FFRRRRRRFF + const string read = "CCCCGCCGAT"; + GraphAlignment alignment = decodeGraphAlignment(4, "0[2M]1[3M]1[3M]2[2M]", &graph); + + EXPECT_EQ(AlignmentType::kSpansRepeat, alignment_classifier.Classify(alignment)); + } + + { // FFFF + const string read = "CCAT"; + GraphAlignment alignment = decodeGraphAlignment(4, "0[2M]2[2M]", &graph); + + RepeatAlignmentClassifier alignment_classifier(graph, 1); + EXPECT_EQ(AlignmentType::kSpansRepeat, alignment_classifier.Classify(alignment)); + } +} + +TEST(AlignmentClassificaton, FlankingAlignment_Classified) +{ + Graph graph = makeStrGraph("AAAACC", "CCG", "ATTT"); + RepeatAlignmentClassifier alignment_classifier(graph, 1); + + { // FFFFRRR + const string read = "AACCCCG"; + GraphAlignment alignment = decodeGraphAlignment(2, "0[4M]1[3M]", &graph); + + EXPECT_EQ(AlignmentType::kFlanksRepeat, alignment_classifier.Classify(alignment)); + } + + { // RRRFFF + const string read = "CCGATT"; + GraphAlignment alignment = decodeGraphAlignment(0, "1[3M]2[3M]", &graph); + + RepeatAlignmentClassifier alignment_classifier(graph, 1); + EXPECT_EQ(AlignmentType::kFlanksRepeat, alignment_classifier.Classify(alignment)); + } +} + +TEST(AlignmentClassificaton, RepeatAlignment_Classified) +{ + Graph graph = makeStrGraph("AAAACC", "CCG", "ATTT"); + RepeatAlignmentClassifier alignment_classifier(graph, 1); + + { // RRRRRRRR + const string read = "CCGCCGCC"; + GraphAlignment alignment = decodeGraphAlignment(0, "1[3M]1[3M]1[2M]", &graph); + + EXPECT_EQ(AlignmentType::kInsideRepeat, alignment_classifier.Classify(alignment)); + } + + { // RRRRRRRR + const string read = "CGCCGCCG"; + GraphAlignment alignment = decodeGraphAlignment(1, "1[2M]1[3M]1[3M]", &graph); + + RepeatAlignmentClassifier alignment_classifier(graph, 1); + EXPECT_EQ(AlignmentType::kInsideRepeat, alignment_classifier.Classify(alignment)); + } +} + +TEST(AlignmentClassificaton, OutsideRepeatAlignment_Classified) +{ + Graph graph = makeStrGraph("AAAACC", "CCG", "ATTT"); + RepeatAlignmentClassifier alignment_classifier(graph, 1); + + { // FFFFF + const string read = "AAAAC"; + GraphAlignment alignment = decodeGraphAlignment(0, "0[5M]", &graph); + + EXPECT_EQ(AlignmentType::kOutsideRepeat, alignment_classifier.Classify(alignment)); + } + + { // FFF + const string read = "TTT"; + GraphAlignment alignment = decodeGraphAlignment(1, "2[3M]", &graph); + + RepeatAlignmentClassifier alignment_classifier(graph, 1); + EXPECT_EQ(AlignmentType::kOutsideRepeat, alignment_classifier.Classify(alignment)); + } +} + +TEST(ObtainingCanonicalAlignment, FlankingAndRepeatRead_ClassifiedAsRepeat) +{ + Graph graph = makeStrGraph("AAAACG", "CCG", "ATTT"); + RepeatAlignmentClassifier alignment_classifier(graph, 1); + + // FFFFFFFF + const string read = "CGCCGCCG"; + const GraphAlignment flanking_alignment = decodeGraphAlignment(4, "0[2M]1[3M]1[3M]", &graph); + const GraphAlignment irr_alignment = decodeGraphAlignment(1, "1[2M]1[3M]1[3M]", &graph); + + const list alignments = { irr_alignment, flanking_alignment }; + + EXPECT_EQ(irr_alignment, alignment_classifier.GetCanonicalAlignment(alignments)); +} + +TEST(ObtainingCanonicalAlignment, FlankingAndSpanningRead_ClassifiedAsFlanking) +{ + Graph graph = makeStrGraph("AAAACG", "CCG", "ATTT"); + RepeatAlignmentClassifier alignment_classifier(graph, 1); + + // FFFFFFFFFF + const string read = "CGCCGCCGAT"; + const GraphAlignment spanning_Alignment = decodeGraphAlignment(4, "0[2M]1[3M]1[3M]2[2M]", &graph); + const GraphAlignment flanking_Alignment = decodeGraphAlignment(1, "1[2M]1[3M]1[3M]2[2M]", &graph); + + const list Alignments = { spanning_Alignment, flanking_Alignment }; + + EXPECT_EQ(flanking_Alignment, alignment_classifier.GetCanonicalAlignment(Alignments)); +} \ No newline at end of file diff --git a/classification/tests/AlignmentSummaryTest.cpp b/classification/tests/AlignmentSummaryTest.cpp new file mode 100755 index 0000000..d4e7391 --- /dev/null +++ b/classification/tests/AlignmentSummaryTest.cpp @@ -0,0 +1,70 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include +#include +#include + +#include "gtest/gtest.h" + +#include "reads/Read.hh" + +using namespace ehunter; + +using reads::Read; +using std::map; +using std::vector; + +/* +TEST(SummarizingAlignments, TypicalAlignments_Summarized) +{ + Read::SharedPtr flanking_read_1 = std::make_shared("frag1", "AAAACCCCG"); + flanking_read_1->SetCanonicalAlignmentType(AlignmentType::kFlanksRepeat); + flanking_read_1->SetNumStrUnitsSpanned(1); + + Read::SharedPtr flanking_read_2 = std::make_shared("frag2", "CCGCCGATT"); + flanking_read_2->SetCanonicalAlignmentType(AlignmentType::kFlanksRepeat); + flanking_read_2->SetNumStrUnitsSpanned(2); + + Read::SharedPtr inrepeat_read = std::make_shared("frag3", "CCGCCGCCG"); + inrepeat_read->SetCanonicalAlignmentType(AlignmentType::kInsideRepeat); + inrepeat_read->SetNumStrUnitsSpanned(3); + + Read::SharedPtr spanning_read = std::make_shared("frag4", "ACCCCGATT"); + spanning_read->SetCanonicalAlignmentType(AlignmentType::kSpansRepeat); + spanning_read->SetNumStrUnitsSpanned(1); + + Read::SharedPtr non_repeat_read = std::make_shared("frag4", "ACTGTGACT"); + non_repeat_read->SetCanonicalAlignmentType(AlignmentType::kOutsideRepeat); + non_repeat_read->SetNumStrUnitsSpanned(0); + + vector read_ptrs + = { flanking_read_1, flanking_read_2, inrepeat_read, spanning_read, non_repeat_read }; + + CountTable counts_of_flanking_reads; + CountTable counts_of_spanning_reads; + SummarizeAlignments(read_ptrs, counts_of_flanking_reads, counts_of_spanning_reads); + + const CountTable expected_counts_of_flanking_reads = { { 1, 1 }, { 2, 1 }, { 3, 1 } }; + const CountTable expected_counts_of_spanning_reads = { { 1, 1 } }; + EXPECT_EQ(expected_counts_of_flanking_reads, counts_of_flanking_reads); + EXPECT_EQ(expected_counts_of_spanning_reads, counts_of_spanning_reads); +} +*/ \ No newline at end of file diff --git a/classification/tests/CMakeLists.txt b/classification/tests/CMakeLists.txt new file mode 100755 index 0000000..f6385b2 --- /dev/null +++ b/classification/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable(AlignmentClassifierTest AlignmentClassifierTest.cpp) +target_link_libraries(AlignmentClassifierTest classification gtest_main) +add_test(NAME AlignmentClassifierTest COMMAND AlignmentClassifierTest) + +add_executable(AlignmentSummaryTest AlignmentSummaryTest.cpp) +target_link_libraries(AlignmentSummaryTest classification gtest_main) +add_test(NAME AlignmentSummaryTest COMMAND AlignmentSummaryTest) + +add_executable(ClassifierOfAlignmentsToVariantTest ClassifierOfAlignmentsToVariantTest.cpp) +target_link_libraries(ClassifierOfAlignmentsToVariantTest classification gtest_main) +add_test(NAME ClassifierOfAlignmentsToVariantTest COMMAND ClassifierOfAlignmentsToVariantTest) diff --git a/classification/tests/ClassifierOfAlignmentsToVariantTest.cpp b/classification/tests/ClassifierOfAlignmentsToVariantTest.cpp new file mode 100755 index 0000000..31166e1 --- /dev/null +++ b/classification/tests/ClassifierOfAlignmentsToVariantTest.cpp @@ -0,0 +1,83 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "classification/ClassifierOfAlignmentsToVariant.hh" + +#include + +#include "gtest/gtest.h" + +#include "graphalign/GraphAlignmentOperations.hh" +#include "graphcore/Graph.hh" + +#include "common/CountTable.hh" +#include "input/GraphBlueprint.hh" +#include "input/RegionGraph.hh" + +using graphtools::Graph; +using graphtools::GraphAlignment; +using std::map; +using std::string; + +using namespace ehunter; + +TEST(InitializingAlignmentClassifier, VariantNodesAreNonconsecutive_ExceptionThrown) +{ + EXPECT_ANY_THROW(ClassifierOfAlignmentsToVariant({})); + EXPECT_ANY_THROW(ClassifierOfAlignmentsToVariant({ 2, 4 })); +} + +TEST(ClassifyingAlignmentsOverIndel, DownstreamAndUpstreamAlignments_Classified) +{ + ClassifierOfAlignmentsToVariant classifier({ 4 }); + + // NodeIds = 0 1 2 3 4 5 + Graph graph = makeRegionGraph(decodeFeaturesFromRegex("AC(T|G)CT(CA)?TGTGT")); + + GraphAlignment upstreamAlignment = decodeGraphAlignment(1, "0[1M]1[1M]3[2M]", &graph); + ASSERT_TRUE(checkConsistency(upstreamAlignment, "CTCT")); + + GraphAlignment downstreamAlignment = decodeGraphAlignment(0, "5[4M]", &graph); + ASSERT_TRUE(checkConsistency(downstreamAlignment, "TGTG")); + + GraphAlignment spanningAlignment = decodeGraphAlignment(1, "0[1M]1[1M]3[2M]4[2M]5[3M]", &graph); + ASSERT_TRUE(checkConsistency(spanningAlignment, "CTCTCATGT")); + + GraphAlignment bypassingAlignment = decodeGraphAlignment(1, "0[1M]1[1M]3[2M]5[3M]", &graph); + ASSERT_TRUE(checkConsistency(bypassingAlignment, "CTCTTGT")); + + GraphAlignment upstreamFlankingAlignment = decodeGraphAlignment(1, "0[1M]1[1M]3[2M]4[2M]", &graph); + ASSERT_TRUE(checkConsistency(upstreamFlankingAlignment, "CTCTCA")); + + GraphAlignment downstreamFlankingAlignment = decodeGraphAlignment(0, "4[2M]5[3M]", &graph); + ASSERT_TRUE(checkConsistency(downstreamFlankingAlignment, "CATGT")); + + classifier.classify(upstreamAlignment); + classifier.classify(downstreamAlignment); + classifier.classify(spanningAlignment); + classifier.classify(bypassingAlignment); + classifier.classify(upstreamFlankingAlignment); + classifier.classify(downstreamFlankingAlignment); + + EXPECT_EQ(CountTable(map({ { 4, 1 } })), classifier.countsOfReadsFlankingUpstream()); + EXPECT_EQ(CountTable(map({ { 4, 1 } })), classifier.countsOfReadsFlankingDownstream()); + EXPECT_EQ(CountTable(map({ { 4, 1 } })), classifier.countsOfSpanningReads()); + EXPECT_EQ(1, classifier.numBypassingReads()); +} diff --git a/cmake/google_test.cmake b/cmake/google_test.cmake old mode 100644 new mode 100755 diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt old mode 100644 new mode 100755 index 714ee24..3351a79 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -1,3 +1,4 @@ -file(GLOB sources *.cc) -add_library(common ${sources}) -target_link_libraries(common ${Boost_LIBRARIES}) \ No newline at end of file +file(GLOB SOURCES "*.cpp") +add_library(common ${SOURCES}) +target_link_libraries(common region_spec ${htslib_static} ${zlib_static} ${Boost_LIBRARIES}) +add_subdirectory(tests) diff --git a/common/Common.cpp b/common/Common.cpp new file mode 100755 index 0000000..a8ddc00 --- /dev/null +++ b/common/Common.cpp @@ -0,0 +1,87 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Mitch Bekritsky , Richard Shaw +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "common/Common.hh" + +using std::string; + +namespace ehunter +{ + +Sex decodeSampleSex(const std::string& encoding) +{ + if (encoding == "male") + { + return Sex::kMale; + } + else if (encoding == "female") + { + return Sex::kFemale; + } + else + { + throw std::invalid_argument(encoding + " is invalid sex; must be either male or female"); + } +} + +std::ostream& operator<<(std::ostream& out, ReadType readType) +{ + switch (readType) + { + case ReadType::kFlanking: + out << "FLANKING"; + break; + case ReadType::kRepeat: + out << "INREPEAT"; + break; + case ReadType::kSpanning: + out << "SPANNING"; + break; + case ReadType::kOther: + out << "OTHER"; + } + return out; +} + +std::ostream& operator<<(std::ostream& out, AlleleCount alleleCount) +{ + switch (alleleCount) + { + case AlleleCount::kZero: + out << "Zero alleles"; + break; + case AlleleCount::kOne: + out << "One allele"; + break; + case AlleleCount::kTwo: + out << "Two alleles"; + } + return out; +} + +std::ostream& operator<<(std::ostream& out, NumericInterval numericInterval) +{ + out << numericInterval.start() << "-" << numericInterval.end(); + return out; +} + +} diff --git a/common/Common.hh b/common/Common.hh new file mode 100755 index 0000000..0bd5a9a --- /dev/null +++ b/common/Common.hh @@ -0,0 +1,109 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Mitch Bekritsky , Richard Shaw +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include +#include + +namespace ehunter +{ + +enum class ReadType +{ + kSpanning, + kFlanking, + kRepeat, + kOther +}; + +enum class Sex +{ + kMale, + kFemale +}; + +Sex decodeSampleSex(const std::string& encoding); + +enum class AlleleCount +{ + kZero, + kOne, + kTwo +}; + +class NumericInterval +{ +public: + NumericInterval() + : start_(0) + , end_(0) + { + } + + NumericInterval(int start, int end) + : start_(start) + , end_(end) + { + } + + int start() const { return start_; } + int end() const { return end_; } + + NumericInterval& operator=(const NumericInterval& other) + { + start_ = other.start_; + end_ = other.end_; + return *this; + } + + bool operator==(const NumericInterval& other) const { return start_ == other.start_ && end_ == other.end_; } + +private: + int start_; + int end_; +}; + +template struct LabeledSequence +{ + LabeledSequence(const std::string sequence, T label) + : sequence(sequence) + , label(label) + { + } + + bool operator==(const LabeledSequence& other) const + { + return sequence == other.sequence && label == other.label; + } + + std::string sequence; + T label; +}; + +std::ostream& operator<<(std::ostream& out, ReadType readType); +std::ostream& operator<<(std::ostream& out, AlleleCount alleleCount); +std::ostream& operator<<(std::ostream& out, NumericInterval numericInterval); + +} diff --git a/common/CountTable.cpp b/common/CountTable.cpp new file mode 100755 index 0000000..23a322d --- /dev/null +++ b/common/CountTable.cpp @@ -0,0 +1,87 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "common/CountTable.hh" + +using std::string; +using std::to_string; +using std::vector; + +namespace ehunter +{ + +int32_t CountTable::countOf(int32_t element) const +{ + if (elements_to_counts_.find(element) == elements_to_counts_.end()) + { + return 0; + } + return elements_to_counts_.at(element); +} + +void CountTable::setCountOf(int32_t element, int32_t count) +{ + elements_to_counts_[element] = count; + if (count == 0) + { + elements_to_counts_.erase(element); + } +} + +void CountTable::incrementCountOf(int32_t element) { ++elements_to_counts_[element]; } + +vector CountTable::getElementsWithNonzeroCounts() const +{ + vector elements; + for (const auto& element_count : elements_to_counts_) + { + elements.push_back(element_count.first); + } + + return elements; +} + +std::ostream& operator<<(std::ostream& out, const CountTable& count_table) +{ + string encoding; + + for (int32_t element : count_table.getElementsWithNonzeroCounts()) + { + + if (!encoding.empty()) + { + encoding += ", "; + } + + encoding += "(" + to_string(element) + ", " + to_string(count_table.countOf(element)) + ")"; + } + + if (encoding.empty()) + { + encoding = "()"; + } + + out << encoding; + + return out; +} + +} diff --git a/common/CountTable.hh b/common/CountTable.hh new file mode 100755 index 0000000..ff54736 --- /dev/null +++ b/common/CountTable.hh @@ -0,0 +1,58 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include +#include + +namespace ehunter { + + +class CountTable +{ +public: + using const_iterator = std::map::const_iterator; + const_iterator begin() const { return elements_to_counts_.begin(); } + const_iterator end() const { return elements_to_counts_.end(); } + + CountTable(){}; + explicit CountTable(const std::map& elements_to_counts) + : elements_to_counts_(elements_to_counts){}; + + void clear() { elements_to_counts_.clear(); } + + int32_t countOf(int32_t element) const; + void incrementCountOf(int32_t element); + void setCountOf(int32_t element, int32_t count); + std::vector getElementsWithNonzeroCounts() const; + + bool operator==(const CountTable& other) const { return elements_to_counts_ == other.elements_to_counts_; } + +private: + std::map elements_to_counts_; +}; + +std::ostream& operator<<(std::ostream& out, const CountTable& count_table); + +} diff --git a/common/GenomicRegion.cpp b/common/GenomicRegion.cpp new file mode 100755 index 0000000..dbc4b2c --- /dev/null +++ b/common/GenomicRegion.cpp @@ -0,0 +1,174 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Mitch Bekritsky , Richard Shaw +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "common/GenomicRegion.hh" + +#include +#include +#include + +#include +#include +#include +#include +#include + +using std::istream; +using std::ostream; +using std::string; +using std::vector; + +using boost::lexical_cast; +using boost::algorithm::is_any_of; +using boost::algorithm::split; + +namespace ehunter +{ + +Region::Region(const std::string chrom, int64_t start, int64_t end) + : chrom_(chrom) + , start_(start) + , end_(end) +{ +} + +Region::Region(const std::string encoding) +{ + vector components; + boost::algorithm::split(components, encoding, is_any_of(":-")); + + if (components.size() != 3) + { + throw std::logic_error("Unexpected range format: " + encoding); + } + + chrom_ = components[0]; + start_ = lexical_cast(components[1]); + end_ = lexical_cast(components[2]); +} + +bool Region::operator<(const Region& other) const +{ + if (chrom_ != other.chrom_) + { + return chrom_ < other.chrom_; + } + + if (start_ != other.start_) + { + return start_ < other.start_; + } + + return end_ < other.end_; +} + +bool Region::Overlaps(const Region& other) const +{ + if (chrom_ != other.chrom_) + { + return false; + } + + const int64_t leftBound = start_ > other.start_ ? start_ : other.start_; + const int64_t rightBound = end_ < other.end_ ? end_ : other.end_; + + return leftBound <= rightBound; +} + +int64_t Region::Distance(const Region& other) const +{ + if (chrom_ != other.chrom_) + { + return std::numeric_limits::max(); + } + + if (end_ < other.start_) + { + return other.start_ - end_; + } + + if (other.end_ < start_) + { + return start_ - other.end_; + } + + return 0; +} + +vector merge(vector regions, int maxMergeDist) +{ + if (regions.empty()) + { + return regions; + } + + std::sort(regions.begin(), regions.end()); + + Region mergedRegion = regions.front(); + vector mergedRegions; + + for (const auto& currentRegion : regions) + { + if (currentRegion.Distance(mergedRegion) <= maxMergeDist) + { + const int64_t furthestEnd = std::max(mergedRegion.end(), currentRegion.end()); + mergedRegion.setEnd(furthestEnd); + } + else + { + mergedRegions.push_back(mergedRegion); + mergedRegion = currentRegion; + } + } + + if (mergedRegions.empty() || (mergedRegions.back() != mergedRegion)) + { + mergedRegions.push_back(mergedRegion); + } + + return mergedRegions; +} + +const string Region::ToString() const +{ + std::ostringstream out; + out << *this; + return out.str(); +} + +// Returns the range extended by flankSize upstream and downstream. +// NOTE: The right boundary of the extended region may stick past chromosome +// end. +Region Region::extend(int length) const +{ + const int64_t new_start = start_ >= length ? (start_ - length) : 0; + const int64_t new_end = end_ + length; + return Region(chrom_, new_start, new_end); +} + +std::ostream& operator<<(std::ostream& out, const Region& region) +{ + out << region.chrom_ << ":" << region.start_ << "-" << region.end_; + return out; +} + +} diff --git a/common/GenomicRegion.hh b/common/GenomicRegion.hh new file mode 100755 index 0000000..e70647c --- /dev/null +++ b/common/GenomicRegion.hh @@ -0,0 +1,71 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Mitch Bekritsky , Richard Shaw +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include + +namespace ehunter +{ + +class Region +{ +public: + friend std::ostream& operator<<(std::ostream& out, const Region& region); + + Region(const std::string chrom, int64_t start, int64_t end); + explicit Region(const std::string encoding); + + bool operator<(const Region& other) const; + + bool Overlaps(const Region& other) const; + int64_t Distance(const Region& other) const; + + const std::string& chrom() const { return chrom_; } + int64_t start() const { return start_; } + int64_t end() const { return end_; } + int64_t length() const { return end_ - start_ + 1; } + + void setChrom(const std::string& chrom) { chrom_ = chrom; } + void setStart(int64_t start) { start_ = start; } + void setEnd(int64_t end) { end_ = end; } + bool operator==(const Region& other) const + { + return chrom_ == other.chrom_ && start_ == other.start_ && end_ == other.end_; + } + bool operator!=(const Region& other) const { return !(*this == other); } + + Region extend(int length) const; + const std::string ToString() const; + +private: + std::string chrom_; + int64_t start_; + int64_t end_; +}; + +std::vector merge(std::vector regions, int maxMergeDist = 500); +std::ostream& operator<<(std::ostream& out, const Region& region); + +} diff --git a/common/Parameters.cpp b/common/Parameters.cpp new file mode 100755 index 0000000..aa6f35f --- /dev/null +++ b/common/Parameters.cpp @@ -0,0 +1,65 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "common/Parameters.hh" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using std::string; +using std::vector; + +namespace ehunter { + + +namespace po = boost::program_options; +namespace fs = boost::filesystem; + +Outputs::Outputs(const string& vcfPath, const string& jsonPath, const string& logPath) +{ + vcf_.open(vcfPath.c_str()); + if (!vcf_.is_open()) + { + throw std::runtime_error("Failed to open " + vcfPath + " for writing: " + strerror(errno)); + } + + json_.open(jsonPath.c_str()); + if (!json_.is_open()) + { + throw std::runtime_error("Failed to open " + jsonPath + " for writing: " + strerror(errno)); + } + + log_.open(logPath.c_str()); + if (!log_.is_open()) + { + throw std::runtime_error("Failed to open " + logPath + " for writing: " + strerror(errno)); + } +} + + +} diff --git a/common/Parameters.hh b/common/Parameters.hh new file mode 100755 index 0000000..f1033f9 --- /dev/null +++ b/common/Parameters.hh @@ -0,0 +1,190 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "common/Common.hh" +#include "common/GenomicRegion.hh" + +namespace ehunter +{ + +class Outputs +{ +public: + Outputs(const std::string& vcfPath, const std::string& jsonPath, const std::string& logPath); + std::ostream& vcf() { return vcf_; } + std::ostream& json() { return json_; } + std::ostream& log() { return log_; } + +private: + std::ofstream vcf_; + std::ofstream json_; + std::ofstream log_; +}; + +class InputPaths +{ +public: + InputPaths(std::string htsFile, std::string reference, std::string catalog) + : htsFile_(std::move(htsFile)) + , reference_(std::move(reference)) + , catalog_(std::move(catalog)) + { + } + + const std::string& htsFile() const { return htsFile_; } + const std::string& reference() const { return reference_; } + const std::string& catalog() const { return catalog_; } + +private: + std::string htsFile_; + std::string reference_; + std::string catalog_; +}; + +class OutputPaths +{ +public: + OutputPaths(std::string vcf, std::string json, std::string log) + : vcf_(vcf) + , json_(json) + , log_(log) + { + } + + const std::string& vcf() const { return vcf_; } + const std::string& json() const { return json_; } + const std::string& log() const { return log_; } + +private: + std::string vcf_; + std::string json_; + std::string log_; +}; + +class SampleParameters +{ +public: + SampleParameters( + std::string id, Sex sex, int readLength, + boost::optional optionalHaplotypeDepth = boost::optional()) + : id_(std::move(id)) + , sex_(sex) + , readLength_(readLength) + , optionalHaplotypeDepth_(optionalHaplotypeDepth) + { + } + + const std::string& id() const { return id_; } + const Sex& sex() const { return sex_; } + int readLength() const { return readLength_; } + + double haplotypeDepth() const + { + if (!optionalHaplotypeDepth_) + { + throw std::logic_error("Attempting to access unset depth parameter"); + } + return *optionalHaplotypeDepth_; + } + + bool isHaplotypeDepthSet() const { return optionalHaplotypeDepth_.is_initialized(); } + void setHaplotypeDepth(double haplotypeDepth) { optionalHaplotypeDepth_ = haplotypeDepth; } + +private: + std::string id_; + Sex sex_; + int readLength_; + boost::optional optionalHaplotypeDepth_; +}; + +class HeuristicParameters +{ +public: + HeuristicParameters( + bool verboseLogging, int regionExtensionLength, int qualityCutoffForGoodBaseCall, bool skipUnaligned, + const std::string& alignerType, int kmerLenForAlignment = 14, int paddingLength = 10, + int seedAffixTrimLength = 5) + : verboseLogging_(verboseLogging) + , regionExtensionLength_(regionExtensionLength) + , qualityCutoffForGoodBaseCall_(qualityCutoffForGoodBaseCall) + , skipUnaligned_(skipUnaligned) + , alignerType_(alignerType) + , kmerLenForAlignment_(kmerLenForAlignment) + , paddingLength_(paddingLength) + , seedAffixTrimLength_(seedAffixTrimLength) + + { + } + + bool verboseLogging() const { return verboseLogging_; } + int regionExtensionLength() const { return regionExtensionLength_; } + int qualityCutoffForGoodBaseCall() const { return qualityCutoffForGoodBaseCall_; } + bool skipUnaligned() const { return skipUnaligned_; } + const std::string& alignerType() const { return alignerType_; } + int kmerLenForAlignment() const { return kmerLenForAlignment_; } + int paddingLength() const { return paddingLength_; } + int seedAffixTrimLength() const { return seedAffixTrimLength_; } + +private: + bool verboseLogging_; + int regionExtensionLength_; + int qualityCutoffForGoodBaseCall_; + bool skipUnaligned_; + std::string alignerType_; + int kmerLenForAlignment_; + int paddingLength_; + int seedAffixTrimLength_; +}; + +class ProgramParameters +{ +public: + ProgramParameters( + InputPaths inputPaths, OutputPaths outputPaths, SampleParameters sample, HeuristicParameters heuristics) + : inputPaths_(std::move(inputPaths)) + , outputPaths_(std::move(outputPaths)) + , sample_(std::move(sample)) + , heuristics_(std::move(heuristics)) + { + } + + const InputPaths& inputPaths() const { return inputPaths_; } + const OutputPaths& outputPaths() const { return outputPaths_; } + SampleParameters& sample() { return sample_; } + const HeuristicParameters& heuristics() const { return heuristics_; } + +private: + InputPaths inputPaths_; + OutputPaths outputPaths_; + SampleParameters sample_; + HeuristicParameters heuristics_; +}; + +} diff --git a/common/Reference.cpp b/common/Reference.cpp new file mode 100755 index 0000000..f4e1421 --- /dev/null +++ b/common/Reference.cpp @@ -0,0 +1,62 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Mitch Bekritsky , Richard Shaw +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "common/Reference.hh" + +#include +#include +#include + +using std::string; + +namespace ehunter +{ + +FastaReference::FastaReference(const string& genome_path) + : genome_path_(genome_path) +{ + fai_ptr_ = fai_load(genome_path_.c_str()); +} + +FastaReference::~FastaReference() { fai_destroy(fai_ptr_); } + +string FastaReference::getSequence(const string& chrom, pos_t start, pos_t end) const +{ + int len; // throwaway... + // this htslib function is 0-based closed but we need to be open + char* ref_tmp = faidx_fetch_seq(fai_ptr_, chrom.c_str(), start, end - 1, &len); + + if (!ref_tmp || len < 0 || static_cast(len) < end - start) + { + string region(chrom + ":" + std::to_string(start) + "-" + std::to_string(end)); + throw std::runtime_error( + "Cannot extract " + region + " from " + genome_path_ + "; chromosome names must match exactly " + + "(e.g. \"chr1\" and \"1\" are distinct names) " + + "and coordinates cannot be past the end of the chromosome"); + } + string sequence("N", len); + std::transform(ref_tmp, ref_tmp + len, sequence.begin(), ::toupper); + free(ref_tmp); + return sequence; +} + +} diff --git a/common/repeat_spec.h b/common/Reference.hh old mode 100644 new mode 100755 similarity index 52% rename from common/repeat_spec.h rename to common/Reference.hh index 5ced5fe..5cdfe17 --- a/common/repeat_spec.h +++ b/common/Reference.hh @@ -22,36 +22,45 @@ #pragma once -#include #include -#include #include -#include - -#include "common/genomic_region.h" - -namespace ehunter { -class RepeatSpec { - public: - RepeatSpec() {} - explicit RepeatSpec(const std::string& json_path); - char LeftFlankBase() const; - bool is_common_unit() const { return is_common_unit_; } - - std::string repeat_id; - Region target_region; - std::string left_flank; - std::string right_flank; - std::string ref_seq; - std::vector units; - std::vector> units_shifts; - std::vector offtarget_regions; - - private: - bool is_common_unit_; + +// Include the fai class from samtools +#include "htslib/faidx.h" + +#include "common/GenomicRegion.hh" + +namespace ehunter +{ + +using pos_t = size_t; + +class Reference +{ +public: + /** + * @param chrom Name of the reference contig (chromosome) + * @param start 0-based, inclusive + * @param end 0-based, exclusive + * @return Reference sequence in upper case + */ + virtual std::string getSequence(const std::string& chrom, pos_t start, pos_t end) const = 0; +}; + +/** + * Reference Genome implementation backed by a fasta file read through htslib + */ +class FastaReference : public Reference +{ +public: + explicit FastaReference(const std::string& genome_path); + ~FastaReference(); + + std::string getSequence(const std::string& chrom, pos_t start, pos_t end) const override; + +private: + std::string genome_path_; + faidx_t* fai_ptr_; }; -bool LoadRepeatSpecs(const std::string& specs_path, - const std::string& genome_path, double min_wp, - std::map* repeat_specs); -} // namespace ehunter +} diff --git a/common/ref_genome.h b/common/SequenceOperations.cpp old mode 100644 new mode 100755 similarity index 61% rename from common/ref_genome.h rename to common/SequenceOperations.cpp index b6a6eec..922b634 --- a/common/ref_genome.h +++ b/common/SequenceOperations.cpp @@ -1,9 +1,8 @@ // // Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. +// Copyright (c) 2018 Illumina, Inc. // // Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw // Concept: Michael Eberle // // This program is free software: you can redistribute it and/or modify @@ -20,24 +19,24 @@ // along with this program. If not, see . // -#pragma once +#include "common/SequenceOperations.hh" -#include -#include +using std::string; -// Include the fai class from samtools -#include "htslib/faidx.h" +namespace ehunter +{ -namespace ehunter { -class RefGenome { - public: - explicit RefGenome(const std::string& genome_path); - ~RefGenome(); +string lowercaseLowQualityBases(const string& bases, const string& quals, int low_base_quality_cutoff) +{ + string cased_bases = bases; + for (size_t index = 0; index != bases.size(); ++index) + { + if (quals[index] - 33 <= low_base_quality_cutoff) + { + cased_bases[index] = std::tolower(bases[index]); + } + } + return cased_bases; +} - void ExtractSeq(const std::string& region, std::string* sequence) const; - - private: - std::string genome_path_; - faidx_t* fai_ptr_; -}; -} // namespace ehunter +} diff --git a/genotyping/repeat_length.h b/common/SequenceOperations.hh old mode 100644 new mode 100755 similarity index 70% rename from genotyping/repeat_length.h rename to common/SequenceOperations.hh index dc89c1c..588b89e --- a/genotyping/repeat_length.h +++ b/common/SequenceOperations.hh @@ -1,9 +1,8 @@ // // Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. +// Copyright (c) 2018 Illumina, Inc. // // Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw // Concept: Michael Eberle // // This program is free software: you can redistribute it and/or modify @@ -22,10 +21,12 @@ #pragma once -#include +#include + +namespace ehunter +{ + +std::string +lowercaseLowQualityBases(const std::string& bases, const std::string& quals, int low_base_quality_cutoff = 20); -namespace ehunter { -void EstimateRepeatLen(const int num_irrs, const int read_len, - const double hap_depth, int& len_estimate, - int& lower_bound, int& upper_bound); } diff --git a/common/common.h b/common/common.h deleted file mode 100644 index 9edf499..0000000 --- a/common/common.h +++ /dev/null @@ -1,109 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#pragma once - -#include -#include -#include - -namespace ehunter { -enum class ReadType { kSpanning, kFlanking, kInrepeat, kOther }; -const std::map kReadTypeToString = { - {ReadType::kInrepeat, "INREPEAT"}, - {ReadType::kSpanning, "SPANNING"}, - {ReadType::kFlanking, "FLANKING"}, - {ReadType::kOther, "OTHER"}}; - -struct Read { - std::string name; - std::string bases; - std::string quals; -}; - -class AlleleSupport { - public: - AlleleSupport() : num_spanning_(0), num_flanking_(0), num_inrepeat_(0) {} - AlleleSupport(int num_spanning, int num_flanking, int num_inrepeat) - : num_spanning_(num_spanning), - num_flanking_(num_flanking), - num_inrepeat_(num_inrepeat) {} - - int num_spanning() const { return num_spanning_; } - int num_flanking() const { return num_flanking_; } - int num_inrepeat() const { return num_inrepeat_; } - - void set_num_spanning(int num_spanning) { num_spanning_ = num_spanning; } - void set_num_flanking(int num_flanking) { num_flanking_ = num_flanking; } - void set_num_inrepeat(int num_inrepeat) { num_inrepeat_ = num_inrepeat; } - - std::string ToString() const { - return std::to_string(num_spanning_) + "-" + std::to_string(num_flanking_) + - "-" + std::to_string(num_inrepeat_); - } - - bool operator==(const AlleleSupport &rhs) const { - return num_spanning_ == rhs.num_spanning_ && - num_flanking_ == rhs.num_flanking_ && - num_inrepeat_ == rhs.num_inrepeat_; - } - - private: - int num_spanning_; - int num_flanking_; - int num_inrepeat_; -}; - -struct Interval { - Interval() : lower_bound_(-1), upper_bound_(-1) {} - int lower_bound_; - int upper_bound_; - bool operator==(const Interval &rhs) const { - return lower_bound_ == rhs.lower_bound_ && upper_bound_ == rhs.upper_bound_; - } - std::string ToString() const { - return std::to_string(lower_bound_) + "-" + std::to_string(upper_bound_); - } -}; - -struct RepeatAllele { - RepeatAllele(int size, int num_supporting_reads, ReadType type) - : size_(size), num_supporting_reads_(num_supporting_reads), type_(type) {} - RepeatAllele(int size, ReadType type, AlleleSupport support) - : size_(size), - support_(support), - num_supporting_reads_(-1), - type_(type) {} - bool operator==(const RepeatAllele &rhs) const { - return size_ == rhs.size_ && ci_ == rhs.ci_ && support_ == rhs.support_ && - type_ == rhs.type_ && - num_supporting_reads_ == rhs.num_supporting_reads_; - } - int size_; - Interval ci_; - AlleleSupport support_; // TODO: Rename to "consistent". - int num_supporting_reads_; - ReadType type_; -}; - -typedef std::vector RepeatGenotype; -} // namespace ehunter diff --git a/common/genomic_region.cc b/common/genomic_region.cc deleted file mode 100644 index 58fc640..0000000 --- a/common/genomic_region.cc +++ /dev/null @@ -1,130 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include "common/genomic_region.h" - -#include -#include -#include - -#include -#include -#include -#include -#include - -using boost::algorithm::split; -using boost::algorithm::is_any_of; -using boost::lexical_cast; - -using std::vector; -using std::ostream; -using std::istream; -using std::endl; -using std::cerr; -using std::string; - -namespace ehunter { - -Region::Region() : chrom_("chr0"), start_(0), end_(0) {} - -Region::Region(const string &chrom, int64_t start, int64_t end, - const string &label) - : chrom_(chrom), start_(start), end_(end), label_(label) {} - -Region::Region(const string &encoding, const string &label) : label_(label) { - vector components; - split(components, encoding, is_any_of(":-")); - - if (components.size() != 3) { - throw std::logic_error("Unexpected range format: " + encoding); - } - - chrom_ = components[0]; - start_ = lexical_cast(components[1]); - end_ = lexical_cast(components[2]); -} - -bool Region::operator<(const Region &other_region) const { - if (chrom_ != other_region.chrom_) { - return chrom_ < other_region.chrom_; - } - - if (start_ != other_region.start_) { - return start_ < other_region.start_; - } - - return end_ < other_region.end_; -} - -bool Region::Overlaps(const Region &other_region) const { - if (chrom_ != other_region.chrom_) { - return false; - } - - const int64_t left_bound = - start_ > other_region.start_ ? start_ : other_region.start_; - const int64_t right_bound = - end_ < other_region.end_ ? end_ : other_region.end_; - - return left_bound <= right_bound; -} - -// Returns the range extended by flankSize upstream and downstream. -// NOTE: The right boundary of the extended region may stick past chromosome -// end. -Region Region::Extend(int extension_len) const { - const int64_t new_start = - start_ > extension_len ? (start_ - extension_len) : 1; - const int64_t new_end = end_ + extension_len; - return Region(chrom_, new_start, new_end); -} - -const string Region::ToString() const { - std::ostringstream ostrm; - ostrm << *this; - return ostrm.str(); -} - -istream &operator>>(istream &istrm, Region ®ion) { - string encoding; - istrm >> encoding; - region = Region(encoding); - - return istrm; -} - -ostream &operator<<(ostream &ostrm, const Region ®ion) { - ostrm << region.chrom_ << ':' << region.start_; - - if (region.end_ != region.start_) { - ostrm << '-' << region.end_; - } - - if (!region.label_.empty()) { - ostrm << " " << region.label_; - } - - return ostrm; -} - -} \ No newline at end of file diff --git a/common/genomic_region.h b/common/genomic_region.h deleted file mode 100644 index 726e216..0000000 --- a/common/genomic_region.h +++ /dev/null @@ -1,67 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#pragma once - -#include -#include - -namespace ehunter { -class Region { - public: - friend std::istream &operator>>(std::istream &istrm, Region ®ion); - friend std::ostream &operator<<(std::ostream &ostrm, const Region ®ion); - - Region(); - Region(const std::string &chrom, int64_t start, int64_t end, - const std::string &labelStr = std::string()); - Region(const std::string &rangeStr, - const std::string &labelStr = std::string()); - - bool is_set() const { return (chrom_ != "chr0"); } - bool operator<(const Region &other_region) const; - - bool Overlaps(const Region &other_region) const; - - Region Extend(int extension_len) const; - - const std::string &chrom() const { return chrom_; } - int64_t start() const { return start_; } - int64_t end() const { return end_; } - const std::string &label() const { return label_; } - - void set_start(int64_t start) { start_ = start; } - void set_end(int64_t end) { end_ = end; } - void set_label(const std::string &label) { label_ = label; } - - const std::string ToString() const; - - private: - std::string chrom_; - int64_t start_; - int64_t end_; - std::string label_; -}; - -std::istream &operator>>(std::istream &istrm, Region ®ion); -std::ostream &operator<<(std::ostream &ostrm, const Region ®ion); -} // namespace ehunter diff --git a/common/parameters.cc b/common/parameters.cc deleted file mode 100644 index e5e3f86..0000000 --- a/common/parameters.cc +++ /dev/null @@ -1,215 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include "common/parameters.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -using boost::assign::list_of; -using boost::format; -using boost::lexical_cast; -using std::cerr; -using std::endl; -using std::string; -using std::vector; - -namespace po = boost::program_options; - -namespace ehunter { -Outputs::Outputs(const string vcf_path, const string json_path, - const string log_path) { - vcf_.open(vcf_path.c_str()); - if (!vcf_.is_open()) { - throw std::runtime_error("ERROR: Failed to open " + vcf_path + - " for writing: " + strerror(errno)); - } - - json_.open(json_path.c_str()); - if (!json_.is_open()) { - throw std::runtime_error("ERROR: Failed to open " + json_path + - " for writing: " + strerror(errno)); - } - - log_.open(log_path.c_str()); - if (!log_.is_open()) { - throw std::runtime_error("ERROR: Failed to open " + log_path + - " for writing: " + strerror(errno)); - } -} - -static bool CheckIfIndexFileExists(const string &bam_path) { - vector kPossibleIndexExtensions = {".bai", ".csi", ".crai"}; - - for (const string &index_extension : kPossibleIndexExtensions) { - if (boost::filesystem::exists(bam_path + index_extension)) { - return true; - } - } - - return false; -} - -void DieIfOutputPathDoesntExist(const string &output_path_str) { - const boost::filesystem::path output_path(output_path_str); - const boost::filesystem::path output_dir(output_path.parent_path()); - - const bool is_no_dir = output_dir.empty(); - const bool is_existing_dir = boost::filesystem::is_directory(output_dir); - const bool is_valid_fname = - boost::filesystem::portable_posix_name(output_path.filename().string()); - - if ((is_no_dir || is_existing_dir) && is_valid_fname) { - return; - } - - throw std::invalid_argument("ERROR: " + output_path_str + - " is not a valid output path"); -} - -bool Parameters::Load(int argc, char **argv) { - // clang-format off - po::options_description usage("Allowed options"); - usage.add_options() - ("help", "Print help message") - ("version", "Print version number") - ("bam", po::value()->required(), "BAM file") - ("ref-fasta", po::value()->required(), "FASTA file with reference genome") - ("repeat-specs", po::value()->required(), "Directory with repeat-specification files") - ("vcf", po::value()->required(), "Output VCF file") - ("json", po::value()->required(), "Output JSON file") - ("log", po::value()->required(), "Output read alignment file") - ("region-extension-length", po::value()->default_value(1000), "How far from on/off-target regions to search for informative reads") - ("min-score", po::value()->default_value(0.90, "0.90"), "Minimum weighted purity score required to flag a read as an in-repeat read; must be between 0 and 1") - ("min-baseq", po::value()->default_value(20), "Minimum quality of a high-confidence base call") - ("min-anchor-mapq", po::value()->default_value(60), "Minimum MAPQ of a read anchor") - ("skip-unaligned", po::bool_switch()->default_value(false), "Skip unaligned reads when searching for IRRs") - ("read-depth", po::value()->default_value(0.0, "calculated if not set"), "Read depth") - ("sex", po::value()->default_value("female"), "Sex of the sample; must be either male or female") - ("read-length",po::value()->default_value(0, "calculated if not set"), "Read sequence length"); - // clang-format on - - if (argc == 1) { - std::cerr << usage << std::endl; - throw std::invalid_argument(""); - } - - po::variables_map arg_map; - po::store(po::command_line_parser(argc, argv).options(usage).run(), arg_map); - - if (arg_map.count("help")) { - std::cerr << usage << std::endl; - return false; - } - - if (arg_map.count("version")) { - return false; - } - - po::notify(arg_map); - - bam_path_ = arg_map["bam"].as(); - if (!boost::filesystem::exists(bam_path_)) { - throw std::invalid_argument("ERROR: " + bam_path_ + " does not exist"); - } - if (!CheckIfIndexFileExists(bam_path_)) { - throw std::invalid_argument("ERROR: Could not find index file for BAM: " + - bam_path_); - } - - // Extract sample name. - boost::filesystem::path boost_bam_path(bam_path_); - sample_name_ = boost_bam_path.stem().string(); - - genome_path_ = arg_map["ref-fasta"].as(); - if (!boost::filesystem::exists(genome_path_)) { - throw std::invalid_argument("ERROR: " + genome_path_ + " does not exist"); - } - - repeat_specs_path_ = arg_map["repeat-specs"].as(); - if (!boost::filesystem::exists(repeat_specs_path_)) { - throw std::invalid_argument("ERROR: " + repeat_specs_path_ + - " is not a directory"); - } - - json_path_ = arg_map["json"].as(); - DieIfOutputPathDoesntExist(json_path_); - - vcf_path_ = arg_map["vcf"].as(); - DieIfOutputPathDoesntExist(vcf_path_); - - log_path_ = arg_map["log"].as(); - DieIfOutputPathDoesntExist(log_path_); - - region_extension_len_ = arg_map["region-extension-length"].as(); - - min_wp_ = arg_map["min-score"].as(); - if (min_wp_ > 1) { - throw std::invalid_argument("min-score must be less than or equal to 1"); - } - - min_baseq_ = arg_map["min-baseq"].as(); - min_anchor_mapq_ = arg_map["min-anchor-mapq"].as(); - skip_unaligned_ = arg_map["skip-unaligned"].as(); - - if (!arg_map["read-depth"].defaulted()) { - depth_ = arg_map["read-depth"].as(); - - if (depth_ < kSmallestPossibleDepth) { - throw std::invalid_argument("read-depth must be at least " + - std::to_string(kSmallestPossibleDepth)); - } - } - - const string sex_encoding = arg_map["sex"].as(); - if (sex_encoding == "male") { - sex_ = Sex::kMale; - } else if (sex_encoding == "female") { - sex_ = Sex::kFemale; - } else { - throw std::invalid_argument( - "ERROR: " + sex_encoding + - " is invalid for sex; must be either male or female"); - } - - if (!arg_map["read-length"].defaulted()) { - read_len_ = arg_map["read-length"].as(); - - if (read_len_ < minReadLength) { - throw std::invalid_argument("read-length must be at least " + - std::to_string(minReadLength)); - } - } - return true; -} -} // namespace ehunter diff --git a/common/parameters.h b/common/parameters.h deleted file mode 100644 index c628c07..0000000 --- a/common/parameters.h +++ /dev/null @@ -1,102 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#pragma once - -#include -#include -#include - -#include "common/genomic_region.h" -#include "common/repeat_spec.h" -#include "genotyping/short_repeat_genotyper.h" - -namespace ehunter { -class Outputs { - public: - Outputs(const std::string vcf_path, const std::string json_path, - const std::string log_path); - std::ostream &vcf() { return vcf_; } - std::ostream &json() { return json_; } - std::ostream &log() { return log_; } - - private: - std::ofstream vcf_; - std::ofstream json_; - std::ofstream log_; -}; - -class Parameters { - public: - const double kSmallestPossibleDepth = 5.0; - const int minReadLength = 10; - Parameters() - : region_extension_len_(1000), - min_wp_(0.90), - min_baseq_(20), - min_anchor_mapq_(60), - read_len_(0), - depth_(0.0), - sex_(Sex::kFemale), - skip_unaligned_(false) {} - bool Load(int argc, char **argv); - std::string bam_path() const { return bam_path_; } - std::string genome_path() const { return genome_path_; } - int region_extension_len() const { return region_extension_len_; } - double min_wp() const { return min_wp_; } - void set_min_wp(double min_wp) { min_wp_ = min_wp; } - int min_baseq() const { return min_baseq_; } - void set_min_baseq(int min_baseq) { min_baseq_ = min_baseq; } - int min_anchor_mapq() const { return min_anchor_mapq_; } - bool skip_unaligned() const { return skip_unaligned_; } - int read_len() const { return read_len_; }; - void set_read_len(int read_len) { read_len_ = read_len; } - double depth() const { return depth_; } - void set_depth(double depth) { depth_ = depth; } - std::string sample_name() const { return sample_name_; } - std::string repeat_specs_path() const { return repeat_specs_path_; } - std::string vcf_path() const { return vcf_path_; } - std::string json_path() const { return json_path_; } - std::string log_path() const { return log_path_; } - bool depth_is_set() const { return depth_ >= kSmallestPossibleDepth; } - Sex sex() const { return sex_; } - bool read_len_is_set() const { return read_len_ >= minReadLength; } - - private: - std::string bam_path_; - std::string genome_path_; - // Maximum distance from a region to search for relevant reads. - int region_extension_len_; - double min_wp_; - int min_baseq_; - int min_anchor_mapq_; - int read_len_; - double depth_; - Sex sex_; - bool skip_unaligned_; - std::string repeat_specs_path_; - std::string sample_name_; - std::string vcf_path_; - std::string json_path_; - std::string log_path_; -}; -} // namespace ehunter diff --git a/common/ref_genome.cc b/common/ref_genome.cc deleted file mode 100644 index e3ec7a1..0000000 --- a/common/ref_genome.cc +++ /dev/null @@ -1,58 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include "common/ref_genome.h" - -#include -#include -#include - -using std::string; - -namespace ehunter { - -RefGenome::RefGenome(const string& genome_path) : genome_path_(genome_path) { - fai_ptr_ = fai_load(genome_path_.c_str()); -} - -RefGenome::~RefGenome() { fai_destroy(fai_ptr_); } - -// Load reference sequence specified by region. -void RefGenome::ExtractSeq(const string& region, string* sequence) const { - int len; // throwaway... - - char* ref_tmp = fai_fetch(fai_ptr_, region.c_str(), &len); - - if (!ref_tmp || len == -1 || len == -2) { - throw std::runtime_error( - "ERROR: can't extract " + region + " from " + genome_path_ + - "; in particular, chromosome names must match " - "exactly (e.g. \"chr1\" and \"1\" are distinct names)"); - } - - sequence->assign(ref_tmp); - free(ref_tmp); - - std::transform(sequence->begin(), sequence->end(), sequence->begin(), - ::toupper); -} -} // namespace ehunter diff --git a/common/repeat_spec.cc b/common/repeat_spec.cc deleted file mode 100644 index 948f3f4..0000000 --- a/common/repeat_spec.cc +++ /dev/null @@ -1,188 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "common/ref_genome.h" -#include "common/repeat_spec.h" -#include "common/timestamp.h" -#include "purity/purity.h" - -using std::string; -using std::cerr; -using std::endl; -using std::vector; -using std::map; - -using boost::property_tree::ptree; -using boost::lexical_cast; -using boost::algorithm::join; - -namespace ehunter { - -typedef boost::tokenizer> Tokenizer; - -char RepeatSpec::LeftFlankBase() const { - if (left_flank.empty()) { - return '.'; - } - - return left_flank[left_flank.size() - 1]; -} - -RepeatSpec::RepeatSpec(const string &json_path) { - std::ifstream istrm(json_path.c_str()); - - if (!istrm.is_open()) { - throw std::logic_error("Failed to open region JSON file " + json_path); - } - - ptree root_node; - boost::property_tree::read_json(istrm, root_node); - - repeat_id = root_node.get_child("RepeatId").data(); - const string unit = root_node.get_child("RepeatUnit").data(); - - const boost::char_separator slash_separator("/"); - - Tokenizer tokenizer(unit, slash_separator); - units = vector(tokenizer.begin(), tokenizer.end()); - units_shifts = shift_units(units); - - is_common_unit_ = false; - boost::optional commonality_node( - root_node.get_child_optional("CommonUnit")); - if (commonality_node) { - const string is_common_unit_encoding = commonality_node->data(); - if (is_common_unit_encoding == "true") { - is_common_unit_ = true; - } else if (is_common_unit_encoding == "false") { - is_common_unit_ = false; - } else { - throw std::runtime_error( - "ERROR: CommonUnit must be either \"true\" or \"false\"."); - } - } - - const string region_encoding(root_node.get_child("TargetRegion").data()); - target_region = Region(region_encoding); - - boost::optional confusion_node( - root_node.get_child_optional("OffTargetRegions")); - - if (confusion_node) { - offtarget_regions.clear(); - for (const ptree::value_type ®ion_node : *confusion_node) { - assert(region_node.first.empty()); // array elements have no names - offtarget_regions.push_back(Region(region_node.second.data())); - } - } -} - -// Fill out prefix and suffix sequences. -bool LoadFlanks(const string &genome_path, double min_wp, - RepeatSpec *repeat_spec) { - RefGenome ref_genome(genome_path); - // Reference repeat flanks should be at least as long as reads. - const int kFlankLen = 250; - - const Region &repeat_region = repeat_spec->target_region; - - const int64_t left_flank_begin = repeat_region.start() - kFlankLen; - const int64_t left_flank_end = repeat_region.start() - 1; - const int64_t right_flank_begin = repeat_region.end() + 1; - const int64_t right_flank_end = repeat_region.end() + kFlankLen; - - const string left_flank_coords = repeat_region.chrom() + ":" + - lexical_cast(left_flank_begin) + - "-" + lexical_cast(left_flank_end); - const string right_flank_coords = repeat_region.chrom() + ":" + - lexical_cast(right_flank_begin) + - "-" + lexical_cast(right_flank_end); - - ref_genome.ExtractSeq(left_flank_coords, &repeat_spec->left_flank); - ref_genome.ExtractSeq(right_flank_coords, &repeat_spec->right_flank); - - // Output prefix, suffix, repeat, and whole locus. - const string repeat_coords = repeat_region.chrom() + ":" + - lexical_cast(left_flank_end + 1) + "-" + - lexical_cast(right_flank_begin - 1); - ref_genome.ExtractSeq(repeat_coords, &repeat_spec->ref_seq); - - string fake_quals = string('P', repeat_spec->ref_seq.length()); - double ref_repeat_wp = - MatchRepeat(repeat_spec->units, repeat_spec->ref_seq, fake_quals); - ref_repeat_wp /= repeat_spec->ref_seq.length(); - if (ref_repeat_wp < min_wp) { - cerr << "[WARNING: reference sequence of " << repeat_spec->repeat_id - << " repeat (" << repeat_spec->ref_seq - << ") has low weighed purity score of " - << lexical_cast(ref_repeat_wp) << "]"; - } - - return true; -} - -bool LoadRepeatSpecs(const string &specs_path, const string &genome_path, - double min_wp, map *repeat_specs) { - assert(!specs_path.empty()); - - const boost::regex regex_json(".*\\.json$"); - boost::filesystem::path path(specs_path); - boost::filesystem::directory_iterator end_itr; - - for (boost::filesystem::directory_iterator itr(path); itr != end_itr; ++itr) { - if (is_regular_file(itr->status())) { - const string fname = itr->path().filename().string(); - boost::smatch what; - - if (boost::regex_match(fname, what, regex_json)) { - cerr << TimeStamp() << ",[Loading " << fname << "]" << endl; - - const string json_path = itr->path().string(); - RepeatSpec repeat_spec(json_path); - LoadFlanks(genome_path, min_wp, &repeat_spec); - (*repeat_specs)[repeat_spec.repeat_id] = repeat_spec; - } - } - } - - return true; -} - -} // namespace ehunter diff --git a/common/tests/CMakeLists.txt b/common/tests/CMakeLists.txt new file mode 100755 index 0000000..cc01f36 --- /dev/null +++ b/common/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable(SequenceOperationsTest SequenceOperationsTest.cpp) +target_link_libraries(SequenceOperationsTest common gtest_main) +add_test(NAME SequenceOperationsTest COMMAND SequenceOperationsTest) + +add_executable(CountTableTest CountTableTest.cpp) +target_link_libraries(CountTableTest common gtest_main) +add_test(NAME CountTableTest COMMAND CountTableTest) + +add_executable(GenomicRegionTest GenomicRegionTest.cpp) +target_link_libraries(GenomicRegionTest common gtest_main) +add_test(NAME GenomicRegionTest COMMAND GenomicRegionTest) diff --git a/common/tests/CountTableTest.cpp b/common/tests/CountTableTest.cpp new file mode 100755 index 0000000..99a3141 --- /dev/null +++ b/common/tests/CountTableTest.cpp @@ -0,0 +1,59 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "common/CountTable.hh" + +#include "gtest/gtest.h" + +using std::map; +using std::vector; + +using namespace ehunter; + +TEST(InitializationOfCountTable, TypicalCountTable_Initialized) +{ + const map elements_and_counts = { { 1, 2 }, { 3, 5 } }; + CountTable count_table(elements_and_counts); + EXPECT_EQ(2, count_table.countOf(1)); + EXPECT_EQ(0, count_table.countOf(2)); + EXPECT_EQ(5, count_table.countOf(3)); +} + +TEST(ManipulatingCountTable, TypicalOperations_TableUpdated) +{ + CountTable count_table; + count_table.incrementCountOf(4); + EXPECT_EQ(1, count_table.countOf(4)); + + count_table.setCountOf(4, 3); + EXPECT_EQ(3, count_table.countOf(4)); +} + +TEST(ObtainingElementsWithNonzeroCounts, TypicalCountTable_ElementsObtained) +{ + const map elements_and_counts = { { 1, 2 }, { 3, 5 }, { 7, 15 } }; + CountTable count_table(elements_and_counts); + + count_table.setCountOf(3, 0); + + vector expected_elements = { 1, 7 }; + EXPECT_EQ(expected_elements, count_table.getElementsWithNonzeroCounts()); +} diff --git a/common/tests/GenomicRegionTest.cpp b/common/tests/GenomicRegionTest.cpp new file mode 100755 index 0000000..98bdd35 --- /dev/null +++ b/common/tests/GenomicRegionTest.cpp @@ -0,0 +1,93 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "common/GenomicRegion.hh" + +#include + +#include "gtest/gtest.h" + +using std::vector; + +using namespace ehunter; + +TEST(ComputingDistanceBetweenRegions, OverlappingRegions_HaveZeroDistance) +{ + Region region_a("1", 1, 10); + Region region_b("1", 5, 15); + ASSERT_EQ(0, region_a.Distance(region_b)); +} + +TEST(ComputingDistanceBetweenRegions, DistanceBetweenDisjointRegions_Calculated) +{ + Region region_a("1", 50, 70); + Region region_b("1", 0, 20); + ASSERT_EQ(30, region_a.Distance(region_b)); + ASSERT_EQ(30, region_b.Distance(region_a)); +} + +TEST(ComputingDistanceBetweenRegions, RegionsOnDifferentChromosomes_HaveMaximalDistance) +{ + Region region_a("1", 50, 70); + Region region_b("2", 0, 20); + ASSERT_EQ(std::numeric_limits::max(), region_a.Distance(region_b)); +} + +TEST(MergingRegions, OverlappingSortedRegions_Merged) +{ + vector regions = { Region("1", 10, 20), Region("1", 15, 25), Region("1", 20, 35) }; + regions = merge(regions); + vector expected_regions = { Region("1", 10, 35) }; + ASSERT_EQ(expected_regions, regions); +} + +TEST(MergingRegions, OverlappingUnsortedRegions_Merged) +{ + vector regions = { Region("1", 15, 25), Region("1", 10, 20), Region("1", 20, 35) }; + regions = merge(regions); + vector expected_regions = { Region("1", 10, 35) }; + ASSERT_EQ(expected_regions, regions); +} + +TEST(MergingRegions, DisjointRegions_Merged) +{ + vector regions = { Region("1", 15, 25), Region("2", 10, 20), Region("1", 20, 35) }; + regions = merge(regions); + vector expected_regions = { Region("1", 15, 35), Region("2", 10, 20) }; + ASSERT_EQ(expected_regions, regions); +} + +TEST(MergingRegions, ProximalRegions_Merged) +{ + vector regions = { Region("1", 200, 250), Region("1", 500, 550), Region("1", 0, 10), + Region("1", 1100, 1200), Region("2", 1100, 1200) }; + regions = merge(regions); + vector expected_regions = { Region("1", 0, 550), Region("1", 1100, 1200), Region("2", 1100, 1200) }; + ASSERT_EQ(expected_regions, regions); +} + +TEST(MergingRegions, IncludedRegions_Merged) +{ + vector regions = { Region("1", 100, 200), Region("1", 90, 300) }; + regions = merge(regions); + vector expected_regions = { Region("1", 90, 300) }; + ASSERT_EQ(expected_regions, regions); +} \ No newline at end of file diff --git a/common/tests/SequenceOperationsTest.cpp b/common/tests/SequenceOperationsTest.cpp new file mode 100755 index 0000000..569c2f0 --- /dev/null +++ b/common/tests/SequenceOperationsTest.cpp @@ -0,0 +1,35 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "common/SequenceOperations.hh" + +#include "gtest/gtest.h" + +using std::string; + +using namespace ehunter; + +TEST(LowercasingLowQualityBases, TypicalRead_LowQualityBasesLowercased) +{ + const string bases = "ATCGATCG"; + const string quals = "?#?##?##"; + ASSERT_EQ("AtCgaTcg", lowercaseLowQualityBases(bases, quals)); +} diff --git a/data/examples/hg19/README.txt b/data/examples/hg19/README.txt deleted file mode 100644 index a16bf9b..0000000 --- a/data/examples/hg19/README.txt +++ /dev/null @@ -1,16 +0,0 @@ - -Expansion Hunter can be run on the example BAMlet using the following command -after replacing "hg19_ref.fa" with a path to hg19 reference FASTA file and -adjusting paths in the remaining command line arguments as approprite. - -Note that "read-depth" must be specified when running Expansion Hunter on an -incomplete BAM file. - -ExpansionHunter \ - --bam bamlets/bamlet.bam \ - --ref-fasta hg19_ref.fa \ - --repeat-specs ../../repeat-specs/hg19/ \ - --vcf output/bamlet.vcf \ - --json output/bamlet.json \ - --log output/bamlet.log \ - --read-depth 30 diff --git a/data/examples/hg19/bamlets/bamlet.bam b/data/examples/hg19/bamlets/bamlet.bam deleted file mode 100644 index 5bd2b6ee79a9c5fa63eab46d769ca71a74b01f51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 489606 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%0APC^}6N1Eo%! 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
dQ8Nd6?<}ue|yUt)ZoMb&FM<%8- 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

Nx7FWH!flhy=IKr?s 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 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103MM%{b8Z zdJ(gK)IlBU9O(mnpbyLe2ROh14sd`29N+*4IKY8_?m*j1c(euajZn8HZu=IueT+B$ zlKUCgJ&qS2?~nF9o?Q1r*`K)O#H^hUR!Q1w? zZu?qCymoB5(&rD^$UEf~6XH!JWi8(*&pbpg$ zeV`BYfjQs+2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K0 z2ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`2 z9N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8 zaDW3G-~b0WzyS_$fCIPAfj>X_>fROQ{}C6}K_8d{4sd`29N+*4IKTl8aDW3G-~b0W zzyS_$fCC)h00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h z00%h00S<70103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70 z103K02ROh14sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCC)h00%h00S<70103K02ROh1 q4sd`29N+*4IKTl8aDW3G-~b0WzyS_$fCJ0Nf%7L$FVvss -##INFO= -##INFO= -##INFO= -##INFO= -##INFO= -##FORMAT= -##FORMAT= -##FORMAT= -##FORMAT= -##FORMAT= -##FORMAT= -##FORMAT= -##ALT= -##ALT= -##ALT= -##ALT= -##ALT= -##ALT= -##ALT= -##ALT= -##ALT= -##ALT= -##ALT= -##ALT= -##ALT= -##ALT= -#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT bamlet -chr12 7045879 . C , . PASS SVTYPE=STR;END=7045936;REF=19;RL=57;RU=CAG;REPID=ATN1 GT:SO:CN:CI:AD_SP:AD_FL:AD_IR 1/2:SPANNING/SPANNING:12/14:12-12/14-14:13/22:9/9:0/0 -chr14 92537353 . C . PASS SVTYPE=STR;END=92537386;REF=11;RL=33;RU=GCT;REPID=ATXN3 GT:SO:CN:CI:AD_SP:AD_FL:AD_IR 1/1:SPANNING/SPANNING:24/24:24-24/24-24:10/10:15/15:0/0 -chr19 46273462 . C , . PASS SVTYPE=STR;END=46273522;REF=20;RL=60;RU=CAG;REPID=DMPK GT:SO:CN:CI:AD_SP:AD_FL:AD_IR 1/2:SPANNING/SPANNING:5/11:5-5/11-11:20/11:8/13:0/0 -chr4 3076603 . C , . PASS SVTYPE=STR;END=3076660;REF=19;RL=57;RU=CAG;REPID=HTT GT:SO:CN:CI:AD_SP:AD_FL:AD_IR 1/2:SPANNING/SPANNING:10/20:10-10/20-20:5/12:14/18:0/0 -chr6 16327864 . G . PASS SVTYPE=STR;END=16327954;REF=30;RL=90;RU=TGC;REPID=ATXN1 GT:SO:CN:CI:AD_SP:AD_FL:AD_IR 0/1:SPANNING/SPANNING:30/37:30-30/37-37:10/4:42/42:0/0 -chr9 27573526 . C , . PASS SVTYPE=STR;END=27573544;REF=3;RL=18;RU=GGCCCC;REPID=C9ORF72 GT:SO:CN:CI:AD_SP:AD_FL:AD_IR 1/2:SPANNING/SPANNING:2/8:2-2/8-8:9/6:5/7:0/0 -chr9 71652202 . A . PASS SVTYPE=STR;END=71652220;REF=6;RL=18;RU=GAA;REPID=FXN GT:SO:CN:CI:AD_SP:AD_FL:AD_IR 1/1:SPANNING/SPANNING:9/9:9-9/9-9:10/10:4/4:0/0 -chrX 66765158 . T , . PASS SVTYPE=STR;END=66765227;REF=23;RL=69;RU=GCA;REPID=AR GT:SO:CN:CI:AD_SP:AD_FL:AD_IR 1/2:SPANNING/SPANNING:24/25:24-24/25-25:10/5:20/20:0/0 -chrX 146993568 . G , . PASS SVTYPE=STR;END=146993628;REF=20;RL=60;RU=CGG;REPID=FMR1 GT:SO:CN:CI:AD_SP:AD_FL:AD_IR 1/2:SPANNING/SPANNING:23/30:23-23/30-30:5/5:4/6:0/0 diff --git a/data/repeat-specs/grch37/AR.json b/data/repeat-specs/grch37/AR.json deleted file mode 100644 index d68d169..0000000 --- a/data/repeat-specs/grch37/AR.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "AR", - "RepeatUnit": "GCA", - "TargetRegion": "X:66765159-66765227" -} diff --git a/data/repeat-specs/grch37/ATN1.json b/data/repeat-specs/grch37/ATN1.json deleted file mode 100644 index e2b67f4..0000000 --- a/data/repeat-specs/grch37/ATN1.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATN1", - "RepeatUnit": "CAG", - "TargetRegion": "12:7045880-7045936" -} diff --git a/data/repeat-specs/grch37/ATXN1.json b/data/repeat-specs/grch37/ATXN1.json deleted file mode 100644 index c19ff3b..0000000 --- a/data/repeat-specs/grch37/ATXN1.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN1", - "RepeatUnit": "TGC", - "TargetRegion": "6:16327865-16327954" -} diff --git a/data/repeat-specs/grch37/ATXN10.json b/data/repeat-specs/grch37/ATXN10.json deleted file mode 100755 index 1db6195..0000000 --- a/data/repeat-specs/grch37/ATXN10.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN10", - "RepeatUnit": "ATTCT", - "TargetRegion": "22:46191235-46191304" -} diff --git a/data/repeat-specs/grch37/ATXN2.json b/data/repeat-specs/grch37/ATXN2.json deleted file mode 100755 index 63a92cb..0000000 --- a/data/repeat-specs/grch37/ATXN2.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN2", - "RepeatUnit": "GCT", - "TargetRegion": "12:112036754-112036822" -} diff --git a/data/repeat-specs/grch37/ATXN3.json b/data/repeat-specs/grch37/ATXN3.json deleted file mode 100644 index bc04afa..0000000 --- a/data/repeat-specs/grch37/ATXN3.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN3", - "RepeatUnit": "GCT", - "TargetRegion": "14:92537354-92537386" -} diff --git a/data/repeat-specs/grch37/ATXN7.json b/data/repeat-specs/grch37/ATXN7.json deleted file mode 100755 index 49a5c10..0000000 --- a/data/repeat-specs/grch37/ATXN7.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN7", - "RepeatUnit": "GCA", - "TargetRegion": "3:63898361-63898390" -} diff --git a/data/repeat-specs/grch37/C9ORF72.json b/data/repeat-specs/grch37/C9ORF72.json deleted file mode 100644 index c95a6ca..0000000 --- a/data/repeat-specs/grch37/C9ORF72.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "OffTargetRegions": [ - "1:150552238-150552273", - "11:910821-910969", - "11:2905960-2906190", - "11:44748502-44748739", - "12:54476278-54476439", - "12:128751638-128751649", - "13:107569867-107570000", - "16:819162-819226", - "16:49526377-49526380", - "18:74203223-74203243", - "19:878803-878954", - "19:39897404-39897410", - "2:240659782-240660010", - "20:388475-388656", - "20:60640211-60640346", - "22:51066608-51066626", - "3:49591993-49592173", - "3:50161662-50161732", - "5:10761494-10761495", - "5:176797951-176798133", - "6:33601522-33601525", - "6:109804703-109804748", - "9:132331488-132331496", - "9:132428311-132428437", - "X:9754276-9754424", - "X:49644488-49644585", - "X:53652706-53652891", - "X:153569931-153569996", - "X:153618748-153618918" - ], - "RepeatId": "C9ORF72", - "RepeatUnit": "GGCCCC", - "TargetRegion": "9:27573527-27573544" -} diff --git a/data/repeat-specs/grch37/CACNA1A.json b/data/repeat-specs/grch37/CACNA1A.json deleted file mode 100755 index b244f0b..0000000 --- a/data/repeat-specs/grch37/CACNA1A.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "CACNA1A", - "RepeatUnit": "CTG", - "TargetRegion": "19:13318673-13318711" -} diff --git a/data/repeat-specs/grch37/CBL.json b/data/repeat-specs/grch37/CBL.json deleted file mode 100755 index 17f8186..0000000 --- a/data/repeat-specs/grch37/CBL.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "CBL", - "RepeatUnit": "CGG", - "TargetRegion": "11:119077000-119077032" -} diff --git a/data/repeat-specs/grch37/CSTB.json b/data/repeat-specs/grch37/CSTB.json deleted file mode 100755 index 338051f..0000000 --- a/data/repeat-specs/grch37/CSTB.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "CSTB", - "RepeatUnit": "CGCGGGGCGGGG", - "TargetRegion": "21:45196325-45196360" -} diff --git a/data/repeat-specs/grch37/DMPK.json b/data/repeat-specs/grch37/DMPK.json deleted file mode 100644 index fd48cdf..0000000 --- a/data/repeat-specs/grch37/DMPK.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "DMPK", - "RepeatUnit": "CAG", - "TargetRegion": "19:46273463-46273522" -} diff --git a/data/repeat-specs/grch37/FMR1.json b/data/repeat-specs/grch37/FMR1.json deleted file mode 100644 index 9759761..0000000 --- a/data/repeat-specs/grch37/FMR1.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "OffTargetRegions": [ - "10:101295193-101295194", - "12:7781291-7781350", - "12:125052155-125052156", - "16:25703614-25703635", - "16:28074517-28074518", - "17:30814025-30814026", - "17:64298468-64298469", - "19:2015525-2015526", - "2:87141541-87141618", - "2:92230910-92230911", - "2:211036021-211036032", - "2:225449879-225449880", - "20:30865501-30865516", - "5:443335-443364", - "7:20824940-20824941", - "7:100271438-100271439", - "7:104654598-104654599", - "7:143059854-143059855", - "9:100616696-100616697", - "X:20009037-20009046" - ], - "RepeatId": "FMR1", - "RepeatUnit": "CGG", - "TargetRegion": "X:146993569-146993628" -} diff --git a/data/repeat-specs/grch37/FXN.json b/data/repeat-specs/grch37/FXN.json deleted file mode 100644 index bb6d743..0000000 --- a/data/repeat-specs/grch37/FXN.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "FXN", - "RepeatUnit": "GAA", - "TargetRegion": "9:71652203-71652220" -} diff --git a/data/repeat-specs/grch37/HTT.json b/data/repeat-specs/grch37/HTT.json deleted file mode 100644 index 1256d36..0000000 --- a/data/repeat-specs/grch37/HTT.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "HTT", - "RepeatUnit": "CAG", - "TargetRegion": "4:3076604-3076660" -} diff --git a/data/repeat-specs/grch37/JPH3.json b/data/repeat-specs/grch37/JPH3.json deleted file mode 100755 index 19206a8..0000000 --- a/data/repeat-specs/grch37/JPH3.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "JPH3", - "RepeatUnit": "CTG", - "TargetRegion": "16:87637894-87637935" -} diff --git a/data/repeat-specs/grch37/PPP2R2B.json b/data/repeat-specs/grch37/PPP2R2B.json deleted file mode 100755 index 3c7cbfe..0000000 --- a/data/repeat-specs/grch37/PPP2R2B.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "PPP2R2B", - "RepeatUnit": "GCT", - "TargetRegion": "5:146258291-146258320" -} diff --git a/data/repeat-specs/grch38/AR.json b/data/repeat-specs/grch38/AR.json deleted file mode 100644 index f756081..0000000 --- a/data/repeat-specs/grch38/AR.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "AR", - "RepeatUnit": "GCA", - "TargetRegion": "X:67545317-67545385" -} diff --git a/data/repeat-specs/grch38/ATN1.json b/data/repeat-specs/grch38/ATN1.json deleted file mode 100644 index 0f8d7f2..0000000 --- a/data/repeat-specs/grch38/ATN1.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATN1", - "RepeatUnit": "CAG", - "TargetRegion": "12:6936717-6936773" -} diff --git a/data/repeat-specs/grch38/ATXN1.json b/data/repeat-specs/grch38/ATXN1.json deleted file mode 100644 index 80607a3..0000000 --- a/data/repeat-specs/grch38/ATXN1.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN1", - "RepeatUnit": "TGC", - "TargetRegion": "6:16327634-16327723" -} diff --git a/data/repeat-specs/grch38/ATXN10.json b/data/repeat-specs/grch38/ATXN10.json deleted file mode 100755 index 5b1f53e..0000000 --- a/data/repeat-specs/grch38/ATXN10.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN10", - "RepeatUnit": "ATTCT", - "TargetRegion": "22:45795355-45795424" -} diff --git a/data/repeat-specs/grch38/ATXN2.json b/data/repeat-specs/grch38/ATXN2.json deleted file mode 100755 index 4f8ca82..0000000 --- a/data/repeat-specs/grch38/ATXN2.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN2", - "RepeatUnit": "GCT", - "TargetRegion": "12:111598950-111599018" -} diff --git a/data/repeat-specs/grch38/ATXN3.json b/data/repeat-specs/grch38/ATXN3.json deleted file mode 100644 index c1d697b..0000000 --- a/data/repeat-specs/grch38/ATXN3.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN3", - "RepeatUnit": "GCT", - "TargetRegion": "14:92071010-92071042" -} diff --git a/data/repeat-specs/grch38/ATXN7.json b/data/repeat-specs/grch38/ATXN7.json deleted file mode 100755 index fbdc288..0000000 --- a/data/repeat-specs/grch38/ATXN7.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN7", - "RepeatUnit": "GCA", - "TargetRegion": "3:63912685-63912714" -} diff --git a/data/repeat-specs/grch38/C9ORF72.json b/data/repeat-specs/grch38/C9ORF72.json deleted file mode 100644 index 86ee03f..0000000 --- a/data/repeat-specs/grch38/C9ORF72.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "OffTargetRegions": [ - "1:8423621-8424123", - "1:30908134-30908774", - "1:150579391-150580033", - "10:930905-931660", - "10:24952417-24952917", - "10:43304793-43305293", - "10:46336799-46337407", - "10:46816084-46816694", - "10:100999604-101000210", - "11:910566-911217", - "11:2884463-2885264", - "11:44726687-44727485", - "11:67372280-67372872", - "12:459655-460155", - "12:54082198-54082983", - "12:128266837-128267355", - "13:20987999-20988595", - "13:25468390-25469012", - "13:106917208-106917872", - "14:32938887-32939513", - "14:105679948-105680448", - "15:30223044-30223669", - "16:768800-769576", - "16:1614060-1614704", - "16:3134792-3135352", - "16:87602167-87602667", - "17:10198366-10198994", - "17:68205353-68205991", - "17:81891216-81891716", - "18:54223898-54224704", - "19:613300-613854", - "19:878543-879249", - "19:2808203-2808704", - "19:17947550-17948080", - "19:39406515-39407140", - "19:50568088-50568733", - "19:52296835-52297335", - "2:3592014-3592538", - "2:25129075-25129575", - "2:168247029-168247668", - "2:234496869-234497369", - "2:234951699-234952200", - "2:236237108-236237609", - "2:239737825-239738572", - "20:407682-408343", - "20:62064994-62065670", - "21:45286832-45287440", - "22:39994754-39995382", - "22:49117030-49117530", - "22:50627811-50628453", - "3:46845816-46846372", - "3:47578061-47578713", - "3:49554284-49555050", - "3:50124040-50124697", - "3:51706745-51707245", - "3:128497690-128498194", - "3:184379812-184380428", - "4:151408888-151409388", - "5:10761009-10761635", - "5:146458825-146459326", - "5:172454308-172454808", - "5:177370695-177371467", - "6:33633375-33634093", - "6:43021419-43022045", - "6:46490763-46491263", - "6:109483279-109483938", - "8:141308351-141308989", - "9:83817588-83818088", - "9:129568834-129569572", - "9:129665779-129666418", - "X:9785975-9786751", - "X:49879637-49880326", - "X:53625583-53626227", - "X:154341222-154341987", - "X:154390158-154390883" - ], - "RepeatId": "C9ORF72", - "RepeatUnit": "GGCCCC", - "TargetRegion": "9:27573529-27573546" -} diff --git a/data/repeat-specs/grch38/CACNA1A.json b/data/repeat-specs/grch38/CACNA1A.json deleted file mode 100755 index 273340a..0000000 --- a/data/repeat-specs/grch38/CACNA1A.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "CACNA1A", - "RepeatUnit": "CTG", - "TargetRegion": "19:13207859-13207897" -} diff --git a/data/repeat-specs/grch38/CBL.json b/data/repeat-specs/grch38/CBL.json deleted file mode 100755 index 091d19f..0000000 --- a/data/repeat-specs/grch38/CBL.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "CBL", - "RepeatUnit": "CGG", - "TargetRegion": "11:119206290-119206322" -} diff --git a/data/repeat-specs/grch38/CSTB.json b/data/repeat-specs/grch38/CSTB.json deleted file mode 100755 index 86fda95..0000000 --- a/data/repeat-specs/grch38/CSTB.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "CSTB", - "RepeatUnit": "CGCGGGGCGGGG", - "TargetRegion": "21:43776444-43776479" -} diff --git a/data/repeat-specs/grch38/DMPK.json b/data/repeat-specs/grch38/DMPK.json deleted file mode 100644 index 2db0aff..0000000 --- a/data/repeat-specs/grch38/DMPK.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "DMPK", - "RepeatUnit": "CAG", - "TargetRegion": "19:45770205-45770264" -} diff --git a/data/repeat-specs/grch38/FMR1.json b/data/repeat-specs/grch38/FMR1.json deleted file mode 100644 index b881223..0000000 --- a/data/repeat-specs/grch38/FMR1.json +++ /dev/null @@ -1,221 +0,0 @@ -{ - "OffTargetRegions": [ - "1:3068801-3069410", - "1:21176192-21176850", - "1:22142747-22143526", - "1:26695804-26696436", - "1:32964054-32964646", - "1:38046254-38047002", - "1:43979083-43979731", - "1:50418726-50419472", - "1:53738909-53739574", - "1:108559854-108560513", - "1:120069050-120069669", - "1:120723874-120724492", - "1:146228425-146229045", - "1:149390545-149391206", - "1:154558233-154558862", - "1:166166597-166167097", - "1:208244049-208244696", - "1:244048774-244049274", - "1:246931115-246931739", - "10:15370500-15371495", - "10:50991054-50991697", - "10:75400964-75401593", - "10:96586507-96587141", - "10:99535057-99535844", - "10:131900768-131901422", - "10:132396902-132397563", - "11:535031-535612", - "11:13010217-13010825", - "11:44309366-44309993", - "11:119206037-119206672", - "12:1593781-1594552", - "12:32399143-32399783", - "12:45729326-45729915", - "12:55743147-55743788", - "12:64404091-64404725", - "12:69239355-69239905", - "12:112381710-112382586", - "12:123533343-123533981", - "13:36919926-36920587", - "14:28767201-28767848", - "14:33800221-33800846", - "14:67515149-67515807", - "14:69572824-69573477", - "14:102592573-102593302", - "15:24847863-24848492", - "15:51737292-51737920", - "15:88256091-88256591", - "16:2178674-2179293", - "16:24729307-24730163", - "16:25691936-25692619", - "16:28062829-28063460", - "16:49855134-49855768", - "16:55478903-55479533", - "16:73058439-73059091", - "16:79598818-79599460", - "16:85168883-85169517", - "16:88570364-88570870", - "17:2593209-2593916", - "17:7885052-7885670", - "17:26774259-26774914", - "17:28644586-28645198", - "17:44219715-44220215", - "17:45241688-45242324", - "17:47693726-47694343", - "17:62065115-62065757", - "17:66301955-66302705", - "17:75588818-75589447", - "18:5891122-5891766", - "18:12896735-12897356", - "18:28176797-28177449", - "18:31942936-31943436", - "18:33578204-33578845", - "18:47246693-47247323", - "18:57435464-57436097", - "19:1285832-1286476", - "19:1407367-1408019", - "19:1567733-1568394", - "19:2015164-2015946", - "19:2163772-2164421", - "19:2307771-2308540", - "19:14495662-14496299", - "19:15332244-15332867", - "19:19406197-19406820", - "19:33796449-33797068", - "19:36140744-36141396", - "19:36309136-36309673", - "19:42116613-42117226", - "19:42241987-42242612", - "19:42253492-42254110", - "19:45496699-45497341", - "19:45972994-45973745", - "19:49072291-49072904", - "2:37156531-37157166", - "2:47905272-47905864", - "2:50346525-50347267", - "2:72148268-72148905", - "2:73269202-73269839", - "2:85133320-85133967", - "2:86914036-86915119", - "2:90324440-90325070", - "2:90325129-90325838", - "2:91809551-91810294", - "2:92042461-92043192", - "2:104855036-104856469", - "2:109760019-109760924", - "2:109794205-109794856", - "2:110610685-110611442", - "2:117870707-117871322", - "2:160493197-160493697", - "2:202376258-202376900", - "2:210170919-210171567", - "2:219627270-219627917", - "2:224584788-224585433", - "2:231037451-231038211", - "20:10034652-10035260", - "20:32277443-32278091", - "20:35226391-35227019", - "20:57709538-57710167", - "21:15730044-15730612", - "21:31732026-31732607", - "21:33554863-33555470", - "21:34614576-34615198", - "21:37072925-37073548", - "21:42218962-42219628", - "22:20131542-20132067", - "22:20858296-20858923", - "22:28883459-28884052", - "22:37983425-37984043", - "22:47379028-47379724", - "22:50474704-50475344", - "3:12287535-12288151", - "3:15859308-15859969", - "3:18425090-18425592", - "3:19947013-19947631", - "3:28575592-28576219", - "3:51390996-51391577", - "3:129605016-129605681", - "3:158105431-158106067", - "3:177197310-177197932", - "3:194583637-194584137", - "4:3074689-3075409", - "4:15003614-15004273", - "4:20251746-20252399", - "4:74932809-74933428", - "4:104491109-104491609", - "4:140153858-140154359", - "4:146639046-146639684", - "5:442842-443512", - "5:6448351-6449058", - "5:14144643-14145253", - "5:45695839-45696469", - "5:61332421-61333055", - "5:93583956-93584562", - "5:108380750-108381400", - "5:134371097-134371734", - "5:141878163-141878822", - "5:171419846-171420495", - "5:177554235-177554890", - "5:180291709-180292348", - "6:1389781-1390413", - "6:7107297-7107932", - "6:13711135-13711716", - "6:45422374-45423021", - "6:46490941-46491586", - "6:98834722-98835357", - "6:114342167-114342817", - "6:116680741-116681370", - "6:117906805-117907451", - "6:149317154-149317781", - "6:149317786-149318327", - "6:150865348-150865972", - "6:156778591-156779239", - "7:18086649-18087297", - "7:20785041-20785780", - "7:27173497-27174130", - "7:32891625-32892251", - "7:42237600-42238236", - "7:50793143-50793767", - "7:55887235-55888138", - "7:63114055-63114797", - "7:64181750-64182417", - "7:75092522-75093173", - "7:77536830-77537470", - "7:97005857-97006507", - "7:99143641-99144272", - "7:100673443-100674084", - "7:100693981-100694615", - "7:105013777-105014419", - "7:143362397-143363025", - "7:151086294-151086936", - "8:37698039-37698670", - "8:60680069-60680652", - "8:65841588-65842212", - "8:88327246-88327873", - "8:104588709-104589361", - "8:139702697-139703331", - "8:144461992-144462624", - "9:27572829-27573455", - "9:34957850-34958490", - "9:63450117-63450878", - "9:77019891-77020661", - "9:97854153-97854814", - "9:120714015-120714592", - "9:122126508-122127093", - "9:124869205-124869705", - "9:128841199-128841699", - "9:130579254-130579892", - "X:19990568-19991309", - "X:21374363-21374866", - "X:48957465-48958121", - "X:68912864-68913485", - "X:100409332-100409962", - "X:126165700-126166354", - "X:150983149-150983796" - ], - "RepeatId": "FMR1", - "RepeatUnit": "CGG", - "TargetRegion": "X:147912051-147912110" -} diff --git a/data/repeat-specs/grch38/FXN.json b/data/repeat-specs/grch38/FXN.json deleted file mode 100644 index 199a18d..0000000 --- a/data/repeat-specs/grch38/FXN.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "FXN", - "RepeatUnit": "GAA", - "TargetRegion": "9:69037287-69037304" -} diff --git a/data/repeat-specs/grch38/HTT.json b/data/repeat-specs/grch38/HTT.json deleted file mode 100644 index 21c5329..0000000 --- a/data/repeat-specs/grch38/HTT.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "HTT", - "RepeatUnit": "CAG", - "TargetRegion": "4:3074877-3074933" -} diff --git a/data/repeat-specs/grch38/JPH3.json b/data/repeat-specs/grch38/JPH3.json deleted file mode 100755 index 10c807b..0000000 --- a/data/repeat-specs/grch38/JPH3.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "JPH3", - "RepeatUnit": "CTG", - "TargetRegion": "16:87604288-87604329" -} diff --git a/data/repeat-specs/grch38/PPP2R2B.json b/data/repeat-specs/grch38/PPP2R2B.json deleted file mode 100755 index 61c4ce3..0000000 --- a/data/repeat-specs/grch38/PPP2R2B.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "PPP2R2B", - "RepeatUnit": "GCT", - "TargetRegion": "5:146878728-146878757" -} diff --git a/data/repeat-specs/hg19/AR.json b/data/repeat-specs/hg19/AR.json deleted file mode 100644 index bfdaea0..0000000 --- a/data/repeat-specs/hg19/AR.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "AR", - "RepeatUnit": "GCA", - "TargetRegion": "chrX:66765159-66765227" -} diff --git a/data/repeat-specs/hg19/ATN1.json b/data/repeat-specs/hg19/ATN1.json deleted file mode 100644 index cb503ee..0000000 --- a/data/repeat-specs/hg19/ATN1.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATN1", - "RepeatUnit": "CAG", - "TargetRegion": "chr12:7045880-7045936" -} diff --git a/data/repeat-specs/hg19/ATXN1.json b/data/repeat-specs/hg19/ATXN1.json deleted file mode 100644 index a3df4cb..0000000 --- a/data/repeat-specs/hg19/ATXN1.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN1", - "RepeatUnit": "TGC", - "TargetRegion": "chr6:16327865-16327954" -} diff --git a/data/repeat-specs/hg19/ATXN10.json b/data/repeat-specs/hg19/ATXN10.json deleted file mode 100755 index ee149b8..0000000 --- a/data/repeat-specs/hg19/ATXN10.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN10", - "RepeatUnit": "ATTCT", - "TargetRegion": "chr22:46191235-46191304" -} diff --git a/data/repeat-specs/hg19/ATXN2.json b/data/repeat-specs/hg19/ATXN2.json deleted file mode 100755 index 695b7ef..0000000 --- a/data/repeat-specs/hg19/ATXN2.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN2", - "RepeatUnit": "GCT", - "TargetRegion": "chr12:112036754-112036822" -} diff --git a/data/repeat-specs/hg19/ATXN3.json b/data/repeat-specs/hg19/ATXN3.json deleted file mode 100644 index 09bbf06..0000000 --- a/data/repeat-specs/hg19/ATXN3.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN3", - "RepeatUnit": "GCT", - "TargetRegion": "chr14:92537354-92537386" -} diff --git a/data/repeat-specs/hg19/ATXN7.json b/data/repeat-specs/hg19/ATXN7.json deleted file mode 100755 index b8ddd97..0000000 --- a/data/repeat-specs/hg19/ATXN7.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN7", - "RepeatUnit": "GCA", - "TargetRegion": "chr3:63898361-63898390" -} diff --git a/data/repeat-specs/hg19/C9ORF72.json b/data/repeat-specs/hg19/C9ORF72.json deleted file mode 100644 index 8ac0778..0000000 --- a/data/repeat-specs/hg19/C9ORF72.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "OffTargetRegions": [ - "chr1:150552238-150552273", - "chr11:910821-910969", - "chr11:2905960-2906190", - "chr11:44748502-44748739", - "chr12:54476278-54476439", - "chr12:128751638-128751649", - "chr13:107569867-107570000", - "chr16:819162-819226", - "chr16:49526377-49526380", - "chr18:74203223-74203243", - "chr19:878803-878954", - "chr19:39897404-39897410", - "chr2:240659782-240660010", - "chr20:388475-388656", - "chr20:60640211-60640346", - "chr22:51066608-51066626", - "chr3:49591993-49592173", - "chr3:50161662-50161732", - "chr5:10761494-10761495", - "chr5:176797951-176798133", - "chr6:33601522-33601525", - "chr6:109804703-109804748", - "chr9:132331488-132331496", - "chr9:132428311-132428437", - "chrX:9754276-9754424", - "chrX:49644488-49644585", - "chrX:53652706-53652891", - "chrX:153569931-153569996", - "chrX:153618748-153618918" - ], - "RepeatId": "C9ORF72", - "RepeatUnit": "GGCCCC", - "TargetRegion": "chr9:27573527-27573544" -} diff --git a/data/repeat-specs/hg19/CACNA1A.json b/data/repeat-specs/hg19/CACNA1A.json deleted file mode 100755 index 1c8316e..0000000 --- a/data/repeat-specs/hg19/CACNA1A.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "CACNA1A", - "RepeatUnit": "CTG", - "TargetRegion": "chr19:13318673-13318711" -} diff --git a/data/repeat-specs/hg19/CBL.json b/data/repeat-specs/hg19/CBL.json deleted file mode 100755 index 16e8c60..0000000 --- a/data/repeat-specs/hg19/CBL.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "CBL", - "RepeatUnit": "CGG", - "TargetRegion": "chr11:119077000-119077032" -} diff --git a/data/repeat-specs/hg19/CSTB.json b/data/repeat-specs/hg19/CSTB.json deleted file mode 100755 index df8cf19..0000000 --- a/data/repeat-specs/hg19/CSTB.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "CSTB", - "RepeatUnit": "CGCGGGGCGGGG", - "TargetRegion": "chr21:45196325-45196360" -} diff --git a/data/repeat-specs/hg19/DMPK.json b/data/repeat-specs/hg19/DMPK.json deleted file mode 100644 index 31169c0..0000000 --- a/data/repeat-specs/hg19/DMPK.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "DMPK", - "RepeatUnit": "CAG", - "TargetRegion": "chr19:46273463-46273522" -} diff --git a/data/repeat-specs/hg19/FMR1.json b/data/repeat-specs/hg19/FMR1.json deleted file mode 100644 index 346cca7..0000000 --- a/data/repeat-specs/hg19/FMR1.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "OffTargetRegions": [ - "chr10:101295193-101295194", - "chr12:7781291-7781350", - "chr12:125052155-125052156", - "chr16:25703614-25703635", - "chr16:28074517-28074518", - "chr17:30814025-30814026", - "chr17:64298468-64298469", - "chr19:2015525-2015526", - "chr2:87141541-87141618", - "chr2:92230910-92230911", - "chr2:211036021-211036032", - "chr2:225449879-225449880", - "chr20:30865501-30865516", - "chr5:443335-443364", - "chr7:20824940-20824941", - "chr7:100271438-100271439", - "chr7:104654598-104654599", - "chr7:143059854-143059855", - "chr9:100616696-100616697", - "chrX:20009037-20009046" - ], - "RepeatId": "FMR1", - "RepeatUnit": "CGG", - "TargetRegion": "chrX:146993569-146993628" -} \ No newline at end of file diff --git a/data/repeat-specs/hg19/FXN.json b/data/repeat-specs/hg19/FXN.json deleted file mode 100644 index 56a745b..0000000 --- a/data/repeat-specs/hg19/FXN.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "FXN", - "RepeatUnit": "GAA", - "TargetRegion": "chr9:71652203-71652220" -} diff --git a/data/repeat-specs/hg19/HTT.json b/data/repeat-specs/hg19/HTT.json deleted file mode 100644 index f45cdf9..0000000 --- a/data/repeat-specs/hg19/HTT.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "HTT", - "RepeatUnit": "CAG", - "TargetRegion": "chr4:3076604-3076660" -} diff --git a/data/repeat-specs/hg19/JPH3.json b/data/repeat-specs/hg19/JPH3.json deleted file mode 100755 index 63e29fb..0000000 --- a/data/repeat-specs/hg19/JPH3.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "JPH3", - "RepeatUnit": "CTG", - "TargetRegion": "chr16:87637894-87637935" -} diff --git a/data/repeat-specs/hg19/PPP2R2B.json b/data/repeat-specs/hg19/PPP2R2B.json deleted file mode 100755 index 72ad40c..0000000 --- a/data/repeat-specs/hg19/PPP2R2B.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "PPP2R2B", - "RepeatUnit": "GCT", - "TargetRegion": "chr5:146258291-146258320" -} diff --git a/data/repeat-specs/hg38/AR.json b/data/repeat-specs/hg38/AR.json deleted file mode 100644 index 3a39527..0000000 --- a/data/repeat-specs/hg38/AR.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "AR", - "RepeatUnit": "GCA", - "TargetRegion": "chrX:67545317-67545385" -} diff --git a/data/repeat-specs/hg38/ATN1.json b/data/repeat-specs/hg38/ATN1.json deleted file mode 100644 index a7e806c..0000000 --- a/data/repeat-specs/hg38/ATN1.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATN1", - "RepeatUnit": "CAG", - "TargetRegion": "chr12:6936717-6936773" -} diff --git a/data/repeat-specs/hg38/ATXN1.json b/data/repeat-specs/hg38/ATXN1.json deleted file mode 100644 index 8e8e22a..0000000 --- a/data/repeat-specs/hg38/ATXN1.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN1", - "RepeatUnit": "TGC", - "TargetRegion": "chr6:16327634-16327723" -} diff --git a/data/repeat-specs/hg38/ATXN10.json b/data/repeat-specs/hg38/ATXN10.json deleted file mode 100755 index 39fba5c..0000000 --- a/data/repeat-specs/hg38/ATXN10.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN10", - "RepeatUnit": "ATTCT", - "TargetRegion": "chr22:45795355-45795424" -} diff --git a/data/repeat-specs/hg38/ATXN2.json b/data/repeat-specs/hg38/ATXN2.json deleted file mode 100755 index 4009973..0000000 --- a/data/repeat-specs/hg38/ATXN2.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN2", - "RepeatUnit": "GCT", - "TargetRegion": "chr12:111598950-111599018" -} diff --git a/data/repeat-specs/hg38/ATXN3.json b/data/repeat-specs/hg38/ATXN3.json deleted file mode 100644 index 3aaf919..0000000 --- a/data/repeat-specs/hg38/ATXN3.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN3", - "RepeatUnit": "GCT", - "TargetRegion": "chr14:92071010-92071042" -} diff --git a/data/repeat-specs/hg38/ATXN7.json b/data/repeat-specs/hg38/ATXN7.json deleted file mode 100755 index 9985019..0000000 --- a/data/repeat-specs/hg38/ATXN7.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "ATXN7", - "RepeatUnit": "GCA", - "TargetRegion": "chr3:63912685-63912714" -} diff --git a/data/repeat-specs/hg38/C9ORF72.json b/data/repeat-specs/hg38/C9ORF72.json deleted file mode 100644 index 9b5df99..0000000 --- a/data/repeat-specs/hg38/C9ORF72.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "OffTargetRegions": [ - "chr1:8423621-8424123", - "chr1:30908134-30908774", - "chr1:150579391-150580033", - "chr10:930905-931660", - "chr10:24952417-24952917", - "chr10:43304793-43305293", - "chr10:46336799-46337407", - "chr10:46816084-46816694", - "chr10:100999604-101000210", - "chr11:910566-911217", - "chr11:2884463-2885264", - "chr11:44726687-44727485", - "chr11:67372280-67372872", - "chr12:459655-460155", - "chr12:54082198-54082983", - "chr12:128266837-128267355", - "chr13:20987999-20988595", - "chr13:25468390-25469012", - "chr13:106917208-106917872", - "chr14:32938887-32939513", - "chr14:105679948-105680448", - "chr15:30223044-30223669", - "chr16:768800-769576", - "chr16:1614060-1614704", - "chr16:3134792-3135352", - "chr16:87602167-87602667", - "chr17:10198366-10198994", - "chr17:68205353-68205991", - "chr17:81891216-81891716", - "chr18:54223898-54224704", - "chr19:613300-613854", - "chr19:878543-879249", - "chr19:2808203-2808704", - "chr19:17947550-17948080", - "chr19:39406515-39407140", - "chr19:50568088-50568733", - "chr19:52296835-52297335", - "chr2:3592014-3592538", - "chr2:25129075-25129575", - "chr2:168247029-168247668", - "chr2:234496869-234497369", - "chr2:234951699-234952200", - "chr2:236237108-236237609", - "chr2:239737825-239738572", - "chr20:407682-408343", - "chr20:62064994-62065670", - "chr21:45286832-45287440", - "chr22:39994754-39995382", - "chr22:49117030-49117530", - "chr22:50627811-50628453", - "chr3:46845816-46846372", - "chr3:47578061-47578713", - "chr3:49554284-49555050", - "chr3:50124040-50124697", - "chr3:51706745-51707245", - "chr3:128497690-128498194", - "chr3:184379812-184380428", - "chr4:151408888-151409388", - "chr5:10761009-10761635", - "chr5:146458825-146459326", - "chr5:172454308-172454808", - "chr5:177370695-177371467", - "chr6:33633375-33634093", - "chr6:43021419-43022045", - "chr6:46490763-46491263", - "chr6:109483279-109483938", - "chr8:141308351-141308989", - "chr9:83817588-83818088", - "chr9:129568834-129569572", - "chr9:129665779-129666418", - "chrX:9785975-9786751", - "chrX:49879637-49880326", - "chrX:53625583-53626227", - "chrX:154341222-154341987", - "chrX:154390158-154390883" - ], - "RepeatId": "C9ORF72", - "RepeatUnit": "GGCCCC", - "TargetRegion": "chr9:27573529-27573546" -} diff --git a/data/repeat-specs/hg38/CACNA1A.json b/data/repeat-specs/hg38/CACNA1A.json deleted file mode 100755 index 5b234d1..0000000 --- a/data/repeat-specs/hg38/CACNA1A.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "CACNA1A", - "RepeatUnit": "CTG", - "TargetRegion": "chr19:13207859-13207897" -} diff --git a/data/repeat-specs/hg38/CBL.json b/data/repeat-specs/hg38/CBL.json deleted file mode 100755 index 30c9a02..0000000 --- a/data/repeat-specs/hg38/CBL.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "CBL", - "RepeatUnit": "CGG", - "TargetRegion": "chr11:119206290-119206322" -} diff --git a/data/repeat-specs/hg38/CSTB.json b/data/repeat-specs/hg38/CSTB.json deleted file mode 100755 index 440bc4a..0000000 --- a/data/repeat-specs/hg38/CSTB.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "CSTB", - "RepeatUnit": "CGCGGGGCGGGG", - "TargetRegion": "chr21:43776444-43776479" -} diff --git a/data/repeat-specs/hg38/DMPK.json b/data/repeat-specs/hg38/DMPK.json deleted file mode 100644 index 63bc294..0000000 --- a/data/repeat-specs/hg38/DMPK.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "DMPK", - "RepeatUnit": "CAG", - "TargetRegion": "chr19:45770205-45770264" -} diff --git a/data/repeat-specs/hg38/FMR1.json b/data/repeat-specs/hg38/FMR1.json deleted file mode 100644 index f70c45b..0000000 --- a/data/repeat-specs/hg38/FMR1.json +++ /dev/null @@ -1,221 +0,0 @@ -{ - "OffTargetRegions": [ - "chr1:3068801-3069410", - "chr1:21176192-21176850", - "chr1:22142747-22143526", - "chr1:26695804-26696436", - "chr1:32964054-32964646", - "chr1:38046254-38047002", - "chr1:43979083-43979731", - "chr1:50418726-50419472", - "chr1:53738909-53739574", - "chr1:108559854-108560513", - "chr1:120069050-120069669", - "chr1:120723874-120724492", - "chr1:146228425-146229045", - "chr1:149390545-149391206", - "chr1:154558233-154558862", - "chr1:166166597-166167097", - "chr1:208244049-208244696", - "chr1:244048774-244049274", - "chr1:246931115-246931739", - "chr10:15370500-15371495", - "chr10:50991054-50991697", - "chr10:75400964-75401593", - "chr10:96586507-96587141", - "chr10:99535057-99535844", - "chr10:131900768-131901422", - "chr10:132396902-132397563", - "chr11:535031-535612", - "chr11:13010217-13010825", - "chr11:44309366-44309993", - "chr11:119206037-119206672", - "chr12:1593781-1594552", - "chr12:32399143-32399783", - "chr12:45729326-45729915", - "chr12:55743147-55743788", - "chr12:64404091-64404725", - "chr12:69239355-69239905", - "chr12:112381710-112382586", - "chr12:123533343-123533981", - "chr13:36919926-36920587", - "chr14:28767201-28767848", - "chr14:33800221-33800846", - "chr14:67515149-67515807", - "chr14:69572824-69573477", - "chr14:102592573-102593302", - "chr15:24847863-24848492", - "chr15:51737292-51737920", - "chr15:88256091-88256591", - "chr16:2178674-2179293", - "chr16:24729307-24730163", - "chr16:25691936-25692619", - "chr16:28062829-28063460", - "chr16:49855134-49855768", - "chr16:55478903-55479533", - "chr16:73058439-73059091", - "chr16:79598818-79599460", - "chr16:85168883-85169517", - "chr16:88570364-88570870", - "chr17:2593209-2593916", - "chr17:7885052-7885670", - "chr17:26774259-26774914", - "chr17:28644586-28645198", - "chr17:44219715-44220215", - "chr17:45241688-45242324", - "chr17:47693726-47694343", - "chr17:62065115-62065757", - "chr17:66301955-66302705", - "chr17:75588818-75589447", - "chr18:5891122-5891766", - "chr18:12896735-12897356", - "chr18:28176797-28177449", - "chr18:31942936-31943436", - "chr18:33578204-33578845", - "chr18:47246693-47247323", - "chr18:57435464-57436097", - "chr19:1285832-1286476", - "chr19:1407367-1408019", - "chr19:1567733-1568394", - "chr19:2015164-2015946", - "chr19:2163772-2164421", - "chr19:2307771-2308540", - "chr19:14495662-14496299", - "chr19:15332244-15332867", - "chr19:19406197-19406820", - "chr19:33796449-33797068", - "chr19:36140744-36141396", - "chr19:36309136-36309673", - "chr19:42116613-42117226", - "chr19:42241987-42242612", - "chr19:42253492-42254110", - "chr19:45496699-45497341", - "chr19:45972994-45973745", - "chr19:49072291-49072904", - "chr2:37156531-37157166", - "chr2:47905272-47905864", - "chr2:50346525-50347267", - "chr2:72148268-72148905", - "chr2:73269202-73269839", - "chr2:85133320-85133967", - "chr2:86914036-86915119", - "chr2:90324440-90325070", - "chr2:90325129-90325838", - "chr2:91809551-91810294", - "chr2:92042461-92043192", - "chr2:104855036-104856469", - "chr2:109760019-109760924", - "chr2:109794205-109794856", - "chr2:110610685-110611442", - "chr2:117870707-117871322", - "chr2:160493197-160493697", - "chr2:202376258-202376900", - "chr2:210170919-210171567", - "chr2:219627270-219627917", - "chr2:224584788-224585433", - "chr2:231037451-231038211", - "chr20:10034652-10035260", - "chr20:32277443-32278091", - "chr20:35226391-35227019", - "chr20:57709538-57710167", - "chr21:15730044-15730612", - "chr21:31732026-31732607", - "chr21:33554863-33555470", - "chr21:34614576-34615198", - "chr21:37072925-37073548", - "chr21:42218962-42219628", - "chr22:20131542-20132067", - "chr22:20858296-20858923", - "chr22:28883459-28884052", - "chr22:37983425-37984043", - "chr22:47379028-47379724", - "chr22:50474704-50475344", - "chr3:12287535-12288151", - "chr3:15859308-15859969", - "chr3:18425090-18425592", - "chr3:19947013-19947631", - "chr3:28575592-28576219", - "chr3:51390996-51391577", - "chr3:129605016-129605681", - "chr3:158105431-158106067", - "chr3:177197310-177197932", - "chr3:194583637-194584137", - "chr4:3074689-3075409", - "chr4:15003614-15004273", - "chr4:20251746-20252399", - "chr4:74932809-74933428", - "chr4:104491109-104491609", - "chr4:140153858-140154359", - "chr4:146639046-146639684", - "chr5:442842-443512", - "chr5:6448351-6449058", - "chr5:14144643-14145253", - "chr5:45695839-45696469", - "chr5:61332421-61333055", - "chr5:93583956-93584562", - "chr5:108380750-108381400", - "chr5:134371097-134371734", - "chr5:141878163-141878822", - "chr5:171419846-171420495", - "chr5:177554235-177554890", - "chr5:180291709-180292348", - "chr6:1389781-1390413", - "chr6:7107297-7107932", - "chr6:13711135-13711716", - "chr6:45422374-45423021", - "chr6:46490941-46491586", - "chr6:98834722-98835357", - "chr6:114342167-114342817", - "chr6:116680741-116681370", - "chr6:117906805-117907451", - "chr6:149317154-149317781", - "chr6:149317786-149318327", - "chr6:150865348-150865972", - "chr6:156778591-156779239", - "chr7:18086649-18087297", - "chr7:20785041-20785780", - "chr7:27173497-27174130", - "chr7:32891625-32892251", - "chr7:42237600-42238236", - "chr7:50793143-50793767", - "chr7:55887235-55888138", - "chr7:63114055-63114797", - "chr7:64181750-64182417", - "chr7:75092522-75093173", - "chr7:77536830-77537470", - "chr7:97005857-97006507", - "chr7:99143641-99144272", - "chr7:100673443-100674084", - "chr7:100693981-100694615", - "chr7:105013777-105014419", - "chr7:143362397-143363025", - "chr7:151086294-151086936", - "chr8:37698039-37698670", - "chr8:60680069-60680652", - "chr8:65841588-65842212", - "chr8:88327246-88327873", - "chr8:104588709-104589361", - "chr8:139702697-139703331", - "chr8:144461992-144462624", - "chr9:27572829-27573455", - "chr9:34957850-34958490", - "chr9:63450117-63450878", - "chr9:77019891-77020661", - "chr9:97854153-97854814", - "chr9:120714015-120714592", - "chr9:122126508-122127093", - "chr9:124869205-124869705", - "chr9:128841199-128841699", - "chr9:130579254-130579892", - "chrX:19990568-19991309", - "chrX:21374363-21374866", - "chrX:48957465-48958121", - "chrX:68912864-68913485", - "chrX:100409332-100409962", - "chrX:126165700-126166354", - "chrX:150983149-150983796" - ], - "RepeatId": "FMR1", - "RepeatUnit": "CGG", - "TargetRegion": "chrX:147912051-147912110" -} diff --git a/data/repeat-specs/hg38/FXN.json b/data/repeat-specs/hg38/FXN.json deleted file mode 100644 index d846bea..0000000 --- a/data/repeat-specs/hg38/FXN.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "FXN", - "RepeatUnit": "GAA", - "TargetRegion": "chr9:69037287-69037304" -} diff --git a/data/repeat-specs/hg38/HTT.json b/data/repeat-specs/hg38/HTT.json deleted file mode 100644 index 279b6db..0000000 --- a/data/repeat-specs/hg38/HTT.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "HTT", - "RepeatUnit": "CAG", - "TargetRegion": "chr4:3074877-3074933" -} diff --git a/data/repeat-specs/hg38/JPH3.json b/data/repeat-specs/hg38/JPH3.json deleted file mode 100755 index 2ac8993..0000000 --- a/data/repeat-specs/hg38/JPH3.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "JPH3", - "RepeatUnit": "CTG", - "TargetRegion": "chr16:87604288-87604329" -} diff --git a/data/repeat-specs/hg38/PPP2R2B.json b/data/repeat-specs/hg38/PPP2R2B.json deleted file mode 100755 index ae7821a..0000000 --- a/data/repeat-specs/hg38/PPP2R2B.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "CommonUnit": "true", - "RepeatId": "PPP2R2B", - "RepeatUnit": "GCT", - "TargetRegion": "chr5:146878728-146878757" -} diff --git a/docs/01_Introduction.md b/docs/01_Introduction.md old mode 100644 new mode 100755 index 6e68364..0429f4c --- a/docs/01_Introduction.md +++ b/docs/01_Introduction.md @@ -1,12 +1,19 @@ -In order to use Expansion Hunter you need to (1) download -the [latest release](https://github.com/Illumina/ExpansionHunter/releases) -or build the program from source, (2) obtain a set of files -specifying repeat regions of interest, and (3) have a BAM -or a CRAM file with alignments of reads from a PCR-free -WGS sample. - -The following pages will help you to get started. - - [Installation](02_Installation.md) - - [Usage](03_Usage.md) - - [Inputs](04_Inputs.md) - - [Outputs](05_Outputs.md) +# A guide to Expansion Hunter software + +Expansion Hunter is a tool to estimate sizes of short tandem repeats by +performing a targeted search through a BAM/CRAM file for reads that span, flank, +and are fully contained in each repeat. Newer versions of the program provide +limited support for insertions, deletions, and sequence swaps. + +In order to use Expansion Hunter you need to (1) download the latest release or +build the program from source, (2) obtain a set of files specifying repeat +regions of interest, and (3) have a BAM or a CRAM file with alignments of reads +from a PCR-free WGS sample. + +The following sections will help you to get started. + +* [Installation](02_Installation.md) +* [Usage](03_Usage.md) +* [Input variant catalogs](04_VariantCatalogFiles.md) +* [Output JSON files](05_OutputJsonFiles.md) +* [Output VCF files](06_OutputVcfFiles.md) diff --git a/docs/02_Installation.md b/docs/02_Installation.md old mode 100644 new mode 100755 index 1b58036..dd0d69f --- a/docs/02_Installation.md +++ b/docs/02_Installation.md @@ -1,21 +1,19 @@ # Installation -Expansion Hunter is designed for Linux and macOS operating systems. -A compiled binary for the latest release can be downloaded from -[here](https://github.com/Illumina/ExpansionHunter/releases). If -you wish to build the program from source follow the instructions -below. +Expansion Hunter is designed for Linux and macOS operating systems. A compiled +binary for the latest release can be downloaded from +[here](https://github.com/Illumina/ExpansionHunter/releases). If you wish to +build the program from source follow the instructions below. -Building from source --------------------- +## Building from source Prerequisites: - A recent version of [GCC](https://gcc.gnu.org/) or - [clang](http://clang.llvm.org/) compiler supporting C++11. - - [CMake](https://cmake.org/) version 3.2.0 or above. + [clang](http://clang.llvm.org/) compiler supporting C++11 standard + - [CMake](https://cmake.org/) version 3.2.0 or above - [Boost C++ Libraries](http://www.boost.org/) version 1.57.0 or - above. + above If you the above prerequisites are satisfied, you are ready to build the program. Note that during the build procedure, cmake will @@ -33,7 +31,8 @@ $ cmake .. $ make ``` -Note that if Boost is installed in a non-default location then its path should be specified with `BOOST_ROOT` in the cmake command above: +Note that if Boost is installed in a non-default location then its path should +be specified with `BOOST_ROOT` in the cmake command above: ```bash $ cmake -DBOOST_ROOT=/path/to/boost/ .. diff --git a/docs/03_Usage.md b/docs/03_Usage.md old mode 100644 new mode 100755 index d1279ce..2dd89d6 --- a/docs/03_Usage.md +++ b/docs/03_Usage.md @@ -1,43 +1,36 @@ # Usage -Expansion Hunter requires an indexed BAM or a CRAM file containing aligned reads from a PCR-free WGS sample, a FASTA -file with a reference genome assembly (which must be identical to the one used for aligning the reads), and repeat -specification files ([see here](Inputs)). +Expansion Hunter requires an indexed BAM or a CRAM file containing aligned reads +from a PCR-free WGS sample, a FASTA file with a reference genome assembly (which +must be the same as the one used to align the reads), and a [variant catalog +file](04_VariantCatalogs.md). -Expansion Hunter outputs a VCF file and a JSON file with repeat genotypes along with other useful information and a log -file with alignments of spanning and flanking reads and sequences of in-repeat reads. The VCF and JSON files are largely -equivalent, but the JSON file may be easier to parse programmatically. Here is a template that provides the names of the -required parameters. +Expansion Hunter outputs a VCF file and a JSON file with repeat genotypes along +with other useful information and a log file with alignments of spanning and +flanking reads and sequences of in-repeat reads. The VCF and JSON files are +largely equivalent, but the JSON file may be easier to parse programmatically. +Here is a template with the names of the required parameters. ```bash -ExpansionHunter --bam \ - --ref-fasta \ - --repeat-specs \ - --vcf \ - --json \ - --log +ExpansionHunter --reads \ + --reference \ + --variant-catalog \ + --output-prefix ``` -Optional arguments ------------------- +## Optional arguments -In addition to the required program options listed above, there are a number of optional arguments. +In addition to the required program options listed above, there are a number of +optional arguments. -| Program option | Description | Default | -|-----------------|--------------|:-------:| -| --sex arg | Specifies sex of the sample; can be either male or female*. | female | -| --skip-unaligned | If set, requires the program to skip the search for IRRs in unaligned reads**. | -- | -| --read-depth arg | Specifies read depth so that Expansion Hunter can skip computing it***. | -- | -| --min-score arg | The minimum weighted matching score (can be any positive number less than 1). | 0.90 | -| --min-baseq arg | The minimum base quality of a high-confidence base call. | 20 | -| --min-anchor-mapq arg | The minimum MAPQ of an in-repeat read anchor | 60 | -| --region-extension-length arg | Specifies how far from on/off-target regions to search for informative reads. | 1000 | +* `--sex ` Specifies sex of the sample; can be either `male` or `female` + (default). This parameter only affects repeats on sex chromosomes. -\* `sex` parameter only affects genotyping of sex chromosomes. +* `--genome-coverage ` Specifies read depth on diploid chromosomes. Specifying + read depth is required for BAM files containing a subset of alignments or CRAMs. -\** Skipping unaligned reads can results in a considerable speed up but can also cause repeat sizes to get -underestimated. +* `--region-extension-length ` Specifies how far from on/off-target regions + to search for informative reads. Set to 1000 by default. -\*** Specifying read depth is required when analyzing BAM files that contain a subset of read alignments. - -Note that the full list of program options with brief explanations can be obtained by running `ExpansionHunter --help`. \ No newline at end of file +Note that the full list of program options with brief explanations can be +obtained by running `ExpansionHunter --help`. \ No newline at end of file diff --git a/docs/04_Inputs.md b/docs/04_Inputs.md deleted file mode 100644 index f65bfdc..0000000 --- a/docs/04_Inputs.md +++ /dev/null @@ -1,52 +0,0 @@ -# Inputs - -Repeat-specification files --------------------------- - -Repeat-specification files define repeat regions for Expansion Hunter to -analyze. Each repeat-specification file is a JSON file containing a single -object that consists of name/value pairs. Sample repeat-specification for -some pathogenic repeats are contained in `data/repeat-specs/` directory. - -| Field | Description | -|------------------|-------------------------------------------------------------------------------------------| -| RepeatId | A unique string identifier of the repeat region. | -| RepeatUnit | The repeat unit (in reference orientation) that the repeat is comprised of. | -| CommonUnit | If true, only anchored IRRs are used to estimate the long repeat sizes* (Default: false). | -| TargetRegion | 1-based coordinates of the repeat region in the reference, specified as `chrom:start-end`.| -| OffTargetRegions | An array of off-target regions. This field is optional and used only if `CommonUnit` is set to `false`. | - -\* Should be set to true if repeats longer than the read length and having -the same repeat unit are expected to occur elsewhere in the genome. - -A note on creating custom repeat-specification files ----------------------------------------------------- - -Creating specification files for new repeat regions is easy: Just use one of -the provided specification files as a template. - -It is important to specify `TargetRegion` in such a way that it encompasses -the full reference repeat sequence. That is, the reference sequence of the -`TargetRegion` should (a) start and end with a perfect match to the repeat -unit and (b) the sequences adjacent to the repeat on both sides should be -distinct from the repeat unit. - -One can use `samtools` to confirm that `TargetRegion` is specified correctly. -For example, the target region of *C9orf72* repeat (chr9:27573527-27573544 in -hg19) is a perfect repetition of GGCCCC hexamer: -```bash - $ samtools faidx hg19_ref.fa chr9:27573527-27573544 - >chr9:27573527-27573544 - ggccccggccccggcccc -``` -while the sequences adjacent to the left and right sides of the repeat do not -end and start (respectively) with the repeat unit hexamer: -```bash - $ samtools faidx hg19_ref.fa chr9:27573507-27573526 - >chr9:27573507-27573526 - gcccgccccgaccacgcccc - - $ samtools faidx hg19_ref.fa chr9:27573545-27573564 - >chr9:27573545-27573564 - TAGCGCGCGACTCCTGAGTT -``` diff --git a/docs/04_VariantCatalogFiles.md b/docs/04_VariantCatalogFiles.md new file mode 100755 index 0000000..34a21b5 --- /dev/null +++ b/docs/04_VariantCatalogFiles.md @@ -0,0 +1,150 @@ +# Variant catalogs + +## Overview + +A variant catalog is an essential component of Expansion Hunter's input. It +specifies reference coordinates and structure of each locus that the program +will analyze. Although the loci usually correspond to repeats, they could also +contain other classes of variants such as insertions, deletions, and sequence +swaps. + +This document describes the format of files that store repeat catalogs. Users +that are considering creating new or modifying existing catalog files should +read this document carefully. Even a minor mistake in locus definition could +lead to a significant drop in genotyping accuracy. + + +## Variant catalog files + +A variant catalog file is a JSON array whose entries specify individual loci +that the program will analyze. Here is an example of a catalog consisting of +three loci containing repeats. + +```json +[ +{ + "LocusId": "DMPK", + "LocusStructure": "(CAG)*", + "ReferenceRegion": "19:46273462-46273522", + "VariantType": "Repeat" +}, +{ + "LocusId": "FMR1", + "LocusStructure": "(CGG)*", + "ReferenceRegion": "X:146993568-146993628", + "VariantType": "RareRepeat", + "OfftargetRegions": [ + "12:7781290-7781350", + "12:125052154-125052156", + "16:25703613-25703635", + "16:28074516-28074518", + "17:30814024-30814026", + "17:64298467-64298469", + "19:2015524-2015526", + "2:87141540-87141618", + "2:92230909-92230911", + "2:211036020-211036032", + "2:225449878-225449880", + "20:30865500-30865516", + "5:443334-443364", + "7:20824939-20824941", + "7:100271437-100271439", + "7:104654597-104654599", + "7:143059853-143059855", + "9:100616695-100616697", + "X:20009036-20009046" + ] +}, +{ + "LocusId": "HTT", + "LocusStructure": "(CAG)*CAACAG(CCG)*", + "ReferenceRegion": ["4:3076604-3076660", "4:3076666-3076693"], + "VariantType": ["Repeat", "Repeat"] +} +] +``` + +The first entry in this catalog specifies a locus containing a single short +tandem repeat. The identifier of this locus is DMPK (field `LocusId`). The +regular expression `(CAG)*` means that it is comprised of zero or more +repetitions of the CAG repeat unit (field `LocusStructure`). The reference +coordinates of this repeat are 19:46273462-46273522 (field `ReferenceRegion`). +The `VariantType` field specifies that it is an ordinary STR meaning that we +expect the genome to contain multiple long repeats (whose size is close to +fragment length and longer) with this repeat unit. For ordinary `Repeat`s +Expansion Hunter limits the types of reads that are used used to infer the size +of the repeat. As a result, regular repeats are genotyped up to the fragment +length and if a repeat is reported to have a size estimate close to the fragment +length then this number should be treated as a lower bound for its true size. + +The second entry is similar to the first. The only difference is that the +variant type of the second repeat is set to `RareRepeat`. This means that the +user is *confident* that there are no other long (fragment length or longer) +repeats with the same repeat unit elsewhere in the genome. This information +permits the program to use additional read-level evidence to potentially +estimate the length of the repeat past the fragment length. For "rare" repeats, +off-target regions (field `OfftargetRegions`) specify regions of the genome that +may contain misaligned reads. + +The final entry describes a repeat region containing multiple short tandem +repeats in close proximity to each other. The regular expression +`(CAG)*CAACAG(CCG)*` specifies that this region consists of two short tandem +repeats with repeat units CAG and CCG separated by the sequence CAACAG. +Fields `ReferenceRegion` and `VariantStatus` contain reference region and status +of each constituent repeat. By default, the program assigns an identifier to +each variant consisting of the locus id and reference region. So the two repeats +receive ids HTT_4:3076603-3076660 and HTT_4:3076666-3076693 respectively. + +The following section describes loci-specification records that the catalogs are +comprised of. + + +## The structure of loci-specification records + +When a region contains a single variant, there is no difference between the +repeat and the region containing it. So field names refer only to the repeat as +shown in the table below. + +* `LocusId` Unique identifier of the entire locus + +* `LocusStructure` Regular expression defining the structure of the locus. When + the locus contains multiple variants, ReferenceRegion and VariantStatus are + arrays with associated information for each variant in the same order. + +* `ReferenceRegion` 0-based half open reference coordinates of the variant + formatted as `chrom:start-end`. + +* `VariantType` Can be either `RareRepeat`, `RareRepeat`, or `SmallVariant` + with the latter corresponding to insertions deletions or sequence swaps. + +* `OfftargetRegions` Array of regions where informative reads may misalign; + only used for variants of type `RareRepeat`. + + +## Using regular expressions to define locus structure + +ExpansionHunter supports a very limited subset of regular expressions to +define the structure of each locus. These expressions can consist of +sub-expressions listed in the table bellow, possibly separated by +interrupting DNA sequences. + + +| Variant | Regular expression | +|----------------------------------------------------|--------------------| +| Short tandem repeat that can occur 0 or more times | (CCG)* | +| Short tandem repeat that can occur 1 or more times | (CCG)+ | +| Single nucleotide variant | (C\|T) | +| Sequence swap | (CAGT\|CGTTG) | +| Deletion or insertion | (CTGGC)\? | + + +For example, a CAG repeat flanked by a CAG/CAT swap is defined by expression +(CAG)+CTGT(CAG|CAT). + + +## A note on creating custom variant catalogs + +Creating custom variant catalogs is relatively straightforward for "common" +variants. Defining "rare" variants is much harder because some data analysis is +required to prove that the variant is indeed rare. Users who are looking to +define custom catalogs are encouraged to contact the developers for assistance. diff --git a/docs/05_OutputJsonFiles.md b/docs/05_OutputJsonFiles.md new file mode 100755 index 0000000..ca3dce0 --- /dev/null +++ b/docs/05_OutputJsonFiles.md @@ -0,0 +1,55 @@ +# JSON files output by Expansion Hunter + +JSON files generated by Expansion Hunter contain an array with entries +describing each genotyped variant. The structure entry structure is different +for repeats and small variants (insertions, deletions, and swaps). + +## Repeat records + +Repeat records contain the following fields. + +* `VariantId` Unique variant identifier + +* `RepeatUnit` Repeat unit in the reference orientation + +* `VariantType` Always set to "Repeat" + +* `VariantSubtype` Either "Repeat" or "RareRepeat" + +* `Genotype` Repeat genotype given by the size of each repeat allele + +* `GenotypeConfidenceInterval` Size confidence interval for each repeat allele + +* `CountsOfSpanningReads` Summary of identified spanning reads given as an array + with entries `[n, m]` where `n` is the number of repeat units spanned by the + flanking read and `m` is the number of such reads + +* `CountsOfFlankingReads` An analog of `CountsOfSpanningReads` for in-repeat + reads + +* `CountsOfInrepeatReads` An analog of `CountsOfSpanningReads` for spanning + reads + +* `ReferenceRegion` 0-based half open reference coordinates of the repeat region + (`chrom:start-end`) + +## Small variant records + +Records for small variants contain the following fields. + +* `VariantId` Unique variant identifier + +* `VariantType` Always set to "SmallVariant" + +* `VariantSubtype` Either "Insertion", "Deletion", or "Swap" + +* `RepeatUnit` Repeat unit in the reference orientation + +* `Genotype` Formed, as usual, from `0`s and `1`s corresponding to ref and alt + alleles + +* `CountOfAltReads` Number of reads supporting the alt allele + +* `CountOfRefReads` Number of reads supporting the ref allele + +* `ReferenceRegion` Reference region of the variant diff --git a/docs/05_Outputs.md b/docs/05_Outputs.md deleted file mode 100644 index fe6b5ba..0000000 --- a/docs/05_Outputs.md +++ /dev/null @@ -1,89 +0,0 @@ -# Outputs - -VCF output ----------- - -The VCF output file contains the following fields. - - Field | Description ---------|---------------------------------------------------------------------------------- - CHROM | Chromosome identifier - POS | Position of the first base before the repeat region in the reference - ID | Always `.` - REF | The reference base at position POS - ALT | List of repeat alleles in format `` where n is the number of repeat units - QUAL | Always `.` - FILTER | Always PASS - -These are followed by the INFO fields. - -Field | Description ----------|--------------------------------------------------------- - SVTYPE | Always STR - END | Position of the last base of the repeat region in the reference - REF | Number of repeat units spanned by the repeat in the reference - RL | Reference length in bp - RU | Repeat unit in the reference orientation - REPID | Repeat id from the repeat-specification file - -And finally the sample fields. - - Field | Description -:-----:|--------------------------------------------------------- - GT | Genotype - SO | Type of reads that support the allele; can be SPANNING, FLANKING, or INREPEAT meaning that the reads span, flank, or are fully contained in the repeat - CN | Allele copy number - CI | Confidence interval for CN - AD_SP | Number of spanning reads consistent with the allele - AD_FL | Number of flanking reads consistent with the allele - AD_IR | Number of in-repeat reads consistent with the allele - -For example, the following VCF entry describes the state of -*C9orf72* repeat in a sample with ID LP6005616-DNA_A03. - -``` -#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT LP6005616-DNA_A03 -chr9 27573526 . C , . PASS SVTYPE=STR;END=27573544;REF=3;RL=18;RU=GGCCCC;REPID=ALS GT:SO:CN:CI:AD_SP:AD_FL:AD_IR 1/2:SPANNING/INREPEAT:2/349:2-2/323-376:19/0:3/6:0/459 -``` - -This line tells us that first allele spans 2 repeat units while -the second allele spans 349 repeat units. The repeat unit is -GGCCCC (`RU` INFO field), so the sequence of the first allele -is GGCCCCGGCCCC and the sequence of the second allele is GGCCCC x 349. -The repeat spans three repeat units in the reference (`REF` INFO -field). The length of the short allele was estimated from spanning -reads (`SPANNING`) while the length of the expanded allele was -estimated from in-repeat reads (`INREPEAT`). The confidence interval -for the size of the expanded allele is (323,376). There are 19 -spanning and 3 flanking reads consistent with the repeat allele of -size 2 (that is 19 reads fully contain the repeat of size 2 and 2 -flanking reads overlap at most 2 repeat units). Also, there are 6 -flanking and 459 in-repeat reads consistent with the repeat allele -of size 349. - -JSON output ------------ - -Each repeat region is represented by a JSON object. Here is the list -of fields contained in each object and their description. - - Field | Description --------------------|--------------------------------------------------------- - RepeatId | A unique string identifier of the repeat region - TargetRegion | 1-based coordinates of the repeat region in the reference, specified by `chrom:start-end` - RepeatUnit | The unit of the repeat - Genotype | Repeat genotype; a pair of repeat sizes separated by `/` for diploid chromosomes and a single repeat size for haploid chromosomes - GenotypeCi | Confidence interval for the size of each repeat allele - GenotypeSupport | The number of spanning, flanking, and in-repeat reads (in this order) consistent with each repeat allele - IrrCount | The total number of identified in-repeat reads - AnchoredIrrCount | The number of in-repeat reads anchored by their mates to the repeat region - OffTargetRegionIrrCounts | Is an object consisting of region/count pairs with each count giving the number of in-repeat reads found in the corresponding region - UnalignedIrrCount | The number in-repeat reads found in the unaligned section of the BAM/CRAM file - RepeatSizes | Describes the type of the reads (`Source`), their number (`NumSupportingReads`), and how many repeat units they contain (`Size`); for flanking reads, `NumSupportingReads` gives the size of the longest flanking read identified - -The log file ------------- - -The log file contains alignments of spanning and flanking reads and sequences of in-repeat reads. - -The top-level keys are repeat region identifiers and the values correspond to repeat alleles. Each allele is described by `_` where `READ_TYPE` can be `SPANNING`, `FLANKING`, or `INREPEAT` and `SIZE` is the size of the repeat in repeat units. For each allele estimated from spanning/flanking reads, the log file lists the name and the alignment of each spanning/flanking read respectively. The low quality bases are printed in lowercase. For `INREPEAT` alleles, the log file lists the sequences of in-repeat reads and, if appropriate, anchors. One of the records is labeled as `FLANKING`; it contains alignments of flanking reads that likely came from one of the `SPANNING` alleles. \ No newline at end of file diff --git a/docs/06_OutputVcfFiles.md b/docs/06_OutputVcfFiles.md new file mode 100755 index 0000000..0768ac8 --- /dev/null +++ b/docs/06_OutputVcfFiles.md @@ -0,0 +1,31 @@ +# VCF files output by Expansion Hunter + +Expansion Hunter generates a separate VCF record for each repeat with +information about repeat's location and genotype. The records for non hom-ref +repeats are demarcated by `` symbolic alleles where `n` is the number of +repeat units that the corresponding allele spans. + +The header of the VCF file contains a detailed description of each record. + +## Example + +The following VCF entry describes the state of *C9orf72* repeat in a sample with +name/barcode LP6005616-DNA_A03. + +``` +#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT LP6005616-DNA_A03 +chr9 27573526 . C , . PASS SVTYPE=STR;END=27573544;REF=3;RL=18;RU=GGCCCC;REPID=ALS GT:SO:CN:CI:AD_SP:AD_FL:AD_IR 1/2:SPANNING/INREPEAT:2/349:2-2/323-376:19/0:3/6:0/459 +``` + +This line tells us that first allele spans 2 repeat units while the second +allele spans 349 repeat units. The repeat unit is GGCCCC (`RU` INFO field), so +the sequence of the first allele is GGCCCCGGCCCC and the sequence of the second +allele is GGCCCC x 349. The repeat spans three repeat units in the reference +(`REF` INFO field). The length of the short allele was estimated from spanning +reads (`SPANNING`) while the length of the expanded allele was estimated from +in-repeat reads (`INREPEAT`). The confidence interval for the size of the +expanded allele is (323,376). There are 19 spanning and 3 flanking reads +consistent with the repeat allele of size 2 (that is 19 reads fully contain the +repeat of size 2 and 2 flanking reads overlap at most 2 repeat units). Also, +there are 6 flanking and 459 in-repeat reads consistent with the repeat allele +of size 349. diff --git a/filtering/CMakeLists.txt b/filtering/CMakeLists.txt new file mode 100755 index 0000000..7eec568 --- /dev/null +++ b/filtering/CMakeLists.txt @@ -0,0 +1,5 @@ +file(GLOB SOURCES "*.cpp") +add_library(filtering ${SOURCES}) +target_link_libraries(filtering common graphtools) + +add_subdirectory(tests) diff --git a/filtering/OrientationPredictor.cpp b/filtering/OrientationPredictor.cpp new file mode 100755 index 0000000..0144be0 --- /dev/null +++ b/filtering/OrientationPredictor.cpp @@ -0,0 +1,107 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "filtering/OrientationPredictor.hh" + +#include +#include +#include +#include +#include + +#include "graphalign/GaplessAligner.hh" +#include "graphcore/Path.hh" +#include "graphcore/PathOperations.hh" + +using graphtools::alignWithoutGaps; +using graphtools::GraphAlignment; +using graphtools::KmerIndex; +using graphtools::Path; +using std::list; +using std::string; +using std::vector; + +namespace ehunter +{ + +static int countNonoverlappingKmerMatches(const string query, const KmerIndex& kmerIndex) +{ + const std::size_t kmerLength = kmerIndex.kmerLength(); + int matchCount = 0; + size_t position = 0; + while (position + kmerLength <= query.length()) + { + string kmer = query.substr(position, kmerLength); + std::transform(kmer.begin(), kmer.end(), kmer.begin(), ::toupper); + + if (kmerIndex.contains(kmer)) + { + ++matchCount; + position += kmerLength; + } + else + { + ++position; + } + } + return matchCount; +} + +OrientationPrediction OrientationPredictor::predict(const std::string& query) const +{ + const int numForwardMatches = countNonoverlappingKmerMatches(query, kmerIndex_); + const int numReverseComplementMatches + = countNonoverlappingKmerMatches(query, kmerIndexForReverseComplementedGraph_); + + const int maxMatches = std::max(numForwardMatches, numReverseComplementMatches); + + if (maxMatches < minKmerMatchesToPass_) + { + return OrientationPrediction::kDoesNotAlign; + } + + if (numForwardMatches >= numReverseComplementMatches) + { + return OrientationPrediction::kAlignsInOriginalOrientation; + } + else + { + return OrientationPrediction::kAlignsInReverseComplementOrientation; + } +} + +std::ostream& operator<<(std::ostream& out, OrientationPrediction orientationPrediction) +{ + switch (orientationPrediction) + { + case OrientationPrediction::kAlignsInOriginalOrientation: + out << "kAlignsInReverseComplementOrientation"; + break; + case OrientationPrediction::kAlignsInReverseComplementOrientation: + out << "kAlignsInReverseComplementOrientation"; + break; + case OrientationPrediction::kDoesNotAlign: + out << "kDoesNotAlign"; + } + + return out; +} + +} diff --git a/filtering/OrientationPredictor.hh b/filtering/OrientationPredictor.hh new file mode 100755 index 0000000..3b5903b --- /dev/null +++ b/filtering/OrientationPredictor.hh @@ -0,0 +1,84 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include + +#include "graphalign/GraphAlignment.hh" +#include "graphalign/KmerIndex.hh" +#include "graphcore/Graph.hh" +#include "graphcore/GraphOperations.hh" + +namespace ehunter +{ + +enum class OrientationPrediction +{ + kAlignsInOriginalOrientation, + kAlignsInReverseComplementOrientation, + kDoesNotAlign +}; + +std::ostream& operator<<(std::ostream& out, OrientationPrediction orientationPrediction); + +class OrientationPredictor +{ +public: + OrientationPredictor(int readLength, const graphtools::Graph* graphRawPtr) + : kmerLength_(pickKmerLength(readLength)) + , minKmerMatchesToPass_(pickKmerMatchesToPass(readLength)) + , kmerIndex_(*graphRawPtr, kmerLength_) + , reverseComplementedGraph_(graphtools::reverseGraph(*graphRawPtr, true)) + , kmerIndexForReverseComplementedGraph_(reverseComplementedGraph_, kmerLength_) + { + } + + static int pickKmerLength(int readLength) + { + int kmerLength = readLength / 10.5; + kmerLength = std::max(kmerLength, 3); + + return kmerLength; + } + + static int pickKmerMatchesToPass(int readLength) + { + if (readLength >= 100) + { + return 3; + } + + return 3; + } + + OrientationPrediction predict(const std::string& query) const; + +private: + int32_t kmerLength_; + int32_t minKmerMatchesToPass_; + graphtools::KmerIndex kmerIndex_; + const graphtools::Graph reverseComplementedGraph_; + graphtools::KmerIndex kmerIndexForReverseComplementedGraph_; +}; + +} diff --git a/filtering/tests/CMakeLists.txt b/filtering/tests/CMakeLists.txt new file mode 100755 index 0000000..73f2b22 --- /dev/null +++ b/filtering/tests/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(OrientationPredictorTest OrientationPredictorTest.cpp) +target_link_libraries(OrientationPredictorTest filtering gtest_main) +add_test(NAME OrientationPredictorTest COMMAND OrientationPredictorTest) diff --git a/filtering/tests/OrientationPredictorTest.cpp b/filtering/tests/OrientationPredictorTest.cpp new file mode 100755 index 0000000..95f8ea7 --- /dev/null +++ b/filtering/tests/OrientationPredictorTest.cpp @@ -0,0 +1,54 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "filtering/OrientationPredictor.hh" + +#include + +#include "gtest/gtest.h" + +#include "graphalign/GappedAligner.hh" +#include "graphalign/GraphAlignment.hh" +#include "graphalign/GraphAlignmentOperations.hh" +#include "graphcore/Graph.hh" +#include "graphcore/GraphBuilders.hh" +#include "graphcore/Path.hh" +#include "graphutils/SequenceOperations.hh" + +#include "input/RegionGraph.hh" +#include "region_spec/LocusSpecification.hh" + +using graphtools::reverseComplement; +using std::string; + +using namespace ehunter; + +TEST(PredictingQueryOrientation, TypicalQueries_Classified) +{ + graphtools::Graph graph = graphtools::makeStrGraph("TAAT", "CCG", "CCTTA"); + + OrientationPredictor orientationPredictor(10, &graph); + + EXPECT_EQ(OrientationPrediction::kAlignsInOriginalOrientation, orientationPredictor.predict("ATCCGCCTTA")); + EXPECT_EQ( + OrientationPrediction::kAlignsInReverseComplementOrientation, + orientationPredictor.predict(reverseComplement("ATCCGCCTTA"))); + EXPECT_EQ(OrientationPrediction::kDoesNotAlign, orientationPredictor.predict("AAAAAAAAA")); +} diff --git a/genotyping/AllelePresenceChecker.cpp b/genotyping/AllelePresenceChecker.cpp new file mode 100755 index 0000000..508599b --- /dev/null +++ b/genotyping/AllelePresenceChecker.cpp @@ -0,0 +1,70 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "genotyping/AllelePresenceChecker.hh" + +#include +#include + +namespace ehunter +{ + +static double poissonLogLikelihood(double lambda, double count) +{ + return count * log(lambda) - lambda - boost::math::lgamma(count + 1); +} + +AllelePresenceStatus AllelePresenceChecker::check(int targetAlleleCount, int otherAlleleCount) const +{ + if (targetAlleleCount < 0 || otherAlleleCount < 0) + { + throw std::runtime_error("Negative read counts are not allowed"); + } + + const int totalReadCount = targetAlleleCount + otherAlleleCount; + double ll0 = (totalReadCount > 0) ? + poissonLogLikelihood(errorRate_ * totalReadCount, targetAlleleCount) : + 0; + double ll1 = poissonLogLikelihood(haplotypeDepth_, targetAlleleCount); + if (abs(ll0 - ll1) < log(llrThreshold_)) + { + return AllelePresenceStatus::kUncertain; + } + + return (ll1 > ll0) ? AllelePresenceStatus::kPresent : AllelePresenceStatus::kAbsent; +} + +std::ostream& operator<<(std::ostream& out, AllelePresenceStatus status) +{ + switch (status) + { + case AllelePresenceStatus::kAbsent: + out << "Absent"; + break; + case AllelePresenceStatus::kPresent: + out << "Present"; + break; + case AllelePresenceStatus::kUncertain: + out << "Uncertain"; + break; + } + + return out; +} + +} diff --git a/genotyping/AllelePresenceChecker.hh b/genotyping/AllelePresenceChecker.hh new file mode 100755 index 0000000..1eed57a --- /dev/null +++ b/genotyping/AllelePresenceChecker.hh @@ -0,0 +1,74 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include + +#include "common/Common.hh" + +namespace ehunter +{ + +enum class AllelePresenceStatus +{ + kPresent, + kAbsent, + kUncertain +}; + +/* + * Genotyper checking for presence (>= 1 allele) of a given 'Key' allele + */ +class AllelePresenceChecker +{ +public: + AllelePresenceChecker(double haplotypeDepth, double errorRate = 0.02, double llrThreshold = 10000) + : haplotypeDepth_(haplotypeDepth) + , errorRate_(errorRate) + , llrThreshold_(llrThreshold) + { + if (haplotypeDepth <= 0) + { + throw std::runtime_error("Haplotype depth must be positive"); + } + if (errorRate <= 0 || errorRate >= 1) + { + throw std::runtime_error("Error rate must be positive and less than 1"); + } + if (llrThreshold < 0) + { + throw std::runtime_error("Likelihood Ratio threshold must be positive"); + } + } + + AllelePresenceStatus check(int targetAlleleCount, int otherAlleleCount) const; + +private: + // expected depth for one allele + double haplotypeDepth_; + // Rate of 'false' key-allele observations + double errorRate_; + // If the likelihood ratio threshold in favor of presence or absence + // is not at least this strong, return no call. + double llrThreshold_; +}; + +std::ostream& operator<<(std::ostream& out, AllelePresenceStatus status); + +} diff --git a/genotyping/CMakeLists.txt b/genotyping/CMakeLists.txt old mode 100644 new mode 100755 index cb7d3bc..e8a66db --- a/genotyping/CMakeLists.txt +++ b/genotyping/CMakeLists.txt @@ -1,3 +1,4 @@ -file(GLOB SOURCES "*.cc") +file(GLOB SOURCES "*.cpp") add_library(genotyping ${SOURCES}) -add_subdirectory(unit_tests) +target_link_libraries(genotyping common) +add_subdirectory(tests) diff --git a/genotyping/RepeatGenotype.cpp b/genotyping/RepeatGenotype.cpp new file mode 100755 index 0000000..7c0a78a --- /dev/null +++ b/genotyping/RepeatGenotype.cpp @@ -0,0 +1,76 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "genotyping/RepeatGenotype.hh" + +#include + +using std::ostream; +using std::string; +using std::to_string; + +namespace ehunter +{ + +void RepeatGenotype::assertValidity() const +{ + if (alleles_.empty() || alleles_.size() > 2) + { + throw std::logic_error(std::to_string(alleles_.size()) + " is not a valid number of alleles"); + } + + if (shortAlleleSizeInBp() > longAlleleSizeInBp()) + { + throw std::logic_error("Allele sizes are not ordered"); + } + + for (const RepeatAllele& allele : alleles_) + { + const bool isCiOrdered = allele.ci.start() <= allele.ci.end(); + const bool isRepeatSizeInsizeCi + = allele.ci.start() <= allele.numRepeatUnits && allele.numRepeatUnits <= allele.ci.end(); + + if (!isCiOrdered || !isRepeatSizeInsizeCi) + { + string ciEncoding = "(" + to_string(allele.ci.start()) + ", " + to_string(allele.ci.end()) + ")"; + string repeatSizeEncoding = to_string(allele.numRepeatUnits); + throw std::logic_error(ciEncoding + " is invalid CI for repeat of size " + repeatSizeEncoding); + } + } +} + +ostream& operator<<(ostream& out, const RepeatGenotype& genotype) +{ + const auto& shortAlleleCi = genotype.shortAlleleSizeInUnitsCi(); + + out << shortAlleleCi; + + if (genotype.numAlleles() == 2) + { + const auto& longAlleleCi = genotype.longAlleleSizeInUnitsCi(); + + out << "/" << longAlleleCi; + } + + return out; +} + +} diff --git a/genotyping/RepeatGenotype.hh b/genotyping/RepeatGenotype.hh new file mode 100755 index 0000000..10e792d --- /dev/null +++ b/genotyping/RepeatGenotype.hh @@ -0,0 +1,108 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "common/Common.hh" + +namespace ehunter +{ + +class RepeatGenotype +{ +public: + RepeatGenotype(int32_t repeatUnitLen, const std::vector& alleleSizes) + : repeatUnitLen_(repeatUnitLen) + { + for (int32_t size : alleleSizes) + { + RepeatAllele repeatAllele; + repeatAllele.numRepeatUnits = size; + repeatAllele.ci = NumericInterval(size, size); + alleles_.push_back(repeatAllele); + } + + assertValidity(); + } + + int32_t numAlleles() const { return alleles_.size(); } + bool isHomozygous() const { return shortAlleleSizeInUnits() == longAlleleSizeInUnits(); } + int32_t shortAlleleSizeInUnits() const { return alleles_.front().numRepeatUnits; } + void setShortAlleleSizeInUnits(int32_t numUnits) + { + alleles_.front().numRepeatUnits = numUnits; + alleles_.front().ci = NumericInterval(numUnits, numUnits); + assertValidity(); + } + int32_t shortAlleleSizeInBp() const { return alleles_.front().numRepeatUnits * repeatUnitLen_; } + NumericInterval shortAlleleSizeInUnitsCi() const { return alleles_.front().ci; } + void setShortAlleleSizeInUnitsCi(int32_t lowerBound, int32_t upperBound) + { + alleles_.front().ci = NumericInterval(lowerBound, upperBound); + assertValidity(); + } + + int32_t longAlleleSizeInUnits() const { return alleles_.back().numRepeatUnits; } + void setLongAlleleSizeInUnits(int32_t numUnits) + { + alleles_.back().numRepeatUnits = numUnits; + alleles_.back().ci = NumericInterval(numUnits, numUnits); + assertValidity(); + } + + int32_t longAlleleSizeInBp() const { return alleles_.back().numRepeatUnits * repeatUnitLen_; } + NumericInterval longAlleleSizeInUnitsCi() const { return alleles_.back().ci; } + void setLongAlleleSizeInUnitsCi(int32_t lowerBound, int32_t upperBound) + { + alleles_.back().ci = NumericInterval(lowerBound, upperBound); + assertValidity(); + } + + bool operator==(const RepeatGenotype& other) const + { + return repeatUnitLen_ == other.repeatUnitLen_ && alleles_ == other.alleles_; + } + +private: + struct RepeatAllele + { + int32_t numRepeatUnits = 0; + NumericInterval ci; + bool operator==(const RepeatAllele& other) const + { + return numRepeatUnits == other.numRepeatUnits && ci == other.ci; + } + }; + void assertValidity() const; + int32_t repeatUnitLen_; + std::vector alleles_; +}; + +std::ostream& operator<<(std::ostream& out, const RepeatGenotype& genotype); + +} diff --git a/genotyping/RepeatGenotyper.cpp b/genotyping/RepeatGenotyper.cpp new file mode 100755 index 0000000..d531f47 --- /dev/null +++ b/genotyping/RepeatGenotyper.cpp @@ -0,0 +1,349 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Mitch Bekritsky , Richard Shaw +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "genotyping/RepeatGenotyper.hh" + +#include +#include +#include + +#include + +#include "genotyping/RepeatLength.hh" +#include "genotyping/ShortRepeatGenotyper.hh" + +namespace ehunter +{ + +using boost::optional; +using std::cerr; +using std::endl; +using std::map; +using std::string; +using std::vector; + +static CountTable combineFlankingAndInrepeatReads( + int maxNumUnitsInRead, const CountTable& flankingCounts, const CountTable& inrepeatCounts) +{ + int maxNumReadsToTransfer = 5; + CountTable updatedFlankingCounts = flankingCounts; + for (int numUnits = maxNumUnitsInRead; numUnits != 0; --numUnits) + { + const int count = inrepeatCounts.countOf(numUnits); + const int countToTransfer = std::min(count, maxNumReadsToTransfer); + + for (int counter = 0; counter != countToTransfer; ++counter) + { + updatedFlankingCounts.incrementCountOf(numUnits); + } + + maxNumReadsToTransfer -= countToTransfer; + if (maxNumReadsToTransfer == 0) + { + break; + } + } + + return updatedFlankingCounts; +} + +optional RepeatGenotyper::genotypeRepeat(const vector& alleleSizeCandidates) const +{ + if (alleleSizeCandidates.empty()) + { + return optional(); + } + + const CountTable countsOfFlankingReadsForShortRepeatGenotyper + = combineFlankingAndInrepeatReads(maxNumUnitsInRead_, countsOfFlankingReads_, countsOfInrepeatReads_); + + std::cerr << countsOfFlankingReads_ << " -> " << countsOfFlankingReadsForShortRepeatGenotyper << std::endl; + + ShortRepeatGenotyper shortRepeatGenotyper(repeatUnitLen_, maxNumUnitsInRead_, propCorrectMolecules_); + + const int repeatReadCount + = ::ehunter::countFullLengthRepeatReads(maxNumUnitsInRead_, countsOfFlankingReads_, countsOfInrepeatReads_); + + if (expectedAlleleCount_ == AlleleCount::kOne) + { + RepeatGenotype genotype = shortRepeatGenotyper.genotypeRepeatWithOneAllele( + countsOfFlankingReadsForShortRepeatGenotyper, countsOfSpanningReads_, alleleSizeCandidates); + + const bool isSpanningAllele = countsOfSpanningReads_.countOf(genotype.longAlleleSizeInUnits()) != 0; + + if (!isSpanningAllele && repeatReadCount != 0) + { + extendGenotypeWhenOneAlleleIsRepeat(genotype, repeatReadCount); + } + else if (!isSpanningAllele) + { + extendGenotypeWhenOneAlleleIsFlanking(genotype); + } + else + { + assert(countsOfSpanningReads_.countOf(genotype.longAlleleSizeInUnits())); + } + + return genotype; + } + + assert(expectedAlleleCount_ == AlleleCount::kTwo); + + RepeatGenotype genotype = shortRepeatGenotyper.genotypeRepeatWithTwoAlleles( + countsOfFlankingReadsForShortRepeatGenotyper, countsOfSpanningReads_, alleleSizeCandidates); + + const bool shortAlleleIsSpanning = countsOfSpanningReads_.countOf(genotype.shortAlleleSizeInUnits()) != 0; + const bool longAlleleIsSpanning = countsOfSpanningReads_.countOf(genotype.longAlleleSizeInUnits()) != 0; + // const int repeatReadCount = countFullLengthRepeatReads(); + + if (!longAlleleIsSpanning && !shortAlleleIsSpanning && repeatReadCount != 0) + { + extendGenotypeWhenBothAllelesAreRepeat(genotype, repeatReadCount); + } + else if (!longAlleleIsSpanning && repeatReadCount != 0) + { + extendGenotypeWhenOneAlleleIsRepeat(genotype, repeatReadCount); + } + else if (shortAlleleIsSpanning && longAlleleIsSpanning) + { + // Nothing needs to be done. + } + else if (shortAlleleIsSpanning) + { + // assert(countsOfFlankingReads_.countOf(genotype.longAlleleSizeInUnits())); + extendGenotypeWhenOneAlleleIsFlanking(genotype); + } + else + { + // Both alleles must be flanking. + // assert(countsOfFlankingReads_.countOf(genotype.shortAlleleSizeInUnits())); + // assert(countsOfFlankingReads_.countOf(genotype.longAlleleSizeInUnits())); + + extendGenotypeWhenBothAllelesAreFlanking(genotype); + } + + return genotype; +} + +void RepeatGenotyper::extendGenotypeWhenBothAllelesAreFlanking(RepeatGenotype& genotype) const +{ + int32_t flankingAlleleSize, flankingAlleleCiLower, flankingAlleleCiUpper; + estimateFlankingAlleleSize(flankingAlleleSize, flankingAlleleCiLower, flankingAlleleCiUpper); + + // genotype.setLongAlleleSizeInUnits(flankingAlleleSize); + flankingAlleleCiLower = std::min(genotype.shortAlleleSizeInUnits(), flankingAlleleCiLower); + flankingAlleleCiUpper = std::max(genotype.longAlleleSizeInUnits(), flankingAlleleCiUpper); + + genotype.setLongAlleleSizeInUnitsCi(flankingAlleleCiLower, flankingAlleleCiUpper); + + // genotype.setShortAlleleSizeInUnits(flankingAlleleSize); + genotype.setShortAlleleSizeInUnitsCi(flankingAlleleCiLower, flankingAlleleCiUpper); +} + +void RepeatGenotyper::extendGenotypeWhenOneAlleleIsFlanking(RepeatGenotype& genotype) const +{ + int32_t flankingAlleleSize, flankingAlleleCiLower, flankingAlleleCiUpper; + estimateFlankingAlleleSize(flankingAlleleSize, flankingAlleleCiLower, flankingAlleleCiUpper); + + // genotype.setLongAlleleSizeInUnits(flankingAlleleSize); + flankingAlleleCiLower = std::min(genotype.shortAlleleSizeInUnits(), flankingAlleleCiLower); + flankingAlleleCiUpper = std::max(genotype.longAlleleSizeInUnits(), flankingAlleleCiUpper); + + genotype.setLongAlleleSizeInUnitsCi(flankingAlleleCiLower, flankingAlleleCiUpper); +} + +void RepeatGenotyper::extendGenotypeWhenOneAlleleIsRepeat(RepeatGenotype& genotype, int numRepeatReads) const +{ + assert(numRepeatReads); + + int32_t longAlleleSize, longAlleleSizeLowerBound, longAlleleSizeUpperBound; + estimateRepeatAlleleSize(numRepeatReads, longAlleleSize, longAlleleSizeLowerBound, longAlleleSizeUpperBound); + genotype.setLongAlleleSizeInUnits(longAlleleSize); + genotype.setLongAlleleSizeInUnitsCi(longAlleleSizeLowerBound, longAlleleSizeUpperBound); +} + +void RepeatGenotyper::extendGenotypeWhenBothAllelesAreRepeat(RepeatGenotype& genotype, int numRepeatReads) const +{ + assert(numRepeatReads); + + // Calculate CI for the long allele. + int32_t longAlleleSize, longAlleleSizeLowerBound, longAlleleSizeUpperBound; + estimateRepeatAlleleSize(numRepeatReads, longAlleleSize, longAlleleSizeLowerBound, longAlleleSizeUpperBound); + genotype.setLongAlleleSizeInUnits(longAlleleSize); + genotype.setLongAlleleSizeInUnitsCi(longAlleleSizeLowerBound, longAlleleSizeUpperBound); + + // Calculate CI for the short allele. + int32_t shortAlleleSize, shortAlleleSizeLowerBound, shortAlleleSizeUpperBound; + estimateRepeatAlleleSize(numRepeatReads / 2, shortAlleleSize, shortAlleleSizeLowerBound, shortAlleleSizeUpperBound); + genotype.setShortAlleleSizeInUnits(shortAlleleSize); + genotype.setShortAlleleSizeInUnitsCi(shortAlleleSizeLowerBound, shortAlleleSizeUpperBound); +} + +void RepeatGenotyper::estimateRepeatAlleleSize( + int32_t numIrrs, int32_t& size, int32_t& sizeLowerBound, int32_t& sizeUpperBound) const +{ + const int32_t readLength = repeatUnitLen_ * maxNumUnitsInRead_; + estimateRepeatLen(numIrrs, readLength, haplotypeDepth_, size, sizeLowerBound, sizeUpperBound); + + size /= repeatUnitLen_; + sizeLowerBound /= repeatUnitLen_; + sizeUpperBound /= repeatUnitLen_; +} + +void RepeatGenotyper::estimateFlankingAlleleSize( + int32_t& flankingAlleleSize, int32_t& flankingAlleleCiLower, int32_t& flankingAlleleCiUpper) const +{ + const int32_t readLength = repeatUnitLen_ * maxNumUnitsInRead_; + + const vector& spanningSizes = countsOfSpanningReads_.getElementsWithNonzeroCounts(); + const int32_t longestSpanning + = spanningSizes.empty() ? 0 : *std::max_element(spanningSizes.begin(), spanningSizes.end()); + + int32_t numFlankingReadsLongerThanSpanning = 0; + for (int32_t repeatSize : countsOfFlankingReads_.getElementsWithNonzeroCounts()) + { + if (repeatSize > longestSpanning) + { + numFlankingReadsLongerThanSpanning += countsOfFlankingReads_.countOf(repeatSize); + } + } + + // Haplotype depth should be twice as high because flanking reads come from both flanks of the repeat. + estimateRepeatLen( + numFlankingReadsLongerThanSpanning, readLength, 2 * haplotypeDepth_, flankingAlleleSize, flankingAlleleCiLower, + flankingAlleleCiUpper); + + // estimateRepeatLen adds read length to size estimates so we need to subtract it out. + flankingAlleleSize -= readLength; + flankingAlleleCiLower -= readLength; + flankingAlleleCiUpper -= readLength; + + flankingAlleleSize = flankingAlleleSize / repeatUnitLen_ + longestSpanning + 1; + flankingAlleleCiLower = flankingAlleleCiLower / repeatUnitLen_ + longestSpanning + 1; + flankingAlleleCiUpper = flankingAlleleCiUpper / repeatUnitLen_ + longestSpanning + 1; + + // Repeat must be at least at long as the longest flanking read. + const vector& flankingSizes = countsOfFlankingReads_.getElementsWithNonzeroCounts(); + const int32_t longestFlanking = *std::max_element(flankingSizes.begin(), flankingSizes.end()); + + flankingAlleleCiLower = std::max(flankingAlleleCiLower, longestFlanking); + flankingAlleleSize = std::max(flankingAlleleSize, longestFlanking); + flankingAlleleCiUpper = std::max(flankingAlleleCiUpper, longestFlanking); + + // Repeat estimated from flanking reads cannot be longer than the read length. + flankingAlleleCiLower = std::min(flankingAlleleCiLower, maxNumUnitsInRead_); + flankingAlleleSize = std::min(flankingAlleleSize, maxNumUnitsInRead_); + flankingAlleleCiUpper = std::min(flankingAlleleCiUpper, maxNumUnitsInRead_); +} + +int RepeatGenotyper::countFullLengthRepeatReads() const +{ + const double kMinProportionAlignedBases = 0.93; + const int fullLengthSizeCutoff = static_cast(std::round(maxNumUnitsInRead_ * kMinProportionAlignedBases)); + + int repeatReadCount = 0; + for (int numUnits : countsOfInrepeatReads_.getElementsWithNonzeroCounts()) + { + if (numUnits >= fullLengthSizeCutoff) + { + repeatReadCount += countsOfInrepeatReads_.countOf(numUnits); + } + } + + return repeatReadCount; +} + +static int depthBasedCountOfInrepeatReads( + int maxNumUnitsInRead, const CountTable& countsOfFlankingReads, const CountTable& countsOfInrepeatReads) +{ + const int kNumFlanks = 2; + const double kPropLowConfidenceFlank = 0.1; + const double kPropHighConfidenceFlank = 1.0 - kPropLowConfidenceFlank; + + const int maxUnitsFromLowConfidenceFlank + = static_cast(std::round(maxNumUnitsInRead * kPropHighConfidenceFlank)); + + int numPutativeIrrs = 0; + for (const auto& numUnitsSpannedAndCount : countsOfInrepeatReads) + { + int numUnitsSpanned = numUnitsSpannedAndCount.first; + int readCount = numUnitsSpannedAndCount.second; + + if (numUnitsSpanned >= maxUnitsFromLowConfidenceFlank) + { + numPutativeIrrs += readCount; + } + } + + int numFlankingReads = 0; + for (const auto& numUnitsSpannedAndCount : countsOfFlankingReads) + { + int numUnitsSpanned = numUnitsSpannedAndCount.first; + int readCount = numUnitsSpannedAndCount.second; + + if (numUnitsSpanned < maxUnitsFromLowConfidenceFlank) + { + numFlankingReads += readCount; + } + } + + const double estimatedDepth = numFlankingReads / (kNumFlanks * kPropHighConfidenceFlank); + const double expectedNumLowconfidenceFlankingReads = kNumFlanks * kPropLowConfidenceFlank * estimatedDepth; + + boost::math::poisson_distribution<> lowconfidenceFlankingDistro(expectedNumLowconfidenceFlankingReads); + const double probability = boost::math::cdf(lowconfidenceFlankingDistro, numPutativeIrrs); + + const double kProbabilityCutoff = 0.95; + return (probability >= kProbabilityCutoff ? numPutativeIrrs : 0); +} + +static int lengthBasedCountOfInrepeatReads(int maxNumUnitsInRead, const CountTable& countsOfInrepeatReads) +{ + const double kPropForFullLength = 0.96; + const int minNumUnitsForFullLength = static_cast(std::round(maxNumUnitsInRead * kPropForFullLength)); + + int numIrrs = 0; + for (const auto& numUnitsSpannedAndCount : countsOfInrepeatReads) + { + int numUnitsSpanned = numUnitsSpannedAndCount.first; + int readCount = numUnitsSpannedAndCount.second; + + if (numUnitsSpanned >= minNumUnitsForFullLength) + { + numIrrs += readCount; + } + } + + return numIrrs; +} + +int countFullLengthRepeatReads( + int maxNumUnitsInRead, const CountTable& countsOfFlankingReads, const CountTable& countsOfInrepeatReads) +{ + const int lengthBasedCount = lengthBasedCountOfInrepeatReads(maxNumUnitsInRead, countsOfInrepeatReads); + const int depthBasedCount + = depthBasedCountOfInrepeatReads(maxNumUnitsInRead, countsOfFlankingReads, countsOfInrepeatReads); + + return std::max(lengthBasedCount, depthBasedCount); +} + +} diff --git a/genotyping/RepeatGenotyper.hh b/genotyping/RepeatGenotyper.hh new file mode 100755 index 0000000..2eef2fa --- /dev/null +++ b/genotyping/RepeatGenotyper.hh @@ -0,0 +1,88 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Mitch Bekritsky , Richard Shaw +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include +#include + +#include + +#include "common/Common.hh" +#include "common/CountTable.hh" +#include "common/Parameters.hh" +#include "genotyping/RepeatGenotype.hh" +#include "region_spec/LocusSpecification.hh" + +namespace ehunter +{ + +class RepeatGenotyper +{ +public: + RepeatGenotyper( + double haplotypeDepth, AlleleCount expectedAlleleCount, int32_t repeatUnitLen, int32_t maxNumUnitsInRead, + double propCorrectMolecules, const CountTable& countsOfSpanningReads, const CountTable& countsOfFlankingReads, + const CountTable& countsOfRepeatReads) + : expectedAlleleCount_(expectedAlleleCount) + , repeatUnitLen_(repeatUnitLen) + , maxNumUnitsInRead_(maxNumUnitsInRead) + , haplotypeDepth_(haplotypeDepth) + , propCorrectMolecules_(propCorrectMolecules) + , countsOfSpanningReads_(countsOfSpanningReads) + , countsOfFlankingReads_(countsOfFlankingReads) + , countsOfInrepeatReads_(countsOfRepeatReads) + { + } + + boost::optional genotypeRepeat(const std::vector& alleleSizeCandidates) const; + +private: + void extendGenotypeWhenBothAllelesAreRepeat(RepeatGenotype& genotype, int numRepeatReads) const; + void extendGenotypeWhenOneAlleleIsRepeat(RepeatGenotype& genotype, int numRepeatReads) const; + void extendGenotypeWhenBothAllelesAreFlanking(RepeatGenotype& genotype) const; + void extendGenotypeWhenOneAlleleIsFlanking(RepeatGenotype& genotype) const; + + void estimateFlankingAlleleSize( + int32_t& flankingAlleleSize, int32_t& flankingAlleleCiLower, int32_t& flankingAlleleCiUpper) const; + void estimateRepeatAlleleSize( + int32_t numIrrs, int32_t& flankingAlleleSize, int32_t& flankingAlleleCiLower, + int32_t& flankingAlleleCiUpper) const; + + int countFullLengthRepeatReads() const; + + const AlleleCount expectedAlleleCount_; + const int32_t repeatUnitLen_; + const int32_t maxNumUnitsInRead_; + const double haplotypeDepth_; + const double propCorrectMolecules_; + const CountTable countsOfSpanningReads_; + const CountTable countsOfFlankingReads_; + const CountTable countsOfInrepeatReads_; +}; + +int countFullLengthRepeatReads( + int maxNumUnitsInRead, const CountTable& countsOfFlankingReads, const CountTable& countsOfInrepeatReads); + +} diff --git a/genotyping/RepeatLength.cpp b/genotyping/RepeatLength.cpp new file mode 100755 index 0000000..124423f --- /dev/null +++ b/genotyping/RepeatLength.cpp @@ -0,0 +1,85 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Mitch Bekritsky , Richard Shaw +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "genotyping/RepeatLength.hh" + +#include +#include +#include + +#include +#include +#include +#include + +using boost::lexical_cast; +using boost::math::binomial_distribution; +using boost::math::cdf; +using std::vector; + +namespace ehunter +{ + +// Given the observed IRR number, haplotype depth, and read length +// estimate repeat length (in nt) and the associated confidence +// interval. +void estimateRepeatLen( + int32_t numIrrs, int32_t readLen, double hapDepth, int32_t& lenEstimate, int32_t& lowerBound, int32_t& upperBound) +{ + const double probReadStart = hapDepth / readLen; + const int mlEstimate = static_cast(std::round(numIrrs / probReadStart)); + + const int32_t kSeed = 42; + std::mt19937 gen(kSeed); + + // Perform mlEstimate trials with probability of succeed probReadStart. + std::binomial_distribution<> binom(mlEstimate, probReadStart); + + vector bootstrapSamples; + const int32_t kNumSamples = 10000; + for (int32_t sampleIndex = 0; sampleIndex < kNumSamples; ++sampleIndex) + { + const int32_t sampledNumIrrs = binom(gen); + const int32_t bootstrapSample = static_cast(std::round(sampledNumIrrs / probReadStart)) - mlEstimate; + bootstrapSamples.push_back(bootstrapSample); + } + + std::sort(bootstrapSamples.begin(), bootstrapSamples.end()); + + // Compute 2.5% and 97.5% quantiles. + const int32_t lowerQuantile = *(bootstrapSamples.begin() + static_cast(bootstrapSamples.size() * 0.025)); + const int32_t upperQuantile = *(bootstrapSamples.begin() + static_cast(bootstrapSamples.size() * 0.975)); + + lenEstimate = mlEstimate + readLen; + + lowerBound = 0; + if (mlEstimate - upperQuantile > 0) + { + lowerBound = static_cast(mlEstimate - upperQuantile); + } + lowerBound += readLen; + + assert(mlEstimate - lowerQuantile + readLen >= 0); + upperBound = static_cast(mlEstimate - lowerQuantile + readLen); +} + +} diff --git a/include/version.h b/genotyping/RepeatLength.hh old mode 100644 new mode 100755 similarity index 82% rename from include/version.h rename to genotyping/RepeatLength.hh index 0d6bea8..dc91ba9 --- a/include/version.h +++ b/genotyping/RepeatLength.hh @@ -22,8 +22,12 @@ #pragma once -#include +#include + +namespace ehunter +{ + +void estimateRepeatLen( + int32_t numIrrs, int32_t readLen, double hapDepth, int32_t& lenEstimate, int32_t& lowerBound, int32_t& upperBound); -namespace ehunter { -const std::string kProgramVersion = "Expansion Hunter v2.5.6"; } diff --git a/genotyping/ShortRepeatGenotyper.cpp b/genotyping/ShortRepeatGenotyper.cpp new file mode 100755 index 0000000..5dc28eb --- /dev/null +++ b/genotyping/ShortRepeatGenotyper.cpp @@ -0,0 +1,211 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Mitch Bekritsky , Richard Shaw +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "genotyping/ShortRepeatGenotyper.hh" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/Common.hh" + +using std::map; +using std::pair; +using std::string; +using std::to_string; +using std::vector; + +namespace ehunter +{ + +QuantifierOfMoleculesGeneratedByAllele::QuantifierOfMoleculesGeneratedByAllele( + int32_t allele_size_in_units, int32_t max_repeat_size_in_units, double prop_correct_molecules) + : allele_size_in_units_(allele_size_in_units) + , max_repeat_size_in_units_(max_repeat_size_in_units) + , prop_correct_molecules_(prop_correct_molecules) + , max_deviation_(5) +{ + norm_factor_ = 0; + for (int num_units = 0; num_units <= max_repeat_size_in_units_; ++num_units) + { + const double deviation = std::abs(num_units - allele_size_in_units_); + + if (deviation < max_deviation_) + { + norm_factor_ += prop_correct_molecules_ * pow(1 - prop_correct_molecules_, deviation); + } + else + { + norm_factor_ += prop_correct_molecules_ * pow(1 - prop_correct_molecules_, max_deviation_); + } + } +} + +double QuantifierOfMoleculesGeneratedByAllele::propMoleculesOfGivenSize(int size_in_units) const +{ + if (size_in_units < 0 || max_repeat_size_in_units_ < size_in_units) + { + throw std::logic_error( + "size_in_units = " + to_string(size_in_units) + " is outside of allowed range (0," + + to_string(max_repeat_size_in_units_) + ")"); + } + double deviation = std::abs(size_in_units - allele_size_in_units_); + deviation = deviation < max_deviation_ ? deviation : max_deviation_; + + const double prop = prop_correct_molecules_ * pow(1 - prop_correct_molecules_, deviation); + + return prop / norm_factor_; +} + +double QuantifierOfMoleculesGeneratedByAllele::propMoleculesShorterThan(int32_t size_upper_bound_in_units) const +{ + double total_prop = 0; + for (int repeat_size_in_units = 0; repeat_size_in_units != size_upper_bound_in_units; ++repeat_size_in_units) + { + total_prop += propMoleculesOfGivenSize(repeat_size_in_units); + } + + return total_prop; +} + +double QuantifierOfMoleculesGeneratedByAllele::propMoleculesAtLeast(int32_t size_lower_bound_in_units) const +{ + return 1.0 - propMoleculesShorterThan(size_lower_bound_in_units); +} + +double ShortRepeatGenotypeLikelihoodEstimator::CalcFlankingLoglik(int32_t num_units_in_read) const +{ + double loglik_sum = 0.0; + for (const auto& allele_quantifier : allele_quantifiers_) + { + loglik_sum += allele_quantifier.propMoleculesAtLeast(num_units_in_read); + } + + const double gen_flanking_lik = loglik_sum / allele_quantifiers_.size(); + return log(gen_flanking_lik); +} + +double ShortRepeatGenotypeLikelihoodEstimator::CalcSpanningLoglik(int32_t num_units_in_read) const +{ + double loglik_sum = 0.0; + for (const auto& allele_quantifier : allele_quantifiers_) + { + loglik_sum += allele_quantifier.propMoleculesOfGivenSize(num_units_in_read); + } + const double gen_spanning_lik = loglik_sum / allele_quantifiers_.size(); + return log(gen_spanning_lik); +} + +double ShortRepeatGenotypeLikelihoodEstimator::CalcLogLik( + const CountTable& counts_of_flanking_reads, const CountTable& counts_of_spanning_reads) const +{ + double genotype_loglik = 0; + + for (const auto& kv : counts_of_flanking_reads) + { + const int num_units = kv.first; + int read_count = kv.second; + + int adjusted_read_count = read_count; + if (num_units == max_repeat_size_in_units_) + { + adjusted_read_count = std::min(read_count, 5); + } + + genotype_loglik += adjusted_read_count * CalcFlankingLoglik(num_units); + } + + for (const auto& kv : counts_of_spanning_reads) + { + const int num_units = kv.first; + const int read_count = kv.second; + genotype_loglik += read_count * CalcSpanningLoglik(num_units); + } + + return genotype_loglik; +} + +RepeatGenotype ShortRepeatGenotyper::genotypeRepeatWithOneAllele( + const CountTable& flanking_size_count, const CountTable& spanning_size_count, + const vector& allele_size_candidates) const +{ + vector most_likely_genotype; + double max_loglik = std::numeric_limits::lowest(); + + for (int32_t candidate_allele_size : allele_size_candidates) + { + ShortRepeatGenotypeLikelihoodEstimator likelihood_estimator( + max_repeat_size_in_units_, prop_correct_molecules_, { candidate_allele_size }); + const double cur_loglik = likelihood_estimator.CalcLogLik(flanking_size_count, spanning_size_count); + + if (max_loglik < cur_loglik) + { + max_loglik = cur_loglik; + most_likely_genotype = { candidate_allele_size }; + } + } + + return RepeatGenotype(repeatUnitLen_, most_likely_genotype); +} + +RepeatGenotype ShortRepeatGenotyper::genotypeRepeatWithTwoAlleles( + const CountTable& flanking_size_count, const CountTable& spanning_size_count, + const vector& allele_size_candidates) const +{ + vector most_likely_genotype; + double max_loglik = std::numeric_limits::lowest(); + + for (int32_t candidate_allele_size_a : allele_size_candidates) + { + for (int32_t candidate_allele_size_b : allele_size_candidates) + { + if (candidate_allele_size_a > candidate_allele_size_b) + { + continue; + } + + const vector candidate_genotype = { candidate_allele_size_a, candidate_allele_size_b }; + + // ShortRepeatGenotyper genotyper(max_repeat_size_in_units_, prop_correct_molecules_); + ShortRepeatGenotypeLikelihoodEstimator likelihood_estimator( + max_repeat_size_in_units_, prop_correct_molecules_, candidate_genotype); + const double cur_loglik = likelihood_estimator.CalcLogLik(flanking_size_count, spanning_size_count); + + if (max_loglik < cur_loglik) + { + max_loglik = cur_loglik; + most_likely_genotype = candidate_genotype; + } + } + } + + return RepeatGenotype(repeatUnitLen_, most_likely_genotype); +} + +} diff --git a/genotyping/ShortRepeatGenotyper.hh b/genotyping/ShortRepeatGenotyper.hh new file mode 100755 index 0000000..59d24b1 --- /dev/null +++ b/genotyping/ShortRepeatGenotyper.hh @@ -0,0 +1,104 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Mitch Bekritsky , Richard Shaw +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +// Defines classes and methods for GenotypeRepeat and haplotype likelihood +// calculations. + +#pragma once + +#include +#include +#include +#include + +#include "common/Common.hh" +#include "common/CountTable.hh" +#include "genotyping/RepeatGenotype.hh" + +namespace ehunter +{ + +class QuantifierOfMoleculesGeneratedByAllele +{ +public: + QuantifierOfMoleculesGeneratedByAllele( + int32_t allele_size_in_units, int32_t max_repeat_size_in_units, double prop_correct_molecules); + double propMoleculesOfGivenSize(int32_t size_in_units) const; + double propMoleculesShorterThan(int32_t size_upper_bound_in_units) const; + double propMoleculesAtLeast(int32_t size_lower_bound_in_units) const; + int32_t alleleSizeInUnits() const { return allele_size_in_units_; } + +private: + int32_t allele_size_in_units_; + int32_t max_repeat_size_in_units_; + double prop_correct_molecules_; + double norm_factor_; + int max_deviation_; +}; + +class ShortRepeatGenotypeLikelihoodEstimator +{ +public: + ShortRepeatGenotypeLikelihoodEstimator( + int32_t max_repeat_size_in_units, double prop_correct_molecules, std::vector allele_sizes_in_units) + : max_repeat_size_in_units_(max_repeat_size_in_units) + { + for (int32_t allele_size_in_units : allele_sizes_in_units) + { + allele_quantifiers_.emplace_back(allele_size_in_units, max_repeat_size_in_units, prop_correct_molecules); + } + } + + double CalcFlankingLoglik(int32_t repeat_size_in_units) const; + double CalcSpanningLoglik(int32_t repeat_size_in_units) const; + double CalcLogLik(const CountTable& counts_of_flanking_reads, const CountTable& counts_of_spanning_reads) const; + +private: + int max_repeat_size_in_units_; + std::vector allele_quantifiers_; +}; + +class ShortRepeatGenotyper +{ +public: + ShortRepeatGenotyper(int32_t repeatUnitLen, int32_t max_repeat_size_in_units, double prop_correct_molecules) + : repeatUnitLen_(repeatUnitLen) + , max_repeat_size_in_units_(max_repeat_size_in_units) + , prop_correct_molecules_(prop_correct_molecules) + { + } + + RepeatGenotype genotypeRepeatWithOneAllele( + const CountTable& flanking_size_count, const CountTable& spanning_size_count, + const std::vector& allele_size_candidates) const; + + RepeatGenotype genotypeRepeatWithTwoAlleles( + const CountTable& flanking_size_count, const CountTable& spanning_size_count, + const std::vector& allele_size_candidates) const; + +private: + int32_t repeatUnitLen_; + int32_t max_repeat_size_in_units_; + double prop_correct_molecules_; +}; + +} diff --git a/include/vcf_output.h b/genotyping/SmallVariantGenotype.cpp old mode 100644 new mode 100755 similarity index 58% rename from include/vcf_output.h rename to genotyping/SmallVariantGenotype.cpp index a07e469..1330dec --- a/include/vcf_output.h +++ b/genotyping/SmallVariantGenotype.cpp @@ -1,9 +1,8 @@ // // Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. +// Copyright (c) 2018 Illumina, Inc. // // Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw // Concept: Michael Eberle // // This program is free software: you can redistribute it and/or modify @@ -20,21 +19,36 @@ // along with this program. If not, see . // -#pragma once +#include "genotyping/SmallVariantGenotype.hh" -#include -#include -#include -#include +using std::ostream; -#include "common/parameters.h" -#include "common/repeat_spec.h" -#include "include/region_findings.h" +namespace ehunter +{ -namespace ehunter { +ostream& operator<<(ostream& out, const SmallVariantGenotype& genotype) +{ -void WriteVcf(const Parameters ¶meters, - const std::map &repeat_specs, - const std::vector &sample_findings, - std::ostream &out); + if (genotype.numAlleles() == 1) + { + out << (genotype.isHomRef() ? "0" : "1"); + } + else + { + if (genotype.isHomRef()) + { + out << "0/0"; + } + else if (genotype.isHomAlt()) + { + out << "1/1"; + } + else + { + out << "0/1"; + } + } + + return out; +} } diff --git a/genotyping/SmallVariantGenotype.hh b/genotyping/SmallVariantGenotype.hh new file mode 100755 index 0000000..0604692 --- /dev/null +++ b/genotyping/SmallVariantGenotype.hh @@ -0,0 +1,60 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include + +namespace ehunter +{ + +enum class AlleleType +{ + kRef, + kAlt +}; + +class SmallVariantGenotype +{ +public: + SmallVariantGenotype(AlleleType firstAlleleType, AlleleType secondAlleleType) + { + alleleTypes_ = { firstAlleleType, secondAlleleType }; + } + SmallVariantGenotype(AlleleType alleleType) { alleleTypes_ = { alleleType }; } + + int numAlleles() const { return alleleTypes_.size(); } + AlleleType firstAlleleType() const { return alleleTypes_.front(); } + AlleleType secondAlleleType() const { return alleleTypes_.back(); } + + bool isHomRef() const { return firstAlleleType() == AlleleType::kRef && secondAlleleType() == AlleleType::kRef; } + bool isHomAlt() const { return firstAlleleType() == AlleleType::kAlt && secondAlleleType() == AlleleType::kAlt; } + + bool operator==(const SmallVariantGenotype& other) const { return alleleTypes_ == other.alleleTypes_; } + +private: + std::vector alleleTypes_; +}; + +std::ostream& operator<<(std::ostream& out, const SmallVariantGenotype& genotype); + +} diff --git a/genotyping/SmallVariantGenotyper.cpp b/genotyping/SmallVariantGenotyper.cpp new file mode 100755 index 0000000..9f587ad --- /dev/null +++ b/genotyping/SmallVariantGenotyper.cpp @@ -0,0 +1,110 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Sai Chen +// Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "genotyping/SmallVariantGenotyper.hh" +#include +#include + +using boost::optional; +using boost::math::poisson_distribution; +using std::vector; + +namespace ehunter +{ + +boost::optional SmallVariantGenotyper::genotype(int refCount, int altCount) const +{ + if (expectedAlleleCount_ > 2) + { + throw std::runtime_error( + "Ploidy = " + std::to_string(expectedAlleleCount_) + ", but only ploidy <=2 is supported at this time."); + } + + if (refCount < 0 || altCount < 0) + { + throw std::runtime_error("Invalid read counts: " + std::to_string(refCount) + " " + std::to_string(altCount)); + } + + auto possibleGenotypes = getPossibleGenotypes(expectedAlleleCount_); + + const int totalReadCount = refCount + altCount; + if (totalReadCount == 0) // missing genotype + { + return optional(); + } + + optional mostLikelyGenotype; + double bestLikelihood = -std::numeric_limits::max(); + for (const auto& currentGenotype : possibleGenotypes) + { + const double currentLikelihood = genotypeLikelihood(currentGenotype, refCount, altCount); + if (currentLikelihood > bestLikelihood) + { + bestLikelihood = currentLikelihood; + mostLikelyGenotype = currentGenotype; + } + } + + return mostLikelyGenotype; +} + +vector SmallVariantGenotyper::getPossibleGenotypes(int numAlleles) const +{ + if (numAlleles == 1) + { + return { AlleleType::kRef, AlleleType::kAlt }; + } + else if (numAlleles == 2) + { + return { { AlleleType::kRef, AlleleType::kRef }, + { AlleleType::kRef, AlleleType::kAlt }, + { AlleleType::kAlt, AlleleType::kAlt } }; + } + else + { + throw std::runtime_error("The given number of alleles is not supported: " + std::to_string(numAlleles)); + } +} + +double +SmallVariantGenotyper::genotypeLikelihood(const SmallVariantGenotype& currentGenotype, int refCount, int altCount) const +{ + const poisson_distribution<> errorDistribution(errorRate_); + + const bool isHomozygous = currentGenotype.isHomRef() || currentGenotype.isHomAlt(); + const int copyNumberOfExistingAllele = isHomozygous ? 2 : 1; + const poisson_distribution<> countDistribution(copyNumberOfExistingAllele * haplotypeDepth_); + + double genotypeLikelihood + = currentGenotype.isHomRef() ? log(pdf(errorDistribution, altCount)) : log(pdf(countDistribution, altCount)); + + genotypeLikelihood + += currentGenotype.isHomAlt() ? log(pdf(errorDistribution, refCount)) : log(pdf(countDistribution, refCount)); + + if (std::isinf(genotypeLikelihood)) + { + genotypeLikelihood = -std::numeric_limits::max(); + } + + return genotypeLikelihood; +} + +} diff --git a/genotyping/SmallVariantGenotyper.hh b/genotyping/SmallVariantGenotyper.hh new file mode 100755 index 0000000..686ca03 --- /dev/null +++ b/genotyping/SmallVariantGenotyper.hh @@ -0,0 +1,76 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Sai Chen , +// Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include + +#include "common/Common.hh" +#include "genotyping/SmallVariantGenotype.hh" + +namespace ehunter +{ + +class SmallVariantGenotyper +{ +public: + SmallVariantGenotyper(double haplotypeDepth, AlleleCount expectedAlleleCount) + : haplotypeDepth_(haplotypeDepth) + , expectedAlleleCount_((int)expectedAlleleCount) + { + } + + /** + * return the most likely genotype given the read count in reference and alternative allele + */ + boost::optional genotype(int refCount, int altCount) const; + +private: + /** + * return a vector of all possible genotypes given the number alleles + */ + std::vector getPossibleGenotypes(int numAlleles) const; + + /** + * return genotype likelihood of the given genotype + * @param currentGenotyper the given genotype + * @param readCounts Read count vector for each allele (in order) + */ + double genotypeLikelihood(const SmallVariantGenotype& currentGenotype, int refCount, int altCount) const; + + /** + * expected depth for one allele + */ + double haplotypeDepth_; + + /** + * the expected number of alleles in a genotype (ploidy) + */ + int expectedAlleleCount_; + + /** + * hard-coded parameters + */ + double errorRate_ = 0.05; +}; + +} diff --git a/genotyping/repeat_genotyper.cc b/genotyping/repeat_genotyper.cc deleted file mode 100644 index b557c1b..0000000 --- a/genotyping/repeat_genotyper.cc +++ /dev/null @@ -1,229 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include "genotyping/repeat_genotyper.h" - -#include -#include - -#include "genotyping/repeat_length.h" - -using std::map; -using std::vector; -using std::string; -using std::cerr; -using std::endl; - -namespace ehunter { -void GenotypeRepeat(const Parameters ¶meters, const RepeatSpec &repeat_spec, - int max_num_units_in_read, double prop_correct_molecules, - double hap_depth, int read_len, - const vector &haplotype_candidates, - const map &flanking_size_count, - const map &spanning_size_count, - GenotypeType genotype_type, RepeatGenotype &genotype) { - GenotypeShortRepeat(max_num_units_in_read, prop_correct_molecules, hap_depth, - read_len, haplotype_candidates, flanking_size_count, - spanning_size_count, genotype_type, genotype); - - const int unit_len = repeat_spec.units[0].length(); - const double haplotype_depth = parameters.depth() / 2; - - for (RepeatAllele &allele : genotype) { - if (allele.type_ == ReadType::kSpanning) { - allele.ci_.lower_bound_ = allele.size_; - allele.ci_.upper_bound_ = allele.size_; - } - } - - if (genotype_type == GenotypeType::kDiploid) { - RepeatAllele &short_allele = genotype.front(); - RepeatAllele &long_allele = genotype.back(); - assert(short_allele.size_ <= long_allele.size_); - - if (short_allele.type_ == ReadType::kInrepeat && - long_allele.type_ == ReadType::kInrepeat) { - assert(flanking_size_count.at(max_num_units_in_read) == - long_allele.num_supporting_reads_); - int num_irrs = long_allele.num_supporting_reads_; - assert(num_irrs != 0); - - // Calculate CI for the short allele. - int short_allele_size, short_allele_size_ci_lower, - short_allele_size_ci_upper; - - EstimateRepeatLen(num_irrs / 2, parameters.read_len(), haplotype_depth, - short_allele_size, short_allele_size_ci_lower, - short_allele_size_ci_upper); - - short_allele_size /= unit_len; - short_allele_size_ci_lower /= unit_len; - short_allele_size_ci_upper /= unit_len; - - // Calculate CI for the long allele. - int long_allele_size, long_allele_size_ci_lower, - long_allele_size_ci_upper; - - EstimateRepeatLen(num_irrs, parameters.read_len(), haplotype_depth, - long_allele_size, long_allele_size_ci_lower, - long_allele_size_ci_upper); - - long_allele_size /= unit_len; - long_allele_size_ci_lower /= unit_len; - long_allele_size_ci_upper /= unit_len; - - short_allele.size_ = short_allele_size; - short_allele.ci_.lower_bound_ = max_num_units_in_read; - short_allele.ci_.upper_bound_ = short_allele_size_ci_upper; - - long_allele.size_ = long_allele_size; - long_allele.ci_.lower_bound_ = short_allele_size_ci_lower; - long_allele.ci_.upper_bound_ = long_allele_size_ci_upper; - } else if (long_allele.type_ == ReadType::kInrepeat) { - assert(short_allele.type_ == ReadType::kSpanning); - - int num_irrs = flanking_size_count.at(max_num_units_in_read); - assert(num_irrs != 0); - int long_allele_size, long_allele_size_ci_lower, - long_allele_size_ci_upper; - EstimateRepeatLen(num_irrs, parameters.read_len(), haplotype_depth, - long_allele_size, long_allele_size_ci_lower, - long_allele_size_ci_upper); - - long_allele_size /= unit_len; - long_allele_size_ci_lower /= unit_len; - long_allele_size_ci_upper /= unit_len; - - long_allele.size_ = long_allele_size; - long_allele.ci_.lower_bound_ = long_allele_size_ci_lower; - long_allele.ci_.upper_bound_ = long_allele_size_ci_upper; - } else if (long_allele.type_ == ReadType::kFlanking) { - assert(short_allele.type_ == ReadType::kSpanning || - short_allele.type_ == ReadType::kFlanking); - const int longest_flanking = long_allele.size_; - int longest_spanning = 0; - if (short_allele.type_ == ReadType::kSpanning) { - longest_spanning = short_allele.size_; - } - int len_estimate = 0; - int lower_bound = 0; - int upper_bound = 0; - // Haplotype depth should be twice as high because flanking reads - // are coming from both flanks. - EstimateRepeatLen(long_allele.num_supporting_reads_, read_len, - 2 * hap_depth, len_estimate, lower_bound, upper_bound); - - // estimateRepeatLen adds read_len to size estimates so we need to - // subtract it. - len_estimate -= read_len; - lower_bound -= read_len; - upper_bound -= read_len; - - len_estimate = len_estimate / unit_len + longest_spanning + 1; - lower_bound = lower_bound / unit_len + longest_spanning + 1; - upper_bound = upper_bound / unit_len + longest_spanning + 1; - - // Repeat must be at least at long as the longest flanking read. - lower_bound = std::max(lower_bound, longest_flanking); - len_estimate = std::max(len_estimate, longest_flanking); - upper_bound = std::max(upper_bound, longest_flanking); - - // Repeat estimated from flanking reads cannot be longer than the read - // length. - const int num_rep_in_read = read_len / unit_len; - lower_bound = std::min(lower_bound, num_rep_in_read); - len_estimate = std::min(len_estimate, num_rep_in_read); - upper_bound = std::min(upper_bound, num_rep_in_read); - - long_allele.size_ = len_estimate; - long_allele.ci_.lower_bound_ = lower_bound; - long_allele.ci_.upper_bound_ = upper_bound; - - if (short_allele.type_ == ReadType::kFlanking) { - short_allele.size_ = long_allele.size_; - short_allele.ci_.lower_bound_ = long_allele.ci_.lower_bound_; - short_allele.ci_.upper_bound_ = long_allele.ci_.upper_bound_; - } - - } else { - // Both alleles must be spanning. - assert(short_allele.type_ == ReadType::kSpanning && - long_allele.type_ == ReadType::kSpanning); - } - } else { - assert(genotype_type == GenotypeType::kHaploid); - RepeatAllele &allele = genotype.front(); - if (allele.type_ == ReadType::kInrepeat) { - int num_irrs = allele.num_supporting_reads_; - assert(num_irrs != 0); - int allele_size, allele_size_ci_lower, allele_size_ci_upper; - EstimateRepeatLen(num_irrs, parameters.read_len(), haplotype_depth, - allele_size, allele_size_ci_lower, - allele_size_ci_upper); - - allele_size /= unit_len; - allele_size_ci_lower /= unit_len; - allele_size_ci_upper /= unit_len; - - allele.size_ = allele_size; - allele.ci_.lower_bound_ = allele_size_ci_lower; - allele.ci_.upper_bound_ = allele_size_ci_upper; - } else if (allele.type_ == ReadType::kFlanking) { - const int longest_flanking = allele.size_; - int longest_spanning = 0; - int len_estimate = 0; - int lower_bound = 0; - int upper_bound = 0; - // Haplotype depth should be twice as high because flanking reads - // are coming from both flanks. - EstimateRepeatLen(allele.num_supporting_reads_, read_len, 2 * hap_depth, - len_estimate, lower_bound, upper_bound); - - // estimateRepeatLen adds read_len to size estimates so we need to - // subtract it. - len_estimate -= read_len; - lower_bound -= read_len; - upper_bound -= read_len; - - len_estimate = len_estimate / unit_len + longest_spanning + 1; - lower_bound = lower_bound / unit_len + longest_spanning + 1; - upper_bound = upper_bound / unit_len + longest_spanning + 1; - - // Repeat must be at least at long as the longest flanking read. - lower_bound = std::max(lower_bound, longest_flanking); - len_estimate = std::max(len_estimate, longest_flanking); - upper_bound = std::max(upper_bound, longest_flanking); - - // Repeat estimated from flanking reads cannot be longer than the read - // length. - const int num_rep_in_read = read_len / unit_len; - lower_bound = std::min(lower_bound, num_rep_in_read); - len_estimate = std::min(len_estimate, num_rep_in_read); - upper_bound = std::min(upper_bound, num_rep_in_read); - - allele.size_ = len_estimate; - allele.ci_.lower_bound_ = lower_bound; - allele.ci_.upper_bound_ = upper_bound; - } - } -} -} // namespace ehunter diff --git a/genotyping/repeat_genotyper.h b/genotyping/repeat_genotyper.h deleted file mode 100644 index 4926d50..0000000 --- a/genotyping/repeat_genotyper.h +++ /dev/null @@ -1,42 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#pragma once - -#include -#include -#include - -#include "common/common.h" -#include "common/parameters.h" -#include "common/repeat_spec.h" -#include "genotyping/short_repeat_genotyper.h" - -namespace ehunter { -void GenotypeRepeat(const Parameters ¶meters, const RepeatSpec &repeat_spec, - int max_num_units_in_read, double prop_correct_molecules, - double hap_depth, int read_len, - const std::vector &haplotype_candidates, - const std::map &flanking_size_count, - const std::map &spanning_size_count, - GenotypeType genotype_type, RepeatGenotype &genotype); -} diff --git a/genotyping/repeat_length.cc b/genotyping/repeat_length.cc deleted file mode 100644 index c1c346c..0000000 --- a/genotyping/repeat_length.cc +++ /dev/null @@ -1,88 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include "genotyping/repeat_length.h" - -#include -using boost::math::cdf; -#include -using boost::math::binomial_distribution; -#include -using boost::lexical_cast; - -#include -#include -using std::cerr; -using std::endl; -#include -#include -using std::vector; - -namespace ehunter { -// Given the observed IRR number, haplotype depth, and read length -// estimate repeat length (in nt) and the associated confidence -// interval. -void EstimateRepeatLen(const int num_irrs, const int read_len, - const double hap_depth, int& len_estimate, - int& lower_bound, int& upper_bound) { - const double prob_read_start = hap_depth / read_len; - const int ml_estimate = - static_cast(std::round(num_irrs / prob_read_start)); - - const unsigned int kSeed = 42; - std::mt19937 gen(kSeed); - - // Perform ml_estimate trials with probability of succeed prob_read_start. - std::binomial_distribution<> binom(ml_estimate, prob_read_start); - - vector bootstrap_samples; - const int kNumSamples = 10000; - for (int n = 0; n < kNumSamples; ++n) { - const int sampled_num_irrs = binom(gen); - const int bootstrap_sample = - static_cast(std::round(sampled_num_irrs / prob_read_start)) - - ml_estimate; - bootstrap_samples.push_back(bootstrap_sample); - } - - std::sort(bootstrap_samples.begin(), bootstrap_samples.end()); - - // Compute 2.5% and 97.5% quantiles. - const int lower_quantile = - *(bootstrap_samples.begin() + - static_cast(bootstrap_samples.size() * 0.025)); - const int upper_quantile = - *(bootstrap_samples.begin() + - static_cast(bootstrap_samples.size() * 0.975)); - - len_estimate = ml_estimate + read_len; - - lower_bound = 0; - if (ml_estimate - upper_quantile > 0) { - lower_bound = (int)(ml_estimate - upper_quantile); - } - lower_bound += read_len; - - assert(ml_estimate - lower_quantile + read_len >= 0); - upper_bound = (int)(ml_estimate - lower_quantile + read_len); -} -} // namespace ehunter diff --git a/genotyping/short_repeat_genotyper.cc b/genotyping/short_repeat_genotyper.cc deleted file mode 100644 index e799e03..0000000 --- a/genotyping/short_repeat_genotyper.cc +++ /dev/null @@ -1,223 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include "genotyping/short_repeat_genotyper.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "common/common.h" - -using std::string; -using std::map; -using std::cerr; -using std::endl; -using std::pair; -using std::vector; - -namespace ehunter { -Allele::Allele(int num_units_haplotype, int max_num_units_in_read, - double prop_correct_molecules) - : num_units_haplotype_(num_units_haplotype), - max_num_units_in_read_(max_num_units_in_read), - prop_correct_molecules_(prop_correct_molecules), - max_deviation_(5) { - const double p = prop_correct_molecules_; - norm_factor_ = 0; - for (int num_units = 0; num_units <= max_num_units_in_read_; ++num_units) { - const double deviation = std::abs(num_units - num_units_haplotype_); - - if (deviation < max_deviation_) { - norm_factor_ += p * pow(1 - p, deviation); - } else { - norm_factor_ += p * pow(1 - p, max_deviation_); - } - } -} - -double Allele::propMolecules(int num_units) const { - if (num_units < 0 || max_num_units_in_read_ < num_units) { - cerr << "ERROR: num_units = " << num_units << endl; - } - const double p = prop_correct_molecules_; - const double deviation = std::abs(num_units - num_units_haplotype_); - - double prop = 0; - if (deviation < max_deviation_) { - prop = p * pow(1 - p, deviation); - } else { - prop = p * pow(1 - p, max_deviation_); - } - - return prop / norm_factor_; -} - -double Allele::propMoleculesShorterThan(int num_units_upper_bound) const { - double total_prop = 0; - for (int num_units = 0; num_units != num_units_upper_bound; ++num_units) { - total_prop += propMolecules(num_units); - } - - return total_prop; -} - -double Allele::propMoleculesAtLeast(int num_units_lower_bound) const { - return 1.0 - propMoleculesShorterThan(num_units_lower_bound); -} - -double ShortRepeatGenotyper::CalcFlankingLoglik(int num_units_in_read) const { - const double prob_start = hap_depth_ / read_len_; - double loglik_sum = 0.0; - for (const Allele &haplotype : alleles) { - loglik_sum += - prob_start * haplotype.propMoleculesAtLeast(num_units_in_read); - } - - const double gen_flanking_lik = loglik_sum / alleles.size(); - return log(gen_flanking_lik); -} - -double ShortRepeatGenotyper::CalcSpanningLoglik(int num_units_in_read) const { - const double prob_start = hap_depth_ / read_len_; - double loglik_sum = 0.0; - for (const Allele &haplotype : alleles) { - loglik_sum += prob_start * haplotype.propMolecules(num_units_in_read); - } - const double gen_spanning_lik = loglik_sum / alleles.size(); - return log(gen_spanning_lik); -} - -double ShortRepeatGenotyper::CalcLogLik( - const map &flanking_size_counts, - const map &spanning_size_counts, - std::vector &support) const { - double genotype_loglik = 0; - support.resize(alleles.size()); - for (int i = 0; i != (int)support.size(); ++i) { - support[i] = {0, 0, 0}; - } - - for (const auto &kv : flanking_size_counts) { - const int num_units = kv.first; - int read_count = kv.second; - - int adjusted_read_count = read_count; - if (num_units == max_num_units_in_read_) { - adjusted_read_count = std::min(read_count, 5); - } - - genotype_loglik += adjusted_read_count * CalcFlankingLoglik(num_units); - - for (int i = 0; i != (int)alleles.size(); ++i) { - const int hap_num_units = alleles[i].num_units(); - if (num_units == max_num_units_in_read_) { - if (hap_num_units == max_num_units_in_read_) { - support[i].set_num_inrepeat(support[i].num_inrepeat() + read_count); - } - } else { - if (num_units <= hap_num_units) { - support[i].set_num_flanking(support[i].num_flanking() + read_count); - } - } - } - } - - for (const auto &kv : spanning_size_counts) { - const int num_units = kv.first; - const int read_count = kv.second; - genotype_loglik += read_count * CalcSpanningLoglik(num_units); - - for (int i = 0; i != (int)alleles.size(); ++i) { - const int hap_num_units = alleles[i].num_units(); - if (num_units == hap_num_units) { - support[i].set_num_spanning(support[i].num_spanning() + read_count); - } - } - } - - return genotype_loglik; -} - -void GenotypeShortRepeat(int max_num_units_in_read, - double prop_correct_molecules, double hap_depth, - int read_len, - const vector &haplotype_candidates, - const map &flanking_size_count, - const map &spanning_size_count, - GenotypeType genotype_type, RepeatGenotype &genotype) { - bool is_first_pass = true; - double max_loglik = std::numeric_limits::lowest(); - RepeatGenotype most_likely_genotype; - - if (genotype_type == GenotypeType::kDiploid) { - for (const RepeatAllele &allele1 : haplotype_candidates) { - for (const RepeatAllele &allele2 : haplotype_candidates) { - if (allele1.size_ > allele2.size_) { - continue; - } - ShortRepeatGenotyper genotype(max_num_units_in_read, - prop_correct_molecules, hap_depth, - read_len, allele1.size_, allele2.size_); - vector genotype_support; - const double cur_loglik = genotype.CalcLogLik( - flanking_size_count, spanning_size_count, genotype_support); - - if (max_loglik < cur_loglik || is_first_pass) { - max_loglik = cur_loglik; - is_first_pass = false; - most_likely_genotype = {allele1, allele2}; - assert(most_likely_genotype.size() == genotype_support.size()); - most_likely_genotype[0].support_ = genotype_support[0]; - most_likely_genotype[1].support_ = genotype_support[1]; - } - } - } - } else if (genotype_type == GenotypeType::kHaploid) { - for (const RepeatAllele &allele : haplotype_candidates) { - ShortRepeatGenotyper genotype(max_num_units_in_read, - prop_correct_molecules, hap_depth, read_len, - allele.size_); - vector genotype_support; - const double cur_loglik = genotype.CalcLogLik( - flanking_size_count, spanning_size_count, genotype_support); - - if (max_loglik < cur_loglik || is_first_pass) { - max_loglik = cur_loglik; - is_first_pass = false; - most_likely_genotype = {allele}; - assert(most_likely_genotype.size() == genotype_support.size()); - most_likely_genotype[0].support_ = genotype_support[0]; - } - } - } else { - throw std::logic_error("ERROR: Unknown GenotypeRepeat type"); - } - genotype = most_likely_genotype; -}; -} // namespace ehunter diff --git a/genotyping/short_repeat_genotyper.h b/genotyping/short_repeat_genotyper.h deleted file mode 100644 index b4eea4d..0000000 --- a/genotyping/short_repeat_genotyper.h +++ /dev/null @@ -1,97 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -// Defines classes and methods for GenotypeRepeat and haplotype likelihood -// calculations. - -#pragma once - -#include -#include -#include - -#include "common/common.h" - -namespace ehunter { -enum class Sex { kMale, kFemale }; -enum class GenotypeType { kHaploid, kDiploid }; - -class Allele { - public: - Allele(int num_units_haplotype, int max_num_units_in_read, - double prop_correct_molecules); - double propMolecules(int num_units_upper_bound) const; - double propMoleculesShorterThan(int num_units_upper_bound) const; - double propMoleculesAtLeast(int num_units_lower_bound) const; - int num_units() const { return num_units_haplotype_; } - - private: - int num_units_haplotype_; - int max_num_units_in_read_; - double prop_correct_molecules_; - double norm_factor_; - int max_deviation_; -}; - -class ShortRepeatGenotyper { - public: - ShortRepeatGenotyper() {} - ShortRepeatGenotyper(int max_num_units_in_read, double prop_correct_molecules, - double hap_depth, int read_len, int num_units_hap1, - int num_units_hap2) - : max_num_units_in_read_(max_num_units_in_read), - hap_depth_(hap_depth), - read_len_(read_len) { - alleles.push_back( - Allele(num_units_hap1, max_num_units_in_read, prop_correct_molecules)); - alleles.push_back( - Allele(num_units_hap2, max_num_units_in_read, prop_correct_molecules)); - } - ShortRepeatGenotyper(int max_num_units_in_read, double prop_correct_molecules, - double hap_depth, int read_len, int num_units_hap) - : max_num_units_in_read_(max_num_units_in_read), - hap_depth_(hap_depth), - read_len_(read_len) { - alleles.push_back( - Allele(num_units_hap, max_num_units_in_read, prop_correct_molecules)); - } - double CalcFlankingLoglik(int num_units_in_read) const; - double CalcSpanningLoglik(int num_units_in_read) const; - double CalcLogLik(const std::map &flanking_size_counts, - const std::map &spanning_size_counts, - std::vector &support) const; - - private: - int max_num_units_in_read_; - double hap_depth_; - int read_len_; - std::vector alleles; -}; - -void GenotypeShortRepeat(int max_num_units_in_read, - double prop_correct_molecules, double hap_depth, - int read_len, - const std::vector &haplotype_candidates, - const std::map &flanking_size_count, - const std::map &spanning_size_count, - GenotypeType genotype_type, RepeatGenotype &genotype); -} // namespace ehunter diff --git a/genotyping/tests/AllelePresenceCheckerTest.cpp b/genotyping/tests/AllelePresenceCheckerTest.cpp new file mode 100755 index 0000000..929ef7f --- /dev/null +++ b/genotyping/tests/AllelePresenceCheckerTest.cpp @@ -0,0 +1,76 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "genotyping/AllelePresenceChecker.hh" + +#include + +#include "gtest/gtest.h" + +#include "common/Common.hh" + +using namespace ehunter; + +TEST(AllelePresenceChecker, ThrowsWithIllegalParameter) +{ + ASSERT_ANY_THROW(new AllelePresenceChecker(0)); + ASSERT_ANY_THROW(new AllelePresenceChecker(15, 1)); + ASSERT_ANY_THROW(new AllelePresenceChecker(15, 0.01, -1)); + ASSERT_ANY_THROW(new AllelePresenceChecker(0)); + + AllelePresenceChecker negCountGenotyper(15.0); + ASSERT_ANY_THROW(negCountGenotyper.check(-1, 20)); +} + +TEST(AllelePresenceChecker, NoReads) +{ + AllelePresenceChecker checker(15.0); + EXPECT_EQ(AllelePresenceStatus::kAbsent, checker.check(0, 0)); +} + +TEST(AllelePresenceChecker, AllelePresent) +{ + AllelePresenceChecker checker(15.0); + EXPECT_EQ(AllelePresenceStatus::kPresent, checker.check(30, 30)); + EXPECT_EQ(AllelePresenceStatus::kPresent, checker.check(10, 45)); + EXPECT_EQ(AllelePresenceStatus::kPresent, checker.check(10, 0)); + EXPECT_EQ(AllelePresenceStatus::kPresent, checker.check(50, 60)); +} + +TEST(AllelePresenceChecker, AlleleAbsent) +{ + AllelePresenceChecker checker(15.0); + EXPECT_EQ(AllelePresenceStatus::kAbsent, checker.check(0, 30)); + EXPECT_EQ(AllelePresenceStatus::kAbsent, checker.check(1, 60)); + EXPECT_EQ(AllelePresenceStatus::kAbsent, checker.check(1, 5)); +} + +TEST(AllelePresenceChecker, NoCall) +{ + AllelePresenceChecker checker(15.0); + EXPECT_EQ(AllelePresenceStatus::kUncertain, checker.check(5, 30)); + EXPECT_EQ(AllelePresenceStatus::kUncertain, checker.check(1, 0)); +} + +TEST(AllelePresenceChecker, HighReads) +{ + AllelePresenceChecker checker(150.0); + EXPECT_EQ(AllelePresenceStatus::kPresent, checker.check(100, 450)); + EXPECT_EQ(AllelePresenceStatus::kAbsent, checker.check(20, 600)); + EXPECT_EQ(AllelePresenceStatus::kUncertain, checker.check(40, 200)); +} diff --git a/genotyping/tests/CMakeLists.txt b/genotyping/tests/CMakeLists.txt new file mode 100755 index 0000000..a9a831c --- /dev/null +++ b/genotyping/tests/CMakeLists.txt @@ -0,0 +1,19 @@ +add_executable(ShortRepeatGenotyperTest ShortRepeatGenotyperTest.cpp) +target_link_libraries(ShortRepeatGenotyperTest genotyping gtest_main) +add_test(NAME ShortRepeatGenotyperTest COMMAND ShortRepeatGenotyperTest) + +add_executable(RepeatGenotyperTest RepeatGenotyperTest.cpp) +target_link_libraries(RepeatGenotyperTest genotyping gtest_main) +add_test(NAME RepeatGenotyperTest COMMAND RepeatGenotyperTest) + +add_executable(RepeatGenotypeTest RepeatGenotypeTest.cpp) +target_link_libraries(RepeatGenotypeTest genotyping gtest_main) +add_test(NAME RepeatGenotypeTest COMMAND RepeatGenotypeTest) + +add_executable(SmallVariantGenotyperTest SmallVariantGenotyperTest.cpp) +target_link_libraries(SmallVariantGenotyperTest genotyping gtest_main) +add_test(NAME SmallVariantGenotyperTest COMMAND SmallVariantGenotyperTest) + +add_executable(AllelePresenceCheckerTest AllelePresenceCheckerTest.cpp) +target_link_libraries(AllelePresenceCheckerTest genotyping gtest_main) +add_test(NAME AllelePresenceCheckerTest COMMAND AllelePresenceCheckerTest) \ No newline at end of file diff --git a/genotyping/tests/GenotypeTest.cpp b/genotyping/tests/GenotypeTest.cpp new file mode 100755 index 0000000..9aaf2ae --- /dev/null +++ b/genotyping/tests/GenotypeTest.cpp @@ -0,0 +1,62 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Mitch Bekritsky , Richard Shaw +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "genotyping/genotype.h" + +using namespace ehunter; + +TEST(InitializingStrAlleles, TypicalAllele_Initialized) +{ + StrAllele allele(3, ReadType::kSpanning); + EXPECT_EQ(3, allele.size()); + EXPECT_EQ(Interval(3, 3), allele.sizeRange()); + EXPECT_EQ(ReadType::kSpanning, allele.supportType()); +} + +TEST(InitializingStrAlleles, AlleleSupportedBySpanningReads_SizeRangeMustEqualToSize) +{ + StrAllele allele(3, ReadType::kSpanning); + EXPECT_NO_THROW(allele.setSizeRange(3, 3)); + EXPECT_ANY_THROW(allele.setSizeRange(4, 4)); + EXPECT_ANY_THROW(allele.setSizeRange(2, 5)); +} + +TEST(InitializingStrAlleles, AlleleSupportedByFlankingOrRepeatReads_SizeRangeMustContainSize) +{ + StrAllele allele(10, ReadType::kFlanking); + EXPECT_NO_THROW(allele.setSizeRange(5, 15)); + EXPECT_ANY_THROW(allele.setSizeRange(11, 12)); + EXPECT_ANY_THROW(allele.setSizeRange(8, 9)); +} + +// TEST(DefiningStrGenotypes, TypicalStrGenotype_Test) +//{ +// StrGenotype genotype = {{3, ReadType::kSpanning}, {50, ReadType::kRepeat}}; + +// EXPECT_EQ(3, genotype.shortAlleleSize()); +// EXPECT_EQ(Interval(3, 3), genotype.shortAlleleCi()); +// EXPECT_EQ(ReadType::kSpanning, genotype.shortAlleleSupportType()); + +// EXPECT_EQ(5, genotype.longAlleleSize()); +// EXPECT_EQ(Interval(5, 5), genotype.longAlleleCi()); +// EXPECT_EQ(ReadType::kRepeat, genotype.longAlleleSupportType()); +//} diff --git a/genotyping/tests/RepeatGenotypeTest.cpp b/genotyping/tests/RepeatGenotypeTest.cpp new file mode 100755 index 0000000..1c76e82 --- /dev/null +++ b/genotyping/tests/RepeatGenotypeTest.cpp @@ -0,0 +1,94 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "genotyping/RepeatGenotype.hh" + +#include "gtest/gtest.h" + +#include "common/Common.hh" + +using std::vector; + +using namespace ehunter; + +TEST(InitializingRepeatGenotype, HaploidGenotype_Initialized) +{ + const int32_t repeatUnitLen = 3; + RepeatGenotype genotype(repeatUnitLen, { 2 }); + + EXPECT_EQ(1, genotype.numAlleles()); + EXPECT_EQ(2, genotype.shortAlleleSizeInUnits()); + EXPECT_EQ(2, genotype.longAlleleSizeInUnits()); +} + +TEST(InitializingRepeatGenotype, DiploidGenotype_Initialized) +{ + RepeatGenotype genotype(3, { 2, 3 }); + + EXPECT_EQ(2, genotype.numAlleles()); + EXPECT_EQ(2, genotype.shortAlleleSizeInUnits()); + EXPECT_EQ(3, genotype.longAlleleSizeInUnits()); +} + +TEST(ExtractingAlleleSizesInBases, DiploidGenotype_SizesExtracted) +{ + RepeatGenotype genotype(3, { 2, 3 }); + + EXPECT_EQ(6, genotype.shortAlleleSizeInBp()); + EXPECT_EQ(9, genotype.longAlleleSizeInBp()); +} + +TEST(InitializingRepeatGenotype, NetherDiploidNorHaploidGenotype_ExceptionThrown) +{ + EXPECT_ANY_THROW(RepeatGenotype genotype(3, {})); + EXPECT_ANY_THROW(RepeatGenotype genotype(3, { 1, 2, 3 })); +} + +TEST(InitializingRepeatGenotype, UnorderedAlleleSizes_ExceptionThrown) +{ + EXPECT_ANY_THROW(RepeatGenotype genotype(3, { 5, 2 })); +} + +TEST(SettingAlleleSizesCis, TypicalGenotype_CiSet) +{ + RepeatGenotype genotype(3, { 2, 3 }); + + genotype.setShortAlleleSizeInUnitsCi(1, 5); + genotype.setLongAlleleSizeInUnitsCi(2, 8); + + EXPECT_EQ(NumericInterval(1, 5), genotype.shortAlleleSizeInUnitsCi()); + EXPECT_EQ(NumericInterval(2, 8), genotype.longAlleleSizeInUnitsCi()); +} + +TEST(SettingAlleleSizesCis, CiNotCoveringRepeatSize_ExpceptionThrown) +{ + RepeatGenotype genotype(3, { 2, 3 }); + + EXPECT_ANY_THROW(genotype.setShortAlleleSizeInUnitsCi(0, 1)); + EXPECT_ANY_THROW(genotype.setLongAlleleSizeInUnitsCi(4, 5)); +} + +TEST(TestingHomozygosity, TypicalGenotypes_HomozygosityDetermined) +{ + EXPECT_TRUE(RepeatGenotype(3, { 2 }).isHomozygous()); + EXPECT_FALSE(RepeatGenotype(3, { 2, 3 }).isHomozygous()); + EXPECT_TRUE(RepeatGenotype(3, { 3, 3 }).isHomozygous()); +} diff --git a/genotyping/tests/RepeatGenotyperTest.cpp b/genotyping/tests/RepeatGenotyperTest.cpp new file mode 100755 index 0000000..1c90576 --- /dev/null +++ b/genotyping/tests/RepeatGenotyperTest.cpp @@ -0,0 +1,51 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "genotyping/RepeatGenotyper.hh" + +#include "gtest/gtest.h" + +#include "common/Common.hh" + +using std::map; +using std::vector; + +using namespace ehunter; + +TEST(CountingInrepeatReads, HaploidExpansion_IrrsCounted) +{ + CountTable countsOfFlankingReads(map( + { { 1, 3 }, { 2, 3 }, { 7, 1 }, { 11, 1 }, { 18, 1 }, { 20, 1 }, { 21, 1 }, { 33, 1 }, { 44, 1 } })); + CountTable countsOfInrepeatReads(map({ { 43, 1 }, { 45, 6 }, { 46, 1 }, { 47, 2 }, { 48, 1 } })); + + const int maxNumUnitsInRead = 50; + EXPECT_EQ(10, countFullLengthRepeatReads(maxNumUnitsInRead, countsOfFlankingReads, countsOfInrepeatReads)); +} + +TEST(CountingInrepeatReads, HaploidNormal_IrrsCounted) +{ + CountTable countsOfFlankingReads(map( + { { 1, 3 }, { 2, 3 }, { 7, 1 }, { 11, 1 }, { 18, 1 }, { 20, 1 }, { 21, 1 }, { 33, 1 }, { 44, 1 } })); + CountTable countsOfInrepeatReads(map({ { 46, 1 }, { 47, 1 }, { 48, 1 } })); + + const int maxNumUnitsInRead = 50; + EXPECT_EQ(1, countFullLengthRepeatReads(maxNumUnitsInRead, countsOfFlankingReads, countsOfInrepeatReads)); +} diff --git a/genotyping/tests/ShortRepeatGenotyperTest.cpp b/genotyping/tests/ShortRepeatGenotyperTest.cpp new file mode 100755 index 0000000..dc6376f --- /dev/null +++ b/genotyping/tests/ShortRepeatGenotyperTest.cpp @@ -0,0 +1,217 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Mitch Bekritsky , Richard Shaw +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "genotyping/ShortRepeatGenotyper.hh" + +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" + +#include "common/Common.hh" +#include "genotyping/RepeatGenotype.hh" + +using std::array; +using std::cerr; +using std::endl; +using std::map; +using std::string; +using std::vector; + +using namespace ehunter; + +TEST(CalculateMoleculeProportions, TypicalHaplotype_ProportionsCalculated) +{ + const int32_t allele_size_in_units = 2; + const int32_t maxRepeatSizeInUnits = 25; + const double propCorrectMolecules = 0.97; + QuantifierOfMoleculesGeneratedByAllele alleleQuantifier( + allele_size_in_units, maxRepeatSizeInUnits, propCorrectMolecules); + + EXPECT_DOUBLE_EQ(2.2885056508333023e-08, alleleQuantifier.propMoleculesOfGivenSize(25)); + EXPECT_DOUBLE_EQ(0.97087262363952287, alleleQuantifier.propMoleculesShorterThan(3)); + EXPECT_DOUBLE_EQ(0.029127376360477131, alleleQuantifier.propMoleculesAtLeast(3)); +} + +TEST(CalcFlankingLoglik, TypicalFlankingReads_LoglikelihoodsCalculated) +{ + const int32_t maxRepeatSizeInUnits = 25; + const double propCorrectMolecules = 0.97; + const vector allele_sizes_in_units = { 2, 3 }; + ShortRepeatGenotypeLikelihoodEstimator likelihoodEstimator( + maxRepeatSizeInUnits, propCorrectMolecules, allele_sizes_in_units); + EXPECT_DOUBLE_EQ(-0.015100313643051028, likelihoodEstimator.CalcFlankingLoglik(2)); + EXPECT_DOUBLE_EQ(-17.592794352808042, likelihoodEstimator.CalcFlankingLoglik(25)); +} + +TEST(CalcSpanningLoglik, TypicalSpanningReads_LoglikelihoodsCalcualted) +{ + const int32_t maxRepeatSizeInUnits = 25; + const double propCorrectMolecules = 0.97; + const vector genotype = { 2, 3 }; + ShortRepeatGenotypeLikelihoodEstimator likelihoodEstimator(maxRepeatSizeInUnits, propCorrectMolecules, genotype); + EXPECT_DOUBLE_EQ(-0.7236052500150770, likelihoodEstimator.CalcSpanningLoglik(3)); + EXPECT_DOUBLE_EQ(-4.2301631473350575, likelihoodEstimator.CalcSpanningLoglik(4)); +} + +TEST(CalcGenotypeLoglik, ShortGenotypes_LoglikelihoodsCalculated) +{ + const int32_t maxRepeatSizeInUnits = 25; + const double propCorrectMolecules = 0.97; + + const map flankingSizesAndCounts = { { 1, 2 }, { 2, 3 }, { 10, 1 } }; + const map spanningSizesAndCounts = { { 3, 4 }, { 5, 5 } }; + const CountTable countsOfFlankingReads(flankingSizesAndCounts); + const CountTable countsOfSpanningReads(spanningSizesAndCounts); + + { + const vector genotype = { 3, 5 }; + const ShortRepeatGenotypeLikelihoodEstimator likelihoodEstimator( + maxRepeatSizeInUnits, propCorrectMolecules, genotype); + const double log_likelihood = likelihoodEstimator.CalcLogLik(countsOfFlankingReads, countsOfSpanningReads); + + const double expectedLogLikelihood = -21.591945631259129; + EXPECT_DOUBLE_EQ(expectedLogLikelihood, log_likelihood); + } + + { + const vector genotype = { 3, 10 }; + const ShortRepeatGenotypeLikelihoodEstimator likelihoodEstimator( + maxRepeatSizeInUnits, propCorrectMolecules, genotype); + const double log_likelihood = likelihoodEstimator.CalcLogLik(countsOfFlankingReads, countsOfSpanningReads); + + const double expectedLogLikelihood = -42.567968025644028; + EXPECT_DOUBLE_EQ(expectedLogLikelihood, log_likelihood); + } + + { + + const vector genotype = { 10, 10 }; + const ShortRepeatGenotypeLikelihoodEstimator likelihoodEstimator( + maxRepeatSizeInUnits, propCorrectMolecules, genotype); + const double log_likelihood = likelihoodEstimator.CalcLogLik(countsOfFlankingReads, countsOfSpanningReads); + + const double expectedLogLikelihood = -158.36482963578563; + EXPECT_DOUBLE_EQ(expectedLogLikelihood, log_likelihood); + } +} + +TEST(CalcDiploidGenotypeLoglik, TypicalGenotypeLoglikelihoods_Calculated) +{ + const int32_t maxRepeatSizeInUnits = 25; + const double propCorrectMolecules = 0.97; + + const map flankingSizesAndCounts = { { 1, 2 }, { 2, 3 }, { 25, 10 } }; + const map spanningSizesAndCounts = { { 5, 5 } }; + const CountTable countsOfFlankingReads(flankingSizesAndCounts); + const CountTable countsOfSpanningReads(spanningSizesAndCounts); + + const vector genotype = { 5, 25 }; + const ShortRepeatGenotypeLikelihoodEstimator likelihoodEstimator( + maxRepeatSizeInUnits, propCorrectMolecules, genotype); + const double log_likelihood = likelihoodEstimator.CalcLogLik(countsOfFlankingReads, countsOfSpanningReads); + + const double expectedLogLikelihood = -7.3838630069778066; + EXPECT_DOUBLE_EQ(expectedLogLikelihood, log_likelihood); +} + +TEST(RepeatGenotyping, TypicalDiploidRepeat_Genotyped) +{ + const int32_t repeatUnitLen = 6; + const int32_t maxRepeatSizeInUnits = 25; + const double propCorrectMolecules = 0.97; + + const map flankingSizesAndCounts = { { 1, 2 }, { 2, 3 }, { 10, 1 } }; + const map spanningSizesAndCounts = { { 3, 4 }, { 5, 5 } }; + const CountTable countsOfFlankingReads(flankingSizesAndCounts); + const CountTable countsOfSpanningReads(spanningSizesAndCounts); + + vector candidateAlleleSizes; + for (int32_t candidateAlleleSize = 0; candidateAlleleSize != 26; ++candidateAlleleSize) + { + candidateAlleleSizes.push_back(candidateAlleleSize); + } + + const ShortRepeatGenotyper shortRepeatGenotyper(repeatUnitLen, maxRepeatSizeInUnits, propCorrectMolecules); + + const RepeatGenotype genotype = shortRepeatGenotyper.genotypeRepeatWithTwoAlleles( + countsOfFlankingReads, countsOfSpanningReads, candidateAlleleSizes); + + const RepeatGenotype expectedGenotype(6, { 3, 5 }); + EXPECT_EQ(expectedGenotype, genotype); +} + +TEST(RepeatGenotyping, TypicalHaploidRepeat_Genotyped) +{ + const int32_t repeatUnitLen = 6; + const int32_t maxRepeatSizeInUnits = 25; + const double propCorrectMolecules = 0.97; + + const map flankingSizesAndCounts = { { 1, 2 }, { 2, 3 }, { 10, 1 } }; + const map spanningSizesAndCounts = { { 3, 4 }, { 5, 5 } }; + const CountTable countsOfFlankingReads(flankingSizesAndCounts); + const CountTable countsOfSpanningReads(spanningSizesAndCounts); + + vector candidateAlleleSizes; + for (int32_t candidateAlleleSize = 0; candidateAlleleSize != 26; ++candidateAlleleSize) + { + candidateAlleleSizes.push_back(candidateAlleleSize); + } + + const ShortRepeatGenotyper shortRepeatGenotyper(repeatUnitLen, maxRepeatSizeInUnits, propCorrectMolecules); + + const RepeatGenotype genotype = shortRepeatGenotyper.genotypeRepeatWithOneAllele( + countsOfFlankingReads, countsOfSpanningReads, candidateAlleleSizes); + + const RepeatGenotype expectedGenotype(repeatUnitLen, { 5 }); + EXPECT_EQ(expectedGenotype, genotype); +} + +TEST(RepeatGenotyping, ExpandedRepeatWithOneAllele_Genotyped) +{ + const int32_t repeatUnitLen = 6; + const int32_t maxRepeatSizeInUnits = 25; + const double propCorrectMolecules = 0.97; + + const map flankingSizesAndCounts = { { 1, 2 }, { 2, 3 }, { 10, 1 }, { 25, 8 } }; + const map spanningSizesAndCounts = { { 3, 1 }, { 5, 1 } }; + const CountTable countsOfFlankingReads(flankingSizesAndCounts); + const CountTable countsOfSpanningReads(spanningSizesAndCounts); + + vector candidateAlleleSizes; + for (int32_t candidateAlleleSize = 0; candidateAlleleSize != 26; ++candidateAlleleSize) + { + candidateAlleleSizes.push_back(candidateAlleleSize); + } + + const ShortRepeatGenotyper shortRepeatGenotyper(repeatUnitLen, maxRepeatSizeInUnits, propCorrectMolecules); + + const RepeatGenotype genotype = shortRepeatGenotyper.genotypeRepeatWithOneAllele( + countsOfFlankingReads, countsOfSpanningReads, candidateAlleleSizes); + + const RepeatGenotype expectedGenotype(repeatUnitLen, { 25 }); + + EXPECT_EQ(expectedGenotype, genotype); +} diff --git a/genotyping/tests/SmallVariantGenotyperTest.cpp b/genotyping/tests/SmallVariantGenotyperTest.cpp new file mode 100755 index 0000000..db0a310 --- /dev/null +++ b/genotyping/tests/SmallVariantGenotyperTest.cpp @@ -0,0 +1,55 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Sai Chen +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "genotyping/SmallVariantGenotyper.hh" + +#include + +#include "gtest/gtest.h" + +#include "common/Common.hh" + +using namespace ehunter; + +TEST(SmallVariantGenotyper, ThrowsWithIllegalParameter) +{ + SmallVariantGenotyper triploidGenotyper(30.0, (AlleleCount)3); + ASSERT_ANY_THROW(triploidGenotyper.genotype(20, 20)); + + SmallVariantGenotyper negCountGenotyper(30.0, (AlleleCount)2); + ASSERT_ANY_THROW(negCountGenotyper.genotype(-1, 20)); +} + +TEST(SmallVariantGenotyper, RegularGenotye) +{ + SmallVariantGenotyper genotyper(30.0, (AlleleCount)2); + + SmallVariantGenotype ref_genotype(AlleleType::kRef, AlleleType::kRef); + SmallVariantGenotype gt0 = *genotyper.genotype(20, 1); + EXPECT_EQ(ref_genotype, gt0); + + SmallVariantGenotype het_genotype(AlleleType::kRef, AlleleType::kAlt); + SmallVariantGenotype gt1 = *genotyper.genotype(20, 19); + EXPECT_EQ(het_genotype, gt1); + + SmallVariantGenotype alt_genotype(AlleleType::kAlt, AlleleType::kAlt); + SmallVariantGenotype gt2 = *genotyper.genotype(1, 20); + EXPECT_EQ(alt_genotype, gt2); +} \ No newline at end of file diff --git a/genotyping/unit_tests/CMakeLists.txt b/genotyping/unit_tests/CMakeLists.txt deleted file mode 100644 index 57c0b1c..0000000 --- a/genotyping/unit_tests/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -add_executable(short_repeat_genotyper_test short_repeat_genotyper_test.cc) -target_link_libraries(short_repeat_genotyper_test genotyping gtest_main) -add_test(NAME short_repeat_genotyper_test COMMAND short_repeat_genotyper_test) \ No newline at end of file diff --git a/genotyping/unit_tests/repeat_genotyper_test.cc b/genotyping/unit_tests/repeat_genotyper_test.cc deleted file mode 100644 index e69de29..0000000 diff --git a/genotyping/unit_tests/short_repeat_genotyper_test.cc b/genotyping/unit_tests/short_repeat_genotyper_test.cc deleted file mode 100644 index c0194db..0000000 --- a/genotyping/unit_tests/short_repeat_genotyper_test.cc +++ /dev/null @@ -1,200 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include "genotyping/short_repeat_genotyper.h" - -#include -#include -#include -#include -#include - -#include "gtest/gtest.h" - -#include "common/common.h" - -using std::vector; -using std::string; -using std::map; -using std::cerr; -using std::endl; -using std::array; - -using namespace ehunter; - -TEST(CalculateMoleculeProportions, TypicalHaplotypeProportionsCalculated) { - const int num_units_haplotype = 2; - const int max_num_units_in_read = 25; - const double prop_correct_molecules = 0.97; - Allele hap(num_units_haplotype, max_num_units_in_read, - prop_correct_molecules); - - EXPECT_DOUBLE_EQ(2.2885056508333023e-08, hap.propMolecules(25)); - EXPECT_DOUBLE_EQ(0.97087262363952287, hap.propMoleculesShorterThan(3)); - EXPECT_DOUBLE_EQ(0.029127376360477131, hap.propMoleculesAtLeast(3)); -} - -TEST(CalcFlankingLoglik, TypicalFlankingReadsLoglikelihoodsCalculated) { - ShortRepeatGenotyper genotype(25, 0.97, 20.0, 150, 2, 3); - EXPECT_DOUBLE_EQ(-2.0300033341853156, genotype.CalcFlankingLoglik(2)); - EXPECT_DOUBLE_EQ(-19.607697373350305, genotype.CalcFlankingLoglik(25)); -} - -TEST(CalcSpanningLoglik, TypicalSpanningReadsLoglikelihoodsCalcualted) { - ShortRepeatGenotyper genotype(25, 0.97, 20.0, 150, 2, 3); - EXPECT_DOUBLE_EQ(-2.7385082705573418, genotype.CalcSpanningLoglik(3)); - EXPECT_DOUBLE_EQ(-6.2450661678773223, genotype.CalcSpanningLoglik(4)); -} - -TEST(CalcGenotypeLoglik, ShortGenotypesLoglikelihoodsCalculated) { - const map flanking_size_counts = {{1, 2}, {2, 3}, {10, 1}}; - const map spanning_size_counts = {{3, 4}, {5, 5}}; - vector genotype_support; - - ShortRepeatGenotyper genotype_3_5(25, 0.97, 25.0, 150, 3, 5); - EXPECT_DOUBLE_EQ( - -48.468337669679954, - genotype_3_5.CalcLogLik(flanking_size_counts, spanning_size_counts, - genotype_support)); - - const vector expected_3_5_support = {{4, 5, 0}, {5, 5, 0}}; - EXPECT_EQ(expected_3_5_support, genotype_support); - - ShortRepeatGenotyper genotype_3_10(25, 0.97, 25.0, 150, 3, 10); - EXPECT_DOUBLE_EQ( - -69.444360064064853, - genotype_3_10.CalcLogLik(flanking_size_counts, spanning_size_counts, - genotype_support)); - const vector expected_3_10_support = {{4, 5, 0}, {0, 6, 0}}; - EXPECT_EQ(expected_3_10_support, genotype_support); - - ShortRepeatGenotyper genotype_10_10(25, 0.97, 25.0, 150, 10, 10); - EXPECT_DOUBLE_EQ( - -185.24122167420646, - genotype_10_10.CalcLogLik(flanking_size_counts, spanning_size_counts, - genotype_support)); - const vector expected_10_10_support = {{0, 6, 0}, {0, 6, 0}}; - EXPECT_EQ(expected_10_10_support, genotype_support); -} - -TEST(CalcGenotypeLoglik, LongGenotypesLoglikelihoodsCalculated) { - const map flanking_size_counts = {{1, 2}, {2, 3}, {10, 1}}; - const map spanning_size_counts = {{3, 4}, {5, 5}}; - vector genotype_support; - - ShortRepeatGenotyper genotype_3_5(25, 0.97, 25.0, 150, 3, 5); - EXPECT_DOUBLE_EQ( - -48.468337669679954, - genotype_3_5.CalcLogLik(flanking_size_counts, spanning_size_counts, - genotype_support)); -} - -TEST(CalcDiploidGenotypeLoglik, TypicalGenotypeLoglikelihoodsCalculated) { - const map flanking_size_counts = {{1, 2}, {2, 3}, {25, 10}}; - const map spanning_size_counts = {{5, 5}}; - vector genotype_support; - - ShortRepeatGenotyper diploid_genotype(25, 0.97, 25.0, 150, 5, 25); - EXPECT_DOUBLE_EQ( - -34.260255045398637, - diploid_genotype.CalcLogLik(flanking_size_counts, spanning_size_counts, - genotype_support)); - - const vector expected_5_25_support = {{5, 5, 0}, {0, 5, 10}}; - EXPECT_EQ(expected_5_25_support, genotype_support); -} - -TEST(GenotypeStr, TypicalDiploidStrReturnsGenotype) { - const map flanking_size_counts = {{1, 2}, {2, 3}, {10, 1}}; - const map spanning_size_counts = {{3, 4}, {5, 5}}; - - const int max_num_units_in_read = 25; - const double prop_correct_molecules = 0.97; - const double hap_depth = 25.0; - const int read_len = 150; - - vector haplotype_candidates; - for (int i = 0; i != 26; ++i) { - haplotype_candidates.push_back(RepeatAllele(i, -1, ReadType::kSpanning)); - } - - RepeatGenotype genotype; - - GenotypeShortRepeat(max_num_units_in_read, prop_correct_molecules, hap_depth, - read_len, haplotype_candidates, flanking_size_counts, - spanning_size_counts, GenotypeType::kDiploid, genotype); - - const vector expected_genotype = { - RepeatAllele(3, ReadType::kSpanning, AlleleSupport(4, 5, 0)), - RepeatAllele(5, ReadType::kSpanning, AlleleSupport(5, 5, 0))}; - EXPECT_EQ(expected_genotype, genotype); -} - -TEST(GenotypeStr, TypicalHaploidStrReturnsGenotype) { - const map flanking_size_counts = {{1, 2}, {2, 3}, {10, 1}}; - const map spanning_size_counts = {{3, 4}, {5, 5}}; - - const int max_num_units_in_read = 25; - const double prop_correct_molecules = 0.97; - const double hap_depth = 25.0; - const int read_len = 150; - - vector haplotype_candidates; - for (int i = 0; i != 26; ++i) { - haplotype_candidates.push_back(RepeatAllele(i, -1, ReadType::kSpanning)); - } - - RepeatGenotype genotype; - GenotypeShortRepeat(max_num_units_in_read, prop_correct_molecules, hap_depth, - read_len, haplotype_candidates, flanking_size_counts, - spanning_size_counts, GenotypeType::kHaploid, genotype); - - const vector expected_genotype = { - RepeatAllele(5, ReadType::kSpanning, AlleleSupport(5, 5, 0))}; - - EXPECT_EQ(expected_genotype, genotype); -} - -TEST(GenotypeStr, ExpandedHaploidStrGenotyped) { - const map flanking_size_counts = {{1, 2}, {2, 3}, {10, 1}, {25, 8}}; - const map spanning_size_counts = {{3, 1}, {5, 1}}; - - const int max_num_units_in_read = 25; - const double prop_correct_molecules = 0.97; - const double hap_depth = 25.0; - const int read_len = 150; - - vector haplotype_candidates; - for (int i = 0; i != 26; ++i) { - haplotype_candidates.push_back(RepeatAllele(i, -1, ReadType::kSpanning)); - } - - RepeatGenotype genotype; - GenotypeShortRepeat(max_num_units_in_read, prop_correct_molecules, hap_depth, - read_len, haplotype_candidates, flanking_size_counts, - spanning_size_counts, GenotypeType::kHaploid, genotype); - - const vector expected_genotype = { - RepeatAllele(25, ReadType::kSpanning, AlleleSupport(0, 6, 8))}; - - EXPECT_EQ(expected_genotype, genotype); -} diff --git a/include/bam_file.h b/include/bam_file.h deleted file mode 100644 index 0849f7b..0000000 --- a/include/bam_file.h +++ /dev/null @@ -1,104 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#pragma once - -#include -#include -#include -#include - -// Include BAM processing from samtools -#include "htslib/hts.h" -#include "htslib/sam.h" - -#include "common/genomic_region.h" -#include "common/parameters.h" -#include "include/read_alignment.h" -namespace ehunter { - -class CramFile { - public: - std::vector CountAlignedReads(const std::string &cram_path, - int num_chroms); - bool GetUnalignedRead(Align &align); - - private: - htsFile *file_ptr_; - bam_hdr_t *header_ptr_; - bam1_t *align_ptr_; - bool found_unaligned_reads_; -}; - -class BamFile { - public: - enum FileFormat { kBamFile, kCramFile, kUnknownFormat }; - - BamFile(); - ~BamFile(); - - void Init(const std::string &path, const std::string &reference); - bool Close(); - bool SetRegionToRange(const Region &gRange); - bool CloseRegion(); - bool JumpToUnaligned(); - double CalcMedianDepth(Parameters ¶meters, size_t read_len); - - // If this returns false, there are no more reads in the set. - // Before calling again, caller should open a new read set using. e.g. - // SetRegionToRange(), JumpToUnaligned(). - bool GetRead(Align &align); - - // Can also be used for shadows since they have alignment pos of mate. - bool GetAlignedMate(const Align &align, Align &mateBAlign); - - const std::vector &ref_vec() const { return ref_vec_; } - FileFormat format() const { return format_; } - - private: - bool GetUnalignedPrRead(Align &align); - int GetNextGoodRead(); - - enum { kSupplimentaryAlign = 0x800, kSecondaryAlign = 0x100 }; - - CramFile cram_suppliment; - // Contians chromosomes and their sizes. - std::vector ref_vec_; - FileFormat format_; - std::string path_; - - // Pointer to the input BAM/CRAM file itself. - htsFile *hts_file_ptr_; - // A pointer to BAM header. - bam_hdr_t *hts_bam_hdr_ptr_; - // A pointer to BAM index. - hts_idx_t *hts_idx_ptr_; - // Pointer to the target region. - hts_itr_t *hts_itr_ptr_; - // A pointer to an alignment in the BAM file. - bam1_t *hts_bam_align_ptr_; - - bool jump_to_unaligned_; - bool at_file_end_; -}; - -} // namespace ehunter diff --git a/include/irr_counting.h b/include/irr_counting.h deleted file mode 100644 index 26f6e09..0000000 --- a/include/irr_counting.h +++ /dev/null @@ -1,80 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#pragma once - -#include -#include -#include -#include -#include - -#include "common/genomic_region.h" -#include "common/parameters.h" -#include "common/repeat_spec.h" -#include "include/bam_file.h" -#include "include/read_alignment.h" -#include "rep_align/rep_align.h" - -namespace ehunter { - -// Represents fragment alignments as pairs of read alignments. -typedef std::array AlignPair; - -// Represents a collection fragments as a map from fragment names to fragment -// Aligns. -typedef std::unordered_map AlignPairs; - -enum WhatToCache { kCacheAll, kCacheIrr }; - -// Extract alignment pairs from a specified region. -void CacheReadsFromRegion( - const Region ®ion, const WhatToCache whatToCache, - const std::vector> &units_shifts, - double min_wp_score, BamFile *bam_file, AlignPairs *align_pairs); - -void CountAnchoredIrrs( - const BamFile &bam_file, const Parameters ¶meters, - const Region &target_neighborhood, - const std::unordered_set &ontarget_frag_names, - AlignPairs &align_pairs, int &num_anchored_irrs, - const std::vector> &units_shifts, - std::vector *anchored_irrs); - -void FillinMates(BamFile &bam_file, AlignPairs &align_pairs, - const std::vector> &units_shifts, - double min_wp_score, - const std::unordered_set &ontarget_frag_names); - -// Count the number of in-repeat reads stored in an AlignPairs object. -// A fragment is in-repeat if both of the reads fuzzy match to the repeat -// sequence. -void CountUnalignedIrrs( - BamFile &bam_file, const Parameters ¶meters, int &numInRepeatReads, - const std::vector> &units_shifts, - std::vector *irr_rep_aligns); - -int CountAlignedIrr(const Parameters ¶meters, const AlignPairs &align_pairs, - std::map &num_irrs_per_offtarget_region, - const std::vector> &units_shifts, - std::vector *irr_rep_aligns); -} // namespace ehunter diff --git a/include/json_output.h b/include/json_output.h deleted file mode 100644 index d393d3e..0000000 --- a/include/json_output.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#pragma once - -#include -#include -#include -#include - -#include "common/parameters.h" -#include "common/repeat_spec.h" -#include "include/region_findings.h" - -namespace ehunter { - -void WriteJson(const Parameters ¶meters, - const std::map &repeat_specs, - const std::vector &sample_findings, - std::ostream &out); -} diff --git a/include/read_alignment.h b/include/read_alignment.h deleted file mode 100644 index 99565dd..0000000 --- a/include/read_alignment.h +++ /dev/null @@ -1,105 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#pragma once - -#include -using boost::lexical_cast; - -#include -#include -#include - -#include "htslib/hts.h" -#include "htslib/sam.h" - -#include "common/genomic_region.h" - -namespace ehunter { - -enum ReadStatus { kNoCheck, kFlankingRead }; - -struct Align { - std::string name; - int32_t len; - std::string bases; - std::string quals; - int32_t chrom_id; - int32_t pos; - uint16_t mapq; - uint32_t flag; - int32_t mate_chrom_id; - int32_t mate_pos; - std::string region; - ReadStatus status; - - bool IsMapped() const { return ((flag & 0x0004) == 0); } - bool IsFirstMate() const { return ((flag & 0x0040) != 0); } - bool IsMateMapped() const { return ((flag & 0x0008) == 0); } - - bool getMateRegion(Region& mateRegion, - const std::vector& refVec) const { - // Relies on mate (alignment) length being same as read (alignment) length. - // May not hold for split alignments (or even gapped alignments?). - if (flag & 0x08) { - return false; // mate unmapped - } - // align.mate_pos is 0-offset - mateRegion = Region(DecodeChrom(mate_chrom_id, refVec), mate_pos + 1, - mate_pos + len); - return true; - } - - bool GetReadRegion(Region& readRegion, - const std::vector& refVec) const { - if (IsMapped()) { - // myAlign.pos is 0-offset - readRegion = Region(DecodeChrom(chrom_id, refVec), pos + 1, pos + len); - return true; - } - - return false; - } - - std::string DecodeChrom(int32_t chromNum, - const std::vector& refVec) const { - if (chromNum == -1) { - return "chr-1"; - } - - if (chromNum >= (int)refVec.size()) { - throw std::out_of_range( - "[DecodeChrom ERROR] Input chromosme index: " + - lexical_cast(chromNum) + " but there are only " + - lexical_cast(refVec.size()) + " references"); - } - - return refVec[chromNum]; - } -}; - -bool GetAlignFromHtsAlign(bam1_t* hts_align_ptr, Align& align, - bool assumeUnaligned = false); - -bool GetQualsFromHtsAlign(bam1_t* hts_align_ptr, std::string& quals); -bool GetBasesFromHtsAlign(bam1_t* hts_align_ptr, std::string& bases); -} // namespace ehunter diff --git a/include/read_group.h b/include/read_group.h deleted file mode 100644 index a1ae97f..0000000 --- a/include/read_group.h +++ /dev/null @@ -1,69 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#pragma once - -#include - -#include -#include -#include -#include -#include -#include - -#include "common/common.h" -#include "common/parameters.h" -#include "common/repeat_spec.h" -#include "rep_align/rep_align.h" - -namespace ehunter { - -struct RepeatReadGroup { - ReadType read_type; - std::vector rep_aligns; - int size; - int num_supporting_reads; -}; - -bool CompareReadGroupsBySize(const RepeatReadGroup &a1, - const RepeatReadGroup &a2); - -void CoalesceFlankingReads( - const RepeatSpec &repeat_spec, std::vector &read_groups, - std::vector *flanking_repaligns, int motif_len, - const std::vector> &units_shifts, int min_baseq, - double min_wp_score); - -void OutputRepeatAligns(const Parameters ¶meters, - const RepeatSpec &repeat_spec, - const std::vector &read_groups, - const std::vector &flanking_repaligns, - std::ostream *out); - -// Realign flanking reads to existing repeats. -void DistributeFlankingReads(const Parameters ¶meters, - const RepeatSpec &repeat_spec, - std::vector *read_groups, - std::vector *flanking_repaligns); - -} // namespace ehunter diff --git a/include/region_findings.h b/include/region_findings.h deleted file mode 100644 index 728de5d..0000000 --- a/include/region_findings.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#pragma once - -#include -#include -#include - -#include "common/genomic_region.h" -#include "genotyping/short_repeat_genotyper.h" -#include "include/read_group.h" - -namespace ehunter { - -class RegionFindings { - public: - std::string region_id; - Region region; - int num_anchored_irrs; - int num_unaligned_irrs; - int num_irrs; - std::vector read_groups; - std::vector rep_aligns; - std::vector flanking_repaligns; - std::vector offtarget_irr_counts; - RepeatGenotype genotype; -}; -} // namespace ehunter diff --git a/input/CMakeLists.txt b/input/CMakeLists.txt new file mode 100755 index 0000000..fbfadba --- /dev/null +++ b/input/CMakeLists.txt @@ -0,0 +1,4 @@ +file(GLOB SOURCES "*.cpp") +add_library(input ${SOURCES}) +target_link_libraries(input common stats region_spec) +add_subdirectory(tests) \ No newline at end of file diff --git a/input/CatalogLoading.cpp b/input/CatalogLoading.cpp new file mode 100755 index 0000000..3e6ba4b --- /dev/null +++ b/input/CatalogLoading.cpp @@ -0,0 +1,422 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "input/CatalogLoading.hh" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "thirdparty/json/json.hpp" +#include "thirdparty/spdlog/spdlog.h" + +#include "common/Common.hh" +#include "common/Reference.hh" +#include "input/GraphBlueprint.hh" +#include "input/RegionGraph.hh" + +using boost::optional; +using graphtools::NodeId; +using std::map; +using std::ostream; +using std::string; +using std::to_string; +using std::vector; + +using Json = nlohmann::json; + +namespace spd = spdlog; + +namespace ehunter +{ + +enum class VariantDescriptionFromUser +{ + kRareRepeat, + kCommonRepeat, + kSmallVariant, + kSMN +}; + +enum class InputRecordType +{ + kRegionWithSingleRepeat, + kRegionWithMultipleRepeats, + kUnknown +}; + +AlleleCount determineExpectedAlleleCount(Sex sex, const string& chrom) +{ + const bool isFemaleChromY = sex == Sex::kFemale && (chrom == "chrY" || chrom == "Y"); + if (isFemaleChromY) + { + return AlleleCount::kZero; + } + + const bool isSexChrom = chrom == "chrX" || chrom == "X" || chrom == "chrY" || chrom == "Y"; + if (sex == Sex::kMale && isSexChrom) + { + return AlleleCount::kOne; + } + + return AlleleCount::kTwo; +} + +static bool checkIfFieldExists(const Json& record, const string& fieldName) +{ + return record.find(fieldName) != record.end(); +} + +static void assertFieldExists(const Json& record, const string& fieldName) +{ + if (!checkIfFieldExists(record, fieldName)) + { + std::stringstream out; + out << record; + throw std::logic_error("Field " + fieldName + " must be present in " + out.str()); + } +} + +static void assertRecordIsArray(const Json& record) +{ + if (!record.is_array()) + { + std::stringstream out; + out << record; + throw std::logic_error("Expected array but got this instead " + out.str()); + } +} + +static VariantDescriptionFromUser decodeVariantDescription(const string& encoding) +{ + if (encoding == "RareRepeat") + { + return VariantDescriptionFromUser::kRareRepeat; + } + if (encoding == "Repeat") + { + return VariantDescriptionFromUser::kCommonRepeat; + } + if (encoding == "SmallVariant") + { + return VariantDescriptionFromUser::kSmallVariant; + } + if (encoding == "SMN") + { + return VariantDescriptionFromUser::kSMN; + } + else + { + throw std::logic_error("Encountered invalid variant type: " + encoding); + } +} + +static vector combine(const std::string& prefix, const vector& suffixes) +{ + vector combinedStrings; + for (const auto& suffix : suffixes) + { + combinedStrings.push_back(prefix + "_" + suffix); + } + + return combinedStrings; +} + +static void makeArray(Json& record) +{ + if (record.type() != Json::value_t::array) + { + record = Json::array({ record }); + } +} + +static bool doesFeatureDefineVariant(GraphBlueprintFeatureType featureType) +{ + switch (featureType) + { + case GraphBlueprintFeatureType::kInsertionOrDeletion: + case GraphBlueprintFeatureType::kSkippableRepeat: + case GraphBlueprintFeatureType::kUnskippableRepeat: + case GraphBlueprintFeatureType::kSwap: + return true; + + case GraphBlueprintFeatureType::kLeftFlank: + case GraphBlueprintFeatureType::kRightFlank: + case GraphBlueprintFeatureType::kInterruption: + return false; + + default: + std::stringstream encoding; + encoding << featureType; + throw std::logic_error("Unrecognized feature type: " + encoding.str()); + } +} + +static std::size_t countVariants(const GraphBlueprint& blueprint) +{ + std::size_t numVariants = 0; + for (const auto& feature : blueprint) + { + if (doesFeatureDefineVariant(feature.type)) + { + ++numVariants; + } + } + + return numVariants; +} + +static VariantType determineVariantType(GraphBlueprintFeatureType featureType) +{ + switch (featureType) + { + case GraphBlueprintFeatureType::kInsertionOrDeletion: + case GraphBlueprintFeatureType::kSwap: + return VariantType::kSmallVariant; + case GraphBlueprintFeatureType::kSkippableRepeat: + case GraphBlueprintFeatureType::kUnskippableRepeat: + return VariantType::kRepeat; + default: + std::ostringstream encoding; + encoding << featureType; + throw std::logic_error("Feature of type " + encoding.str() + " does not define a variant"); + } +} + +static VariantSubtype determineVariantSubtype( + GraphBlueprintFeatureType featureType, VariantDescriptionFromUser userDescription, const Region referenceRegion) +{ + if (featureType == GraphBlueprintFeatureType::kInsertionOrDeletion) + { + if (referenceRegion.length() == 0) + { + return VariantSubtype::kInsertion; + } + else + { + return VariantSubtype::kDeletion; + } + } + else if (featureType == GraphBlueprintFeatureType::kSwap) + { + if (userDescription == VariantDescriptionFromUser::kSMN) + { + return VariantSubtype::kSMN; + } + else + { + return VariantSubtype::kSwap; + } + } + else if (userDescription == VariantDescriptionFromUser::kCommonRepeat) + { + return VariantSubtype::kCommonRepeat; + } + else if (userDescription == VariantDescriptionFromUser::kRareRepeat) + { + return VariantSubtype::kRareRepeat; + } + else + { + std::ostringstream message; + message << featureType; + throw std::logic_error("Feature " + message.str() + " does not correspond to variant"); + } +} + + +static optional +determineReferenceNode(const GraphBlueprintFeature& feature, const Reference& reference, const Region& referenceRegion) +{ + const string refSequence + = reference.getSequence(referenceRegion.chrom(), referenceRegion.start(), referenceRegion.end()); + + optional optionalReferenceNode; + for (int index = 0; index != static_cast(feature.nodeIds.size()); ++index) + { + if (refSequence == feature.sequences[index]) + { + optionalReferenceNode = feature.nodeIds[index]; + break; + } + } + + return optionalReferenceNode; +} + +static GraphBlueprint generateBlueprint(const Reference& reference, const Region& region, const string& locusStructure) +{ + // Reference repeat flanks should be at least as long as reads. + const int kFlankLen = 1500; + const int64_t leftFlankStart = region.start() - kFlankLen; + const int64_t rightFlankEnd = region.end() + kFlankLen; + + const string leftFlank = reference.getSequence(region.chrom(), leftFlankStart, region.start()); + const string regionSequence = reference.getSequence(region.chrom(), region.start(), region.end()); + const string rightFlank = reference.getSequence(region.chrom(), region.end(), rightFlankEnd); + + return decodeFeaturesFromRegex(leftFlank + locusStructure + rightFlank); +} + +static Region mergeRegions(const vector& regions) +{ + const int kMaxMergeDistance = 500; + vector mergedReferenceRegions = merge(regions, kMaxMergeDistance); + if (mergedReferenceRegions.size() != 1) + { + std::stringstream out; + for (const Region& region : regions) + { + out << region << " "; + } + throw std::runtime_error( + "Expected reference regions to be closer than " + std::to_string(kMaxMergeDistance) + + " from one another: " + out.str()); + } + + return mergedReferenceRegions.front(); +} + +static LocusSpecification loadLocusSpecification(Json& locusJson, Sex sampleSex, const Reference& reference) +{ + assertFieldExists(locusJson, "LocusId"); + const string& locusId = locusJson["LocusId"]; + + assertFieldExists(locusJson, "ReferenceRegion"); + makeArray(locusJson["ReferenceRegion"]); + vector referenceRegions; + for (const string& encoding : locusJson["ReferenceRegion"]) + { + referenceRegions.emplace_back(encoding); + } + + vector variantIds = combine(locusId, locusJson["ReferenceRegion"]); + + assertFieldExists(locusJson, "LocusStructure"); + const string& locusStructure = locusJson["LocusStructure"]; + + assertFieldExists(locusJson, "VariantType"); + makeArray(locusJson["VariantType"]); + vector variantDescriptions; + for (const string& encoding : locusJson["VariantType"]) + { + variantDescriptions.push_back(decodeVariantDescription(encoding)); + } + + const Region mergedReferenceRegion = mergeRegions(referenceRegions); + vector targetRegions; + if (checkIfFieldExists(locusJson, "TargetRegion")) + { + makeArray(locusJson["TargetRegion"]); + for (const string& locusEncoding : locusJson["TargetRegion"]) + { + targetRegions.emplace_back(locusEncoding); + } + } + else + { + targetRegions = { mergedReferenceRegion }; + } + + vector offtargetRegions; + if (checkIfFieldExists(locusJson, "OfftargetRegions")) + { + assertRecordIsArray(locusJson["OfftargetRegions"]); + for (const string& locusEncoding : locusJson["OfftargetRegions"]) + { + offtargetRegions.push_back(Region(locusEncoding)); + } + } + + GraphBlueprint blueprint = generateBlueprint(reference, mergedReferenceRegion, locusStructure); + graphtools::Graph locusGraph = makeRegionGraph(blueprint); + + std::size_t numVariants = countVariants(blueprint); + if (numVariants == 0) + { + std::stringstream out; + out << locusJson; + throw std::runtime_error("Locus must contain at least one variant: " + out.str()); + } + + if (referenceRegions.size() != numVariants || variantDescriptions.size() != numVariants) + { + std::stringstream out; + out << locusJson; + throw std::runtime_error("Expected reference region and type for each variant: " + out.str()); + } + + AlleleCount expectedAlleleCount = determineExpectedAlleleCount(sampleSex, mergedReferenceRegion.chrom()); + LocusSpecification regionSpec(locusId, targetRegions, expectedAlleleCount, locusGraph); + + int variantIndex = 0; + for (const auto& feature : blueprint) + { + if (doesFeatureDefineVariant(feature.type)) + { + const Region& referenceRegion = referenceRegions[variantIndex]; + VariantDescriptionFromUser variantDescription = variantDescriptions[variantIndex]; + + VariantType variantType = determineVariantType(feature.type); + VariantSubtype variantSubtype = determineVariantSubtype(feature.type, variantDescription, referenceRegion); + optional optionalReferenceNode = determineReferenceNode(feature, reference, referenceRegion); + + VariantClassification classification(variantType, variantSubtype); + regionSpec.addVariantSpecification( + variantIds[variantIndex], classification, referenceRegion, feature.nodeIds, optionalReferenceNode); + ++variantIndex; + } + } + + return regionSpec; +} + +RegionCatalog loadRegionCatalogFromDisk(const string& catalogPath, const Reference& reference, Sex sampleSex) +{ + std::ifstream inputStream(catalogPath.c_str()); + + if (!inputStream.is_open()) + { + throw std::runtime_error("Failed to open catalog file " + catalogPath); + } + + Json catalogJson; + inputStream >> catalogJson; + makeArray(catalogJson); + + RegionCatalog catalog; + for (auto& locusJson : catalogJson) + { + LocusSpecification locusSpec = loadLocusSpecification(locusJson, sampleSex, reference); + catalog.emplace(std::make_pair(locusSpec.regionId(), locusSpec)); + } + + return catalog; +} + +} diff --git a/input/CatalogLoading.hh b/input/CatalogLoading.hh new file mode 100755 index 0000000..a1a858b --- /dev/null +++ b/input/CatalogLoading.hh @@ -0,0 +1,39 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include +#include +#include + +#include "common/Common.hh" +#include "common/Reference.hh" +#include "region_spec/LocusSpecification.hh" + +namespace ehunter +{ + +RegionCatalog loadRegionCatalogFromDisk(const std::string& catalogPath, const Reference& reference, Sex sampleSex); + +} diff --git a/input/GraphBlueprint.cpp b/input/GraphBlueprint.cpp new file mode 100755 index 0000000..2e0a8fd --- /dev/null +++ b/input/GraphBlueprint.cpp @@ -0,0 +1,256 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "input/GraphBlueprint.hh" + +#include + +using graphtools::NodeId; +using std::string; +using std::vector; + +namespace ehunter +{ + +class TokenizationHelper +{ +public: + TokenizationHelper(const string& regex) + : regex_(regex) + , currentSymbolIter_(regex_.begin()) + { + } + + bool reachedEnd() const { return currentSymbolIter_ == regex_.end(); } + void advance() { ++currentSymbolIter_; } + + char currentSymbol() const { return *currentSymbolIter_; } + bool pointingAtBase() const { return kBaseSymbols.find(*currentSymbolIter_) != string::npos; } + + bool pointingAtTokenTerminator() const + { + // Last character is always a token terminator + if (currentSymbolIter_ + 1 == regex_.end()) + { + return true; + } + + if (isCountQuantifier(*currentSymbolIter_)) + { + return true; + } + + char nextSymbol = *(currentSymbolIter_ + 1); + if (*currentSymbolIter_ == ')' && !isCountQuantifier(nextSymbol)) + { + return true; + } + + if (nextSymbol == '(') + { + return true; + } + + return false; + } + +private: + bool isCountQuantifier(char symbol) const { return kCountQuantifiers.find(symbol) != string::npos; } + + static const string kBaseSymbols; + static const string kCountQuantifiers; + + const string& regex_; + string::const_iterator currentSymbolIter_; +}; + +const string TokenizationHelper::kBaseSymbols("ACGTBDHKMNSRVWY"); +const string TokenizationHelper::kCountQuantifiers("*+?"); + +vector tokenizeRegex(const string& regex) +{ + vector tokens; + string token; + + TokenizationHelper tokenizationHelper(regex); + while (!tokenizationHelper.reachedEnd()) + { + token += tokenizationHelper.currentSymbol(); + if (tokenizationHelper.pointingAtTokenTerminator()) + { + tokens.push_back(token); + token.clear(); + } + + tokenizationHelper.advance(); + } + + return tokens; +} + +TokenParser::TokenParser() + : skippableRepeatRegex_("^\\([ACGTBDHKMNSRVWY]+\\)\\*$") + , unskippableRepeatRegex_("^\\([ACGTBDHKMNSRVWY]+\\)\\+$") + , insertionOrDeletionRegex_("^\\([ACGTBDHKMNSRVWY]+\\)\\?$") + , swapRegex_("^\\([ACGTBDHKMNSRVWY]+\\|[ACGTBDHKMNSRVWY]+\\)$") + , interruptionRegex_("^[ACGTBDHKMNSRVWY]+$") +{ +} + +FeatureTypeAndSequences TokenParser::parse(const string& token) const +{ + if (std::regex_match(token, insertionOrDeletionRegex_)) + { + auto sequence = token.substr(1, token.size() - 3); + return { GraphBlueprintFeatureType::kInsertionOrDeletion, { sequence } }; + } + else if (std::regex_match(token, skippableRepeatRegex_)) + { + auto sequence = token.substr(1, token.size() - 3); + return { GraphBlueprintFeatureType::kSkippableRepeat, { sequence } }; + } + else if (std::regex_match(token, unskippableRepeatRegex_)) + { + auto sequence = token.substr(1, token.size() - 3); + return { GraphBlueprintFeatureType::kUnskippableRepeat, { sequence } }; + } + else if (std::regex_match(token, swapRegex_)) + { + auto sequence = token.substr(1, token.size() - 2); + int pipePosition = sequence.find('|'); + auto firstAllele = sequence.substr(0, pipePosition); + auto secondAllele = sequence.substr(pipePosition + 1); + return { GraphBlueprintFeatureType::kSwap, { firstAllele, secondAllele } }; + } + else if (std::regex_match(token, interruptionRegex_)) + { + return { GraphBlueprintFeatureType::kInterruption, { token } }; + } + else + { + throw std::logic_error("Could not parse the token " + token); + } +} + +std::ostream& operator<<(std::ostream& out, GraphBlueprintFeatureType tokenType) +{ + switch (tokenType) + { + case GraphBlueprintFeatureType::kLeftFlank: + out << "GraphBlueprintFeatureType::kLeftFlank"; + break; + case GraphBlueprintFeatureType::kRightFlank: + out << "GraphBlueprintFeatureType::kRightFlank"; + break; + case GraphBlueprintFeatureType::kInsertionOrDeletion: + out << "GraphBlueprintFeatureType::kInsertionOrDeletion"; + break; + case GraphBlueprintFeatureType::kInterruption: + out << "GraphBlueprintFeatureType::kInterruption"; + break; + case GraphBlueprintFeatureType::kSkippableRepeat: + out << "GraphBlueprintFeatureType::kSkippableRepeat"; + break; + case GraphBlueprintFeatureType::kUnskippableRepeat: + out << "GraphBlueprintFeatureType::kUnskippableRepeat"; + break; + case GraphBlueprintFeatureType::kSwap: + out << "GraphBlueprintFeatureType::kSwap"; + break; + default: + throw std::logic_error("Encountered unknown token type"); + } + + return out; +} + +bool isSkippable(GraphBlueprintFeatureType featureType) +{ + switch (featureType) + { + case GraphBlueprintFeatureType::kLeftFlank: + case GraphBlueprintFeatureType::kRightFlank: + case GraphBlueprintFeatureType::kInterruption: + case GraphBlueprintFeatureType::kUnskippableRepeat: + case GraphBlueprintFeatureType::kSwap: + return false; + case GraphBlueprintFeatureType::kSkippableRepeat: + case GraphBlueprintFeatureType::kInsertionOrDeletion: + return true; + default: + throw std::logic_error("Encountered unrecognized feature blueprint type"); + } +} + +GraphBlueprint decodeFeaturesFromRegex(const string& regex) +{ + GraphBlueprint blueprint; + + const auto tokens = tokenizeRegex(regex); + TokenParser parser; + + NodeId firstUnusedNodeId = 0; + for (int index = 0; index != static_cast(tokens.size()); ++index) + { + const auto& token = tokens[index]; + auto featureTypeAndSequences = parser.parse(token); + + auto featureType = featureTypeAndSequences.first; + const auto& sequences = featureTypeAndSequences.second; + + if (index == 0) + { + if (featureType != GraphBlueprintFeatureType::kInterruption) + { + throw std::logic_error("Malformed regular expression " + regex); + } + + featureType = GraphBlueprintFeatureType::kLeftFlank; + blueprint.push_back(GraphBlueprintFeature(featureType, sequences, { firstUnusedNodeId })); + ++firstUnusedNodeId; + } + else if (index == static_cast(tokens.size()) - 1) + { + if (featureType != GraphBlueprintFeatureType::kInterruption) + { + throw std::logic_error("Malformed regular expression " + regex); + } + + featureType = GraphBlueprintFeatureType::kRightFlank; + blueprint.push_back(GraphBlueprintFeature(featureType, sequences, { firstUnusedNodeId })); + ++firstUnusedNodeId; + } + else + { + vector nodeIds; + for (int nodeIndex = 0; nodeIndex != static_cast(sequences.size()); ++nodeIndex) + { + nodeIds.push_back(firstUnusedNodeId); + ++firstUnusedNodeId; + } + + blueprint.push_back(GraphBlueprintFeature(featureType, sequences, nodeIds)); + } + } + + return blueprint; +} + +} diff --git a/input/GraphBlueprint.hh b/input/GraphBlueprint.hh new file mode 100755 index 0000000..d3bc108 --- /dev/null +++ b/input/GraphBlueprint.hh @@ -0,0 +1,87 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "graphcore/Graph.hh" + +namespace ehunter +{ + +enum class GraphBlueprintFeatureType +{ + kLeftFlank, + kRightFlank, + kSkippableRepeat, + kUnskippableRepeat, + kInsertionOrDeletion, + kSwap, + kInterruption +}; + +bool isSkippable(GraphBlueprintFeatureType featureType); + +using FeatureTypeAndSequences = std::pair>; + +std::vector tokenizeRegex(const std::string& regex); + +class TokenParser +{ +public: + TokenParser(); + + FeatureTypeAndSequences parse(const std::string& token) const; + +private: + std::regex skippableRepeatRegex_; + std::regex unskippableRepeatRegex_; + std::regex insertionOrDeletionRegex_; + std::regex swapRegex_; + std::regex interruptionRegex_; +}; + +struct GraphBlueprintFeature +{ + GraphBlueprintFeature( + GraphBlueprintFeatureType type, std::vector sequences, std::vector nodeIds) + : type(std::move(type)) + , sequences(std::move(sequences)) + , nodeIds(std::move(nodeIds)) + { + } + GraphBlueprintFeatureType type; + std::vector sequences; + std::vector nodeIds; +}; + +using GraphBlueprint = std::vector; + +GraphBlueprint decodeFeaturesFromRegex(const std::string& regex); + +std::ostream& operator<<(std::ostream& out, GraphBlueprintFeatureType tokenType); + +} diff --git a/input/ParameterLoading.cpp b/input/ParameterLoading.cpp new file mode 100755 index 0000000..a6d1c75 --- /dev/null +++ b/input/ParameterLoading.cpp @@ -0,0 +1,272 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "input/ParameterLoading.hh" + +#include +#include + +#include +#include +#include + +#include "input/SampleStats.hh" +#include "src/Version.hh" + +namespace po = boost::program_options; +namespace fs = boost::filesystem; + +using boost::optional; +using std::string; +using std::to_string; +using std::vector; + +namespace ehunter +{ + +struct UserParameters +{ + // Input file paths + string htsFilePath; + string referencePath; + string catalogPath; + + // Output prefix + string outputPrefix; + + // Sample parameters + optional optionalReadLength; + optional optionalGenomeCoverage; + string sampleSexEncoding; + + // Heuristic parameters + bool verboseLogging; + string alignerType; + int regionExtensionLength; + int qualityCutoffForGoodBaseCall; + bool skipUnaligned; +}; + +boost::optional tryParsingUserParameters(int argc, char** argv) +{ + UserParameters params; + + // clang-format off + po::options_description usage("Allowed options"); + usage.add_options() + ("help", "Print help message") + ("version", "Print version number") + ("reads", po::value(¶ms.htsFilePath)->required(), "BAM/CRAM file with aligned reads") + ("reference", po::value(¶ms.referencePath)->required(), "FASTA file with reference genome") + ("variant-catalog", po::value(¶ms.catalogPath)->required(), "JSON file with variants to genotype") + ("output-prefix", po::value(¶ms.outputPrefix)->required(), "Prefix for the output files") + ("region-extension-length", po::value(¶ms.regionExtensionLength)->default_value(1000), "How far from on/off-target regions to search for informative reads") + ("read-length", po::value(), "Read length") + ("genome-coverage", po::value(), "Read depth on diploid chromosomes") + ("sex", po::value(¶ms.sampleSexEncoding)->default_value("female"), "Sex of the sample; must be either male or female") + ("aligner", po::value(¶ms.alignerType)->default_value("dag-aligner"), "dag-aligner or path-aligner") + ("verbose-logging", po::bool_switch(¶ms.verboseLogging)->default_value(false), "Enable verbose logging"); + // clang-format on + + if (argc == 1) + { + std::cerr << usage << std::endl; + return boost::optional(); + } + + po::variables_map argumentMap; + po::store(po::command_line_parser(argc, argv).options(usage).run(), argumentMap); + + if (argumentMap.count("help")) + { + std::cerr << usage << std::endl; + return boost::optional(); + } + + if (argumentMap.count("version")) + { + std::cerr << "Starting " << kProgramVersion << std::endl; + return boost::optional(); + } + + po::notify(argumentMap); + + if (argumentMap.count("read-length")) + { + params.optionalReadLength = argumentMap["read-length"].as(); + } + + if (argumentMap.count("genome-coverage")) + { + params.optionalGenomeCoverage = argumentMap["genome-coverage"].as(); + } + + return params; +} + +static void assertWritablePath(const string& pathEncoding) +{ + const fs::path path(pathEncoding); + const fs::path pathToDirectory = path.parent_path(); + + const bool thereIsNoDirectory = pathToDirectory.empty(); + const bool pathLeadsExistingDirectory = fs::is_directory(pathToDirectory); + const bool filenameIsValid = fs::portable_posix_name(path.filename().string()); + + if (!filenameIsValid || (!thereIsNoDirectory && !pathLeadsExistingDirectory)) + { + throw std::invalid_argument(pathEncoding + " is not a valid output path"); + } +} + +static void assertPathToExistingFile(const string& pathEncoding) +{ + const fs::path path(pathEncoding); + + if (fs::is_directory(path) && !fs::exists(path)) + { + throw std::invalid_argument(pathEncoding + " is not a path to an existing file"); + } +} + +static void assertIndexExists(const string& htsFilePath) +{ + const vector kPossibleIndexExtensions = { ".bai", ".csi", ".crai" }; + + for (const string& indexExtension : kPossibleIndexExtensions) + { + if (fs::exists(htsFilePath + indexExtension)) + { + return; + } + } + + throw std::invalid_argument("Could not find index of " + htsFilePath); +} + +void assertValidity(const UserParameters& userParameters) +{ + // Validate input file paths + assertPathToExistingFile(userParameters.htsFilePath); + assertIndexExists(userParameters.htsFilePath); + assertPathToExistingFile(userParameters.referencePath); + assertPathToExistingFile(userParameters.catalogPath); + + // Validate output prefix + assertWritablePath(userParameters.outputPrefix); + + // Validate sample parameters + if (userParameters.optionalReadLength) + { + const int readLength = *userParameters.optionalReadLength; + const int minAllowedReadLength = 50; + const int maxAllowedReadLength = 500; + if (readLength < minAllowedReadLength || readLength > maxAllowedReadLength) + { + throw std::invalid_argument(to_string(readLength) + "bp reads are not supported"); + } + } + + const double kMinDepthAllowed = 10.0; + if (userParameters.optionalGenomeCoverage && *userParameters.optionalGenomeCoverage < kMinDepthAllowed) + { + throw std::invalid_argument("Read depth must be at least " + std::to_string(kMinDepthAllowed)); + } + + if (userParameters.sampleSexEncoding != "female" && userParameters.sampleSexEncoding != "male") + { + throw std::invalid_argument(userParameters.sampleSexEncoding + " is not a valid sex encoding"); + } + + // Heuristic parameters + if (userParameters.alignerType != "dag-aligner" && userParameters.alignerType != "path-aligner") + { + throw std::invalid_argument(userParameters.alignerType + " is not a valid aligner type"); + } + + const int kMinExtensionLength = 500; + const int kMaxExtensionLength = 1500; + if (userParameters.regionExtensionLength < kMinExtensionLength + && userParameters.regionExtensionLength > kMaxExtensionLength) + { + const string message = "Extension length of size " + to_string(userParameters.regionExtensionLength) + + " is not supported; the range of allowed extensions is between " + to_string(kMinExtensionLength) + + " and " + to_string(kMaxExtensionLength); + throw std::invalid_argument(message); + } + + const int kMinQualityCutoffForGoodBaseCall = 5; + const int kMaxQualityCutoffForGoodBaseCall = 40; + if (userParameters.qualityCutoffForGoodBaseCall < kMinQualityCutoffForGoodBaseCall + && userParameters.qualityCutoffForGoodBaseCall > kMaxQualityCutoffForGoodBaseCall) + { + const string message = "Base call quality cutoff of " + to_string(userParameters.qualityCutoffForGoodBaseCall) + + " is not supported; the range of allowed cutoffs is between " + + to_string(kMinQualityCutoffForGoodBaseCall) + " and " + to_string(kMaxQualityCutoffForGoodBaseCall); + throw std::invalid_argument(message); + } +} + +SampleParameters decodeSampleParameters(const UserParameters& userParams) +{ + fs::path boostHtsFilePath(userParams.htsFilePath); + auto sampleId = boostHtsFilePath.stem().string(); + + Sex sex = decodeSampleSex(userParams.sampleSexEncoding); + + int readLength + = userParams.optionalReadLength ? *userParams.optionalReadLength : extractReadLength(userParams.htsFilePath); + + if (userParams.optionalGenomeCoverage) + { + const double haplotypeDepth = *userParams.optionalGenomeCoverage / 2; + return SampleParameters(sampleId, sex, readLength, haplotypeDepth); + } + else + { + return SampleParameters(sampleId, sex, readLength); + } +} + +boost::optional tryLoadingProgramParameters(int argc, char** argv) +{ + auto optionalUserParameters = tryParsingUserParameters(argc, argv); + if (!optionalUserParameters) + { + return boost::optional(); + } + + const auto& userParams = *optionalUserParameters; + assertValidity(userParams); + + InputPaths inputPaths(userParams.htsFilePath, userParams.referencePath, userParams.catalogPath); + const string vcfPath = userParams.outputPrefix + ".vcf"; + const string jsonPath = userParams.outputPrefix + ".json"; + const string logPath = userParams.outputPrefix + ".log"; + OutputPaths outputPaths(vcfPath, jsonPath, logPath); + SampleParameters sampleParameters = decodeSampleParameters(userParams); + HeuristicParameters heuristicParameters( + userParams.verboseLogging, userParams.regionExtensionLength, userParams.qualityCutoffForGoodBaseCall, + userParams.skipUnaligned, userParams.alignerType); + + return ProgramParameters(inputPaths, outputPaths, sampleParameters, heuristicParameters); +} + +} diff --git a/input/ParameterLoading.hh b/input/ParameterLoading.hh new file mode 100755 index 0000000..06c8880 --- /dev/null +++ b/input/ParameterLoading.hh @@ -0,0 +1,32 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include + +#include "common/Parameters.hh" + +namespace ehunter +{ + +boost::optional tryLoadingProgramParameters(int argc, char** argv); + +} diff --git a/input/RegionGraph.cpp b/input/RegionGraph.cpp new file mode 100755 index 0000000..e9ffd1d --- /dev/null +++ b/input/RegionGraph.cpp @@ -0,0 +1,118 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "input/RegionGraph.hh" + +#include +#include +#include + +#include "input/GraphBlueprint.hh" + +using graphtools::Graph; +using graphtools::NodeId; +using std::string; +using std::vector; + +namespace ehunter +{ + +int getNumNodes(const GraphBlueprint& blueprint) +{ + int numNodes = 0; + for (const auto& feature : blueprint) + { + numNodes += feature.sequences.size(); + } + + return numNodes; +} + +static void setFeatureSequences(const GraphBlueprintFeature& feature, Graph& graph) +{ + assert(feature.nodeIds.size() == feature.sequences.size()); + for (int index = 0; index != static_cast(feature.sequences.size()); ++index) + { + graph.setNodeSeq(feature.nodeIds[index], feature.sequences[index]); + } +} + +static void +connectFeatures(const GraphBlueprintFeature& sourceFeature, const GraphBlueprintFeature& sinkFeature, Graph& graph) +{ + for (NodeId nodeIdOfPreviousFeature : sourceFeature.nodeIds) + { + for (NodeId nodeIdOfNewFeature : sinkFeature.nodeIds) + { + graph.addEdge(nodeIdOfPreviousFeature, nodeIdOfNewFeature); + } + } +} + +static void setInternalFeatureEdges(const GraphBlueprintFeature& feature, Graph& graph) +{ + const bool isRepeat = feature.type == GraphBlueprintFeatureType::kSkippableRepeat + || feature.type == GraphBlueprintFeatureType::kUnskippableRepeat; + + if (isRepeat) + { + assert(feature.nodeIds.size() == 1); + const NodeId nodeId = feature.nodeIds.front(); + graph.addEdge(nodeId, nodeId); + } +} + +void setOutgoingFeatureEdges(const GraphBlueprint& blueprint, int index, Graph& graph) +{ + const GraphBlueprintFeature& currentFeature = blueprint[index]; + const GraphBlueprintFeature* downstreamFeaturePtr = &blueprint[++index]; + + while (isSkippable(downstreamFeaturePtr->type)) + { + connectFeatures(currentFeature, *downstreamFeaturePtr, graph); + downstreamFeaturePtr = &blueprint[++index]; + } + + connectFeatures(currentFeature, *downstreamFeaturePtr, graph); +} + +Graph makeRegionGraph(const GraphBlueprint& blueprint) +{ + // Implicit assumptions about the graph structure + assert(blueprint.front().type == GraphBlueprintFeatureType::kLeftFlank); + assert(blueprint.back().type == GraphBlueprintFeatureType::kRightFlank); + + Graph graph(getNumNodes(blueprint)); + + for (const auto& feature : blueprint) + { + setFeatureSequences(feature, graph); + setInternalFeatureEdges(feature, graph); + } + + for (int index = 0; index != static_cast(blueprint.size()) - 1; ++index) + { + setOutgoingFeatureEdges(blueprint, index, graph); + } + + return graph; +} + +} diff --git a/input/RegionGraph.hh b/input/RegionGraph.hh new file mode 100755 index 0000000..57dbf92 --- /dev/null +++ b/input/RegionGraph.hh @@ -0,0 +1,34 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include + +#include "graphcore/Graph.hh" + +#include "input/GraphBlueprint.hh" + +namespace ehunter +{ + +graphtools::Graph makeRegionGraph(const GraphBlueprint& blueprint); + +} diff --git a/input/SampleStats.cpp b/input/SampleStats.cpp new file mode 100755 index 0000000..8a15ea6 --- /dev/null +++ b/input/SampleStats.cpp @@ -0,0 +1,87 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "input/SampleStats.hh" + +#include + +#include "htslib/hts.h" +#include "htslib/sam.h" + +using std::string; + +namespace ehunter +{ + +int extractReadLength(const string& bamPath) +{ + // Open a BAM file for reading. + samFile* htsFilePtr = sam_open(bamPath.c_str(), "r"); + if (!htsFilePtr) + { + throw std::runtime_error("Failed to read BAM file '" + bamPath + "'"); + } + bam_hdr_t* htsHeaderPtr = sam_hdr_read(htsFilePtr); + if (!htsHeaderPtr) + { + throw std::runtime_error("BamFile::Init: Failed to read BAM header: '" + bamPath + "'"); + } + + enum + { + kSupplementaryAlign = 0x800, + kSecondaryAlign = 0x100 + }; + + int readLength = 99; + bam1_t* htsAlignmentPtr = bam_init1(); + int ret; + while ((ret = sam_read1(htsFilePtr, htsHeaderPtr, htsAlignmentPtr)) >= 0) + { + const bool isSupplementary = htsAlignmentPtr->core.flag & kSupplementaryAlign; + const bool isSecondary = htsAlignmentPtr->core.flag & kSecondaryAlign; + const bool isPrimaryAlign = (!isSupplementary) && (!isSecondary); + if (isPrimaryAlign) + { + readLength = htsAlignmentPtr->core.l_qseq; + break; + } + } + + if (ret < 0) + { + throw std::runtime_error("Failed to extract a read from BAM file"); + } + + bam_destroy1(htsAlignmentPtr); + bam_hdr_destroy(htsHeaderPtr); + sam_close(htsFilePtr); + + return readLength; +} + +bool isBamFile(const string& htsFilePath) +{ + string extension = boost::filesystem::extension(htsFilePath); + return extension == ".bam"; +} + +} diff --git a/common/timestamp.cc b/input/SampleStats.hh old mode 100644 new mode 100755 similarity index 64% rename from common/timestamp.cc rename to input/SampleStats.hh index 4e34975..b387dca --- a/common/timestamp.cc +++ b/input/SampleStats.hh @@ -1,3 +1,8 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko // Concept: Michael Eberle // // This program is free software: you can redistribute it and/or modify @@ -14,16 +19,17 @@ // along with this program. If not, see . // -#include "common/timestamp.h" +#pragma once -namespace ehunter { +#include -std::string TimeStamp() { - std::time_t now = time(0); - const size_t timestamp_size = 80; - char timestamp_buf[timestamp_size]; - std::strftime(timestamp_buf, timestamp_size, "%FT%T", std::localtime(&now)); - return std::string(timestamp_buf); -} +namespace ehunter +{ -} // namespace ehunter +// Returns the length of the first read in a BAM file +int extractReadLength(const std::string& bamPath); + +// Checks if a file is in the BAM format by checking the extension +bool isBamFile(const std::string& htsFilePath); + +} diff --git a/input/tests/CMakeLists.txt b/input/tests/CMakeLists.txt new file mode 100755 index 0000000..e336670 --- /dev/null +++ b/input/tests/CMakeLists.txt @@ -0,0 +1,12 @@ +add_executable(LocusSpecificationTest LocusSpecificationTest.cpp) +target_link_libraries(LocusSpecificationTest input gtest gmock_main) +add_test(NAME LocusSpecificationTest COMMAND LocusSpecificationTest) + +add_executable(RegionGraphTest RegionGraphTest.cpp) +target_link_libraries(RegionGraphTest input gtest gmock_main) +add_test(NAME RegionGraphTest COMMAND RegionGraphTest) + +add_executable(GraphBlueprintTest GraphBlueprintTest.cpp) +target_link_libraries(GraphBlueprintTest input gtest gmock_main) +add_test(NAME GraphBlueprintTest COMMAND GraphBlueprintTest) + diff --git a/input/tests/GraphBlueprintTest.cpp b/input/tests/GraphBlueprintTest.cpp new file mode 100755 index 0000000..1c14eda --- /dev/null +++ b/input/tests/GraphBlueprintTest.cpp @@ -0,0 +1,62 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "input/GraphBlueprint.hh" + +#include "gtest/gtest.h" + +using std::string; +using std::vector; + +using namespace ehunter; + +TEST(SplittingStringsIntoTokens, ValidStrings_Split) +{ + const string regex = "ATGC(CAG)+GTCG(AAA|TTT)(AGTC)?(CAG)*"; + auto tokens = tokenizeRegex(regex); + + vector expectedTokens = { "ATGC", "(CAG)+", "GTCG", "(AAA|TTT)", "(AGTC)?", "(CAG)*" }; + ASSERT_EQ(expectedTokens, tokens); +} + +TEST(ParsingTokens, TypicalTokens_Parsed) +{ + TokenParser parser; + { + FeatureTypeAndSequences expectedResult = { GraphBlueprintFeatureType::kInsertionOrDeletion, { "AGTC" } }; + EXPECT_EQ(expectedResult, parser.parse("(AGTC)?")); + } + { + FeatureTypeAndSequences expectedResult = { GraphBlueprintFeatureType::kSkippableRepeat, { "CAG" } }; + EXPECT_EQ(expectedResult, parser.parse("(CAG)*")); + } + { + FeatureTypeAndSequences expectedResult = { GraphBlueprintFeatureType::kUnskippableRepeat, { "CAG" } }; + EXPECT_EQ(expectedResult, parser.parse("(CAG)+")); + } + { + FeatureTypeAndSequences expectedResult = { GraphBlueprintFeatureType::kInterruption, { "GTCG" } }; + EXPECT_EQ(expectedResult, parser.parse("GTCG")); + } + { + FeatureTypeAndSequences expectedResult = { GraphBlueprintFeatureType::kSwap, { "AAA", "TTT" } }; + EXPECT_EQ(expectedResult, parser.parse("(AAA|TTT)")); + } +} \ No newline at end of file diff --git a/input/tests/LocusSpecificationTest.cpp b/input/tests/LocusSpecificationTest.cpp new file mode 100755 index 0000000..c6a03d3 --- /dev/null +++ b/input/tests/LocusSpecificationTest.cpp @@ -0,0 +1,86 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "region_spec/LocusSpecification.hh" + +#include "gtest/gtest.h" + +using std::string; +using std::vector; + +using namespace ehunter; + +/* +TEST(CreatingRegionSpecs, SingeUnitRepeatSpec_Parsed) +{ + const string regionId = "region1"; + const vector repeatIds; + RegionSpec regionSpec(regionId, repeatIds, Region("chr1:1-10"), "GCC", "AT", "GCC", "TA"); + + vector expectedComponents = { RegionComponent("LF", "AT", RegionComponentType::kFlank), + RegionComponent("Repeat0", "GCC", RegionComponentType::kRepeat), + RegionComponent("RF", "TA", RegionComponentType::kFlank) }; + + ASSERT_EQ(expectedComponents, regionSpec.components()); +} + +TEST(CreatingRegionSpecs, MultiunitFromatToSpecifySingleUnitRepeat_Parsed) +{ + const string regionId = "region1"; + const vector repeatIds; + RegionSpec regionSpec(regionId, repeatIds, Region("chr1:1-10"), "(AGG)", "AT", "AGG", "TA"); + + vector expectedComponents = { RegionComponent("LF", "AT", RegionComponentType::kFlank), + RegionComponent("Repeat0", "AGG", RegionComponentType::kRepeat), + RegionComponent("RF", "TA", RegionComponentType::kFlank) }; + + ASSERT_EQ(expectedComponents, regionSpec.components()); +} + +TEST(CreatingRegionSpecs, TypicalMultiunitRepeatSpecs_Parsed) +{ + const string regionId = "region1"; + const vector repeatIds; + + { + RegionSpec regionSpec(regionId, repeatIds, Region("chr1:1-10"), "(AGG)(CG)", "AT", "AGGCG", "TA"); + + vector expectedComponents = { RegionComponent("LF", "AT", RegionComponentType::kFlank), + RegionComponent("Repeat0", "AGG", RegionComponentType::kRepeat), + RegionComponent("Repeat1", "CG", RegionComponentType::kRepeat), + RegionComponent("RF", "TA", RegionComponentType::kFlank) }; + + EXPECT_EQ(expectedComponents, regionSpec.components()); + } + + { + RegionSpec regionSpec(regionId, repeatIds, Region("chr1:1-10"), "(AGG)ATG(CG)", "CC", "AGGATGCG", "GG"); + + vector expectedComponents = { RegionComponent("LF", "CC", RegionComponentType::kFlank), + RegionComponent("Repeat0", "AGG", RegionComponentType::kRepeat), + RegionComponent("", "ATG", RegionComponentType::kInterruption), + RegionComponent("Repeat1", "CG", RegionComponentType::kRepeat), + RegionComponent("RF", "GG", RegionComponentType::kFlank) }; + + EXPECT_EQ(expectedComponents, regionSpec.components()); + } +} +*/ \ No newline at end of file diff --git a/input/tests/RegionGraphTest.cpp b/input/tests/RegionGraphTest.cpp new file mode 100755 index 0000000..f15d8ca --- /dev/null +++ b/input/tests/RegionGraphTest.cpp @@ -0,0 +1,73 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "input/RegionGraph.hh" + +#include "gtest/gtest.h" + +#include "graphcore/Graph.hh" + +#include "input/GraphBlueprint.hh" +#include "region_spec/LocusSpecification.hh" + +using graphtools::Graph; +using std::string; + +using namespace ehunter; + +TEST(ConstructingRepeatRegionGraphs, SingleUnitStr_GraphConstructed) +{ + Graph graph = makeRegionGraph(decodeFeaturesFromRegex("ATTCGA(C)*ATGTCG")); + + ASSERT_EQ(3u, graph.numNodes()); + ASSERT_EQ("ATTCGA", graph.nodeSeq(0)); + ASSERT_EQ("C", graph.nodeSeq(1)); + ASSERT_EQ("ATGTCG", graph.nodeSeq(2)); + + EXPECT_TRUE(graph.hasEdge(0, 1)); + EXPECT_TRUE(graph.hasEdge(1, 1)); + EXPECT_TRUE(graph.hasEdge(1, 2)); +} + +TEST(ConstructingRepeatRegionGraphs, MultiUnitStr_GraphConstructed) +{ + Graph graph = makeRegionGraph(decodeFeaturesFromRegex("AAAATT(AGG)*ATG(CG)*GGGGCC")); + + ASSERT_EQ(5u, graph.numNodes()); + EXPECT_EQ(8u, graph.numEdges()); + + EXPECT_EQ("AAAATT", graph.nodeSeq(0)); + EXPECT_EQ("AGG", graph.nodeSeq(1)); + EXPECT_EQ("ATG", graph.nodeSeq(2)); + EXPECT_EQ("CG", graph.nodeSeq(3)); + EXPECT_EQ("GGGGCC", graph.nodeSeq(4)); + + EXPECT_TRUE(graph.hasEdge(0, 1)); + EXPECT_TRUE(graph.hasEdge(0, 2)); + + EXPECT_TRUE(graph.hasEdge(1, 1)); + EXPECT_TRUE(graph.hasEdge(1, 2)); + + EXPECT_TRUE(graph.hasEdge(2, 3)); + EXPECT_TRUE(graph.hasEdge(2, 4)); + + EXPECT_TRUE(graph.hasEdge(3, 3)); + EXPECT_TRUE(graph.hasEdge(3, 4)); +} diff --git a/output/CMakeLists.txt b/output/CMakeLists.txt new file mode 100755 index 0000000..c1cef5f --- /dev/null +++ b/output/CMakeLists.txt @@ -0,0 +1,3 @@ +file(GLOB SOURCES "*.cpp") +add_library(output ${SOURCES}) +target_link_libraries(output common stats genotyping region_spec) diff --git a/output/JsonWriter.cpp b/output/JsonWriter.cpp new file mode 100755 index 0000000..2fa590f --- /dev/null +++ b/output/JsonWriter.cpp @@ -0,0 +1,169 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "output/JsonWriter.hh" + +#include +#include + +#include +#include + +#include "stats/ReadSupportCalculator.hh" + +namespace ehunter +{ + +using std::map; +using std::string; +using Json = nlohmann::json; +using boost::optional; +using std::to_string; +using std::vector; + +std::ostream& operator<<(std::ostream& out, JsonWriter& jsonWriter) +{ + jsonWriter.write(out); + return out; +} + +JsonWriter::JsonWriter( + const string& sampleName, int readLength, const RegionCatalog& regionCatalog, const SampleFindings& sampleFindings) + : sampleName_(sampleName) + , readLength_(readLength) + , regionCatalog_(regionCatalog) + , sampleFindings_(sampleFindings) +{ +} + +void JsonWriter::write(std::ostream& out) +{ + Json array = Json::array(); + + for (const auto& regionIdAndFindings : sampleFindings_) + { + const string& regionId = regionIdAndFindings.first; + const LocusSpecification& regionSpec = regionCatalog_.at(regionId); + const RegionFindings& regionFindings = regionIdAndFindings.second; + + for (const auto& variantIdAndFindings : regionFindings) + { + const string& variantId = variantIdAndFindings.first; + const VariantSpecification& variantSpec = regionSpec.getVariantSpecById(variantId); + + VariantJsonWriter variantWriter(regionSpec, variantSpec, readLength_); + variantIdAndFindings.second->accept(&variantWriter); + array.push_back(variantWriter.record()); + } + }; + + out << std::setw(4) << array << std::endl; +} + +template static string streamToString(const T& streamableObject) +{ + std::stringstream out; + out << streamableObject; + return out.str(); +} + +static string encodeSupportCounts(const ReadSupportCalculator& readSupportCalculator, int repeatSize) +{ + const string countsOfSpanningReads = to_string(readSupportCalculator.getCountOfConsistentSpanningReads(repeatSize)); + const string countsOfFlankingReads = to_string(readSupportCalculator.getCountOfConsistentFlankingReads(repeatSize)); + return countsOfSpanningReads + "-" + countsOfFlankingReads; +} + +static string encodeRepeatAlleleSupport(const string& repeatUnit, const RepeatFindings& repeatFindings, int readLength) +{ + const int maxUnitsInRead = std::ceil(readLength / static_cast(repeatUnit.length())); + ReadSupportCalculator readSupportCalculator( + maxUnitsInRead, repeatFindings.countsOfSpanningReads(), repeatFindings.countsOfFlankingReads()); + + assert(repeatFindings.optionalGenotype()); + const RepeatGenotype& genotype = *repeatFindings.optionalGenotype(); + + string supportEncoding = encodeSupportCounts(readSupportCalculator, genotype.shortAlleleSizeInUnits()); + + if (genotype.numAlleles() == 2) + { + supportEncoding += "/" + encodeSupportCounts(readSupportCalculator, genotype.longAlleleSizeInUnits()); + } + + return supportEncoding; +} + +static string encodeGenotype(const RepeatGenotype& genotype) +{ + string encoding = std::to_string(genotype.shortAlleleSizeInUnits()); + + if (genotype.numAlleles() == 2) + { + encoding = encoding + "/" + std::to_string(genotype.longAlleleSizeInUnits()); + } + + return encoding; +} + +void VariantJsonWriter::visit(const RepeatFindings* repeatFindingsPtr) +{ + assert(variantSpec_.classification().type == VariantType::kRepeat); + + const RepeatFindings& repeatFindings = *repeatFindingsPtr; + if (repeatFindings.optionalGenotype()) + { + record_.clear(); + record_["VariantId"] = variantSpec_.id(); + record_["ReferenceRegion"] = streamToString(variantSpec_.referenceLocus()); + record_["VariantType"] = streamToString(variantSpec_.classification().type); + record_["VariantSubtype"] = streamToString(variantSpec_.classification().subtype); + + const auto repeatNodeId = variantSpec_.nodes().front(); + const auto& repeatUnit = regionSpec_.regionGraph().nodeSeq(repeatNodeId); + record_["RepeatUnit"] = repeatUnit; + + record_["Genotype"] = encodeGenotype(*repeatFindings.optionalGenotype()); + record_["GenotypeConfidenceInterval"] = streamToString(*repeatFindings.optionalGenotype()); + record_["GenotypeSupport"] = encodeRepeatAlleleSupport(repeatUnit, repeatFindings, readLength_); + record_["CountsOfSpanningReads"] = streamToString(repeatFindings.countsOfSpanningReads()); + record_["CountsOfFlankingReads"] = streamToString(repeatFindings.countsOfFlankingReads()); + record_["CountsOfInrepeatReads"] = streamToString(repeatFindings.countsOfInrepeatReads()); + } +} + +void VariantJsonWriter::visit(const SmallVariantFindings* smallVariantFindingsPtr) +{ + const SmallVariantFindings& indelFindings = *smallVariantFindingsPtr; + record_.clear(); + record_["VariantId"] = variantSpec_.id(); + record_["VariantType"] = streamToString(variantSpec_.classification().type); + record_["VariantSubtype"] = streamToString(variantSpec_.classification().subtype); + record_["ReferenceRegion"] = streamToString(variantSpec_.referenceLocus()); + record_["CountOfRefReads"] = indelFindings.numRefReads(); + record_["CountOfAltReads"] = indelFindings.numAltReads(); + record_["StatusOfRefAllele"] = streamToString(indelFindings.refAllelePresenceStatus()); + record_["StatusOfAltAllele"] = streamToString(indelFindings.altAllelePresenceStatus()); + if (indelFindings.optionalGenotype() != boost::none) + { + record_["Genotype"] = streamToString(*indelFindings.optionalGenotype()); + } +} + +} diff --git a/output/JsonWriter.hh b/output/JsonWriter.hh new file mode 100755 index 0000000..9eaf204 --- /dev/null +++ b/output/JsonWriter.hh @@ -0,0 +1,71 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include "region_analysis/VariantFindings.hh" +#include "region_spec/LocusSpecification.hh" + +#include "thirdparty/json/json.hpp" + +namespace ehunter +{ + +class VariantJsonWriter : public VariantFindingsVisitor +{ +public: + VariantJsonWriter(const LocusSpecification& regionSpec, const VariantSpecification& variantSpec, int readLength) + : regionSpec_(regionSpec) + , variantSpec_(variantSpec) + , readLength_(readLength) + { + } + + ~VariantJsonWriter() = default; + void visit(const RepeatFindings* repeatFindingsPtr); + void visit(const SmallVariantFindings* smallVariantFindingsPtr); + nlohmann::json record() const { return record_; } + +private: + const LocusSpecification& regionSpec_; + const VariantSpecification& variantSpec_; + const int readLength_; + nlohmann::json record_; +}; + +class JsonWriter +{ +public: + JsonWriter( + const std::string& sampleName, int readLength, const RegionCatalog& regionCatalog, + const SampleFindings& sampleFindings); + + void write(std::ostream& out); + +private: + const std::string sampleName_; + const int readLength_; + const RegionCatalog& regionCatalog_; + const SampleFindings& sampleFindings_; +}; + +std::ostream& operator<<(std::ostream& out, JsonWriter& jsonWriter); + +} diff --git a/output/VcfHeader.cpp b/output/VcfHeader.cpp new file mode 100755 index 0000000..c9502d9 --- /dev/null +++ b/output/VcfHeader.cpp @@ -0,0 +1,199 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "output/VcfHeader.hh" + +#include + +using std::ostream; +using std::string; + +namespace ehunter +{ + +void FieldDescriptionWriter::visit(const RepeatFindings* repeatFindingsPtr) +{ + if (!repeatFindingsPtr->optionalGenotype()) + { + return; + } + + tryAddingFieldDescription(FieldType::kInfo, "SVTYPE", "1", "String", "Type of structural variant"); + tryAddingFieldDescription(FieldType::kInfo, "END", "1", "Integer", "End position of the variant"); + tryAddingFieldDescription(FieldType::kInfo, "REF", "1", "Integer", "Reference copy number"); + tryAddingFieldDescription(FieldType::kInfo, "RL", "1", "Integer", "Reference length in bp"); + tryAddingFieldDescription(FieldType::kInfo, "RU", "1", "String", "Repeat unit in the reference orientation"); + + const string kRepidFieldDescription = "Repeat identifier from the repeat specification file"; + tryAddingFieldDescription(FieldType::kInfo, "REPID", "1", "String", kRepidFieldDescription); + tryAddingFieldDescription(FieldType::kFormat, "GT", "1", "String", "Genotype"); + + const string kSoFieldDescription + = "Type of reads that support the allele; can be SPANNING, FLANKING, or INREPEAT meaning that the reads span, " + "flank, or are fully contained in the repeat"; + tryAddingFieldDescription(FieldType::kFormat, "SO", "1", "String", kSoFieldDescription); + + const string kRepcnFieldDescription = "Number of repeat units spanned by the allele"; + tryAddingFieldDescription(FieldType::kFormat, "REPCN", "1", "String", kRepcnFieldDescription); + tryAddingFieldDescription(FieldType::kFormat, "REPCI", "1", "String", "Confidence interval for REPCN"); + + const string kAdflFieldDescription = "Number of flanking reads consistent with the allele"; + tryAddingFieldDescription(FieldType::kFormat, "ADFL", "1", "String", kAdflFieldDescription); + + const string kAdspFieldDescription = "Number of spanning reads consistent with the allele"; + tryAddingFieldDescription(FieldType::kFormat, "ADSP", "1", "String", kAdspFieldDescription); + + const string kAdirFieldDescription = "Number of in-repeat reads consistent with the allele"; + tryAddingFieldDescription(FieldType::kFormat, "ADIR", "1", "String", kAdirFieldDescription); + + tryAddingFieldDescription(FieldType::kFilter, "PASS", "", "", "All filters passed"); + + const auto repeatNodeId = variantSpec_.nodes().front(); + const string& repeatUnit = regionSpec_.regionGraph().nodeSeq(repeatNodeId); + const auto& referenceLocus = variantSpec_.referenceLocus(); + const int referenceSize = referenceLocus.length() / repeatUnit.length(); + + const RepeatGenotype& genotype = repeatFindingsPtr->optionalGenotype().get(); + + if (genotype.shortAlleleSizeInUnits() != referenceSize) + { + const string sizeEncoding = std::to_string(genotype.shortAlleleSizeInUnits()); + const string description = "Allele comprised of " + sizeEncoding + " repeat units"; + tryAddingFieldDescription(FieldType::kAlt, "STR" + sizeEncoding, "", "", description); + } + + if (genotype.longAlleleSizeInUnits() != referenceSize) + { + const string sizeEncoding = std::to_string(genotype.longAlleleSizeInUnits()); + const string description = "Allele comprised of " + sizeEncoding + " repeat units"; + tryAddingFieldDescription(FieldType::kAlt, "STR" + sizeEncoding, "", "", description); + } +} + +void FieldDescriptionWriter::visit(const SmallVariantFindings* smallVariantFindingsPtr) +{ + if (!smallVariantFindingsPtr->optionalGenotype()) + { + return; + } + + tryAddingFieldDescription(FieldType::kFormat, "GT", "1", "String", "Genotype"); + tryAddingFieldDescription(FieldType::kFilter, "PASS", "", "", "All filters passed"); +} + +void FieldDescriptionWriter::tryAddingFieldDescription( + FieldType fieldType, const string& id, const string& number, const string& contentType, const string& description) +{ + const auto key = std::make_pair(fieldType, id); + if (fieldDescriptions_.find(key) == fieldDescriptions_.end()) + { + FieldDescription fieldDescription( + fieldType, id, std::move(number), std::move(contentType), std::move(description)); + fieldDescriptions_.emplace(std::make_pair(std::move(key), std::move(fieldDescription))); + } +} + +void FieldDescriptionWriter::dumpTo(FieldDescriptionCatalog& descriptionCatalog) +{ + descriptionCatalog.insert(fieldDescriptions_.begin(), fieldDescriptions_.end()); +} + +FieldDescription::FieldDescription( + FieldType fieldType, string id, string number, string contentType, string description) + : fieldType(fieldType) + , id(std::move(id)) + , number(std::move(number)) + , contentType(std::move(contentType)) + , description(std::move(description)) +{ +} + +void outputVcfHeader(const RegionCatalog& regionCatalog, const SampleFindings& sampleFindings, ostream& out) +{ + out << "##fileformat=VCFv4.1\n"; + + FieldDescriptionCatalog fieldDescriptionCatalog; + + for (const auto& regionIdAndFindings : sampleFindings) + { + const string& regionId = regionIdAndFindings.first; + const LocusSpecification& regionSpec = regionCatalog.at(regionId); + const RegionFindings& regionFindings = regionIdAndFindings.second; + + for (const auto& variantIdAndFindings : regionFindings) + { + const string& variantId = variantIdAndFindings.first; + const VariantSpecification& variantSpec = regionSpec.getVariantSpecById(variantId); + + FieldDescriptionWriter descriptionWriter(regionSpec, variantSpec); + variantIdAndFindings.second->accept(&descriptionWriter); + descriptionWriter.dumpTo(fieldDescriptionCatalog); + } + } + + for (const auto& fieldIdAndDescription : fieldDescriptionCatalog) + { + const auto& description = fieldIdAndDescription.second; + out << description << "\n"; + } +} + +std::ostream& operator<<(std::ostream& out, FieldType fieldType) +{ + switch (fieldType) + { + case FieldType::kAlt: + out << "ALT"; + break; + case FieldType::kFormat: + out << "FORMAT"; + break; + case FieldType::kInfo: + out << "INFO"; + break; + case FieldType::kFilter: + out << "FILTER"; + break; + } + + return out; +} + +std::ostream& operator<<(std::ostream& out, const FieldDescription& fieldDescription) +{ + switch (fieldDescription.fieldType) + { + case FieldType::kInfo: + case FieldType::kFormat: + out << "##" << fieldDescription.fieldType << "="; + break; + case FieldType::kAlt: + case FieldType::kFilter: + out << "##" << fieldDescription.fieldType << "="; + break; + } + + return out; +} + +} diff --git a/output/VcfHeader.hh b/output/VcfHeader.hh new file mode 100755 index 0000000..ce36582 --- /dev/null +++ b/output/VcfHeader.hh @@ -0,0 +1,87 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include + +#include "region_analysis/VariantFindings.hh" +#include "region_spec/LocusSpecification.hh" + +namespace ehunter +{ + +enum class FieldType +{ + kInfo = 0, + kFilter = 1, + kFormat = 2, + kAlt = 3 +}; + +struct FieldDescription +{ + FieldDescription( + FieldType fieldType, std::string id, std::string number, std::string contentType, std::string description); + FieldType fieldType; + std::string id; + std::string number; + std::string contentType; + std::string description; +}; + +using FieldDescriptionIdentifier = std::pair; +using FieldDescriptionCatalog = std::map; + +// Generates VCF field descriptions required for a given variant call +class FieldDescriptionWriter : public VariantFindingsVisitor +{ +public: + FieldDescriptionWriter(const LocusSpecification& regionSpec, const VariantSpecification& variantSpec) + : regionSpec_(regionSpec) + , variantSpec_(variantSpec) + { + } + + ~FieldDescriptionWriter() = default; + + void visit(const RepeatFindings* repeatFindingsPtr) override; + void visit(const SmallVariantFindings* smallVariantFindingsPtr) override; + + void tryAddingFieldDescription( + FieldType fieldType, const std::string& id, const std::string& number, const std::string& contentType, + const std::string& description); + + void dumpTo(FieldDescriptionCatalog& descriptionCatalog); + +private: + const LocusSpecification& regionSpec_; + const VariantSpecification& variantSpec_; + FieldDescriptionCatalog fieldDescriptions_; +}; + +void outputVcfHeader(const RegionCatalog& regionCatalog, const SampleFindings& sampleFindings, std::ostream& out); + +std::ostream& operator<<(std::ostream& out, FieldType fieldType); +std::ostream& operator<<(std::ostream& out, const FieldDescription& fieldDescription); + +} \ No newline at end of file diff --git a/output/VcfWriter.cpp b/output/VcfWriter.cpp new file mode 100755 index 0000000..83f4797 --- /dev/null +++ b/output/VcfWriter.cpp @@ -0,0 +1,330 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "output/VcfWriter.hh" + +#include +#include +#include +#include + +#include + +#include "output/VcfHeader.hh" +#include "output/VcfWriterHelpers.hh" +#include "stats/ReadSupportCalculator.hh" + +using std::deque; +using std::map; +using std::ostream; +using std::pair; +using std::set; +using std::string; +using std::to_string; +using std::vector; + +namespace ehunter +{ + +template static string streamToString(const T& streamableObject) +{ + std::stringstream out; + out << streamableObject; + return out.str(); +} + +void writeBodyHeader(const string& sampleName, ostream& out) +{ + out << "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO\tFORMAT\t" << sampleName << "\n"; +} + +std::ostream& operator<<(std::ostream& out, VcfWriter& vcfWriter) +{ + outputVcfHeader(vcfWriter.regionCatalog_, vcfWriter.sampleFindings_, out); + writeBodyHeader(vcfWriter.sampleName_, out); + vcfWriter.writeBody(out); + return out; +} + +VcfWriter::VcfWriter( + const string& sampleName, int readLength, const RegionCatalog& regionCatalog, const SampleFindings& sampleFindings, + Reference& reference) + : sampleName_(sampleName) + , readLength_(readLength) + , regionCatalog_(regionCatalog) + , sampleFindings_(sampleFindings) + , reference_(reference) +{ +} + +void VcfWriter::writeBody(ostream& out) +{ + for (const auto& regionIdAndFindings : sampleFindings_) + { + const string& regionId = regionIdAndFindings.first; + const LocusSpecification& regionSpec = regionCatalog_.at(regionId); + const RegionFindings& regionFindings = regionIdAndFindings.second; + + for (const auto& variantIdAndFindings : regionFindings) + { + const string& variantId = variantIdAndFindings.first; + const VariantSpecification& variantSpec = regionSpec.getVariantSpecById(variantId); + + VariantVcfWriter variantWriter(regionSpec, variantSpec, readLength_, reference_, out); + variantIdAndFindings.second->accept(&variantWriter); + } + } +} + +static string createRepeatAlleleSymbol(int repeatSize) { return ""; } + +static string computeAltSymbol(const RepeatGenotype& genotype, int referenceSizeInUnits) +{ + vector alleleEncodings; + + if (genotype.shortAlleleSizeInUnits() != referenceSizeInUnits) + { + alleleEncodings.push_back(createRepeatAlleleSymbol(genotype.shortAlleleSizeInUnits())); + } + + if (genotype.longAlleleSizeInUnits() != referenceSizeInUnits + && genotype.shortAlleleSizeInUnits() != genotype.longAlleleSizeInUnits()) + { + alleleEncodings.push_back(createRepeatAlleleSymbol(genotype.longAlleleSizeInUnits())); + } + + if (alleleEncodings.empty()) + { + return "."; + } + + return boost::algorithm::join(alleleEncodings, ","); +} + +static string computeInfoFields(const VariantSpecification& variantSpec, const string& repeatUnit) +{ + const auto& referenceLocus = variantSpec.referenceLocus(); + const int referenceSizeInBp = referenceLocus.length(); + const int referenceSizeInUnits = referenceSizeInBp / repeatUnit.length(); + + vector fields; + fields.push_back("END=" + std::to_string(referenceLocus.end())); + fields.push_back("REF=" + std::to_string(referenceSizeInUnits)); + fields.push_back("RL=" + std::to_string(referenceSizeInUnits)); + fields.push_back("RU=" + repeatUnit); + fields.push_back("REPID=" + variantSpec.id()); + + return boost::algorithm::join(fields, ";"); +} + +static ReadType +deterimineSupportType(int maxUnitsInRead, CountTable spanningCounts, CountTable flankingCounts, int repeatSize) +{ + if (spanningCounts.countOf(repeatSize) != 0) + { + return ReadType::kSpanning; + } + else if (flankingCounts.countOf(maxUnitsInRead) != 0) + { + return ReadType::kRepeat; + } + + return ReadType::kFlanking; +} + +static string computeSampleFields( + int readLength, const VariantSpecification& variantSpec, const string& repeatUnit, + const RepeatFindings& repeatFindings) +{ + const auto referenceLocus = variantSpec.referenceLocus(); + const int referenceSizeInBp = referenceLocus.length(); + const int referenceSizeInUnits = referenceSizeInBp / repeatUnit.length(); + const RepeatGenotype& genotype = *repeatFindings.optionalGenotype(); + + const int maxUnitsInRead = std::ceil(readLength / static_cast(repeatUnit.length())); + ReadSupportCalculator readSupportCalculator( + maxUnitsInRead, repeatFindings.countsOfSpanningReads(), repeatFindings.countsOfFlankingReads()); + + VcfSampleFields sampleFields(referenceSizeInUnits); + + const int shortAlleleSize = genotype.shortAlleleSizeInUnits(); + ReadType shortAlleleSupportType = deterimineSupportType( + maxUnitsInRead, repeatFindings.countsOfSpanningReads(), repeatFindings.countsOfFlankingReads(), + shortAlleleSize); + + sampleFields.addAlleleInfo( + shortAlleleSize, shortAlleleSupportType, genotype.shortAlleleSizeInUnitsCi(), + readSupportCalculator.getCountOfConsistentSpanningReads(shortAlleleSize), + readSupportCalculator.getCountOfConsistentFlankingReads(shortAlleleSize), + readSupportCalculator.getCountOfConsistentRepeatReads(shortAlleleSize)); + + if (genotype.numAlleles() == 2) + { + const int longAlleleSize = genotype.longAlleleSizeInUnits(); + ReadType longAlleleSupportType = deterimineSupportType( + maxUnitsInRead, repeatFindings.countsOfSpanningReads(), repeatFindings.countsOfFlankingReads(), + longAlleleSize); + + sampleFields.addAlleleInfo( + genotype.longAlleleSizeInUnits(), longAlleleSupportType, genotype.longAlleleSizeInUnitsCi(), + readSupportCalculator.getCountOfConsistentSpanningReads(longAlleleSize), + readSupportCalculator.getCountOfConsistentFlankingReads(longAlleleSize), + readSupportCalculator.getCountOfConsistentRepeatReads(longAlleleSize)); + } + + return sampleFields.encode(); +} + +void VariantVcfWriter::visit(const RepeatFindings* repeatFindingsPtr) +{ + if (!repeatFindingsPtr->optionalGenotype()) + { + return; + } + + const auto& genotype = *(repeatFindingsPtr->optionalGenotype()); + + const auto& referenceLocus = variantSpec_.referenceLocus(); + const auto repeatNodeId = variantSpec_.nodes().front(); + const string& repeatUnit = regionSpec_.regionGraph().nodeSeq(repeatNodeId); + + const int referenceSizeInUnits = referenceLocus.length() / repeatUnit.length(); + + const string altSymbol = computeAltSymbol(genotype, referenceSizeInUnits); + const string infoFields = computeInfoFields(variantSpec_, repeatUnit); + const string sampleFields = computeSampleFields(readLength_, variantSpec_, repeatUnit, *repeatFindingsPtr); + + const int posPreceedingRepeat1based = referenceLocus.start(); + const string leftFlankingBase + = reference_.getSequence(referenceLocus.chrom(), referenceLocus.start() - 1, referenceLocus.start()); + + vector vcfRecordElements = { referenceLocus.chrom(), + to_string(posPreceedingRepeat1based), + ".", + leftFlankingBase, + altSymbol, + ".", + "PASS", + infoFields, + "GT:SO:REPCN:REPCI:ADSP:ADFL:ADIR", + sampleFields }; + + out_ << boost::algorithm::join(vcfRecordElements, "\t") << std::endl; +} + +void VariantVcfWriter::visit(const SmallVariantFindings* smallVariantFindingsPtr) +{ + const auto& referenceLocus = variantSpec_.referenceLocus(); + string refSequence; + string altSequence; + int64_t startPosition = -1; + + if ((variantSpec_.classification().subtype == VariantSubtype::kSwap) + || (variantSpec_.classification().subtype == VariantSubtype::kSMN)) + { + assert(variantSpec_.optionalRefNode()); + const auto refNode = *variantSpec_.optionalRefNode(); + const int refNodeIndex = refNode == variantSpec_.nodes().front() ? 0 : 1; + const int altNodeIndex = refNode == variantSpec_.nodes().front() ? 1 : 0; + + const auto refNodeId = variantSpec_.nodes()[refNodeIndex]; + const auto altNodeId = variantSpec_.nodes()[altNodeIndex]; + + refSequence = regionSpec_.regionGraph().nodeSeq(refNodeId); + altSequence = regionSpec_.regionGraph().nodeSeq(altNodeId); + // Conversion from 0-based to 1-based coordinates + startPosition = referenceLocus.start() + 1; + } + else if (variantSpec_.classification().subtype == VariantSubtype::kDeletion) + { + const string refFlankingBase + = reference_.getSequence(referenceLocus.chrom(), referenceLocus.start() - 1, referenceLocus.start()); + + const int refNodeId = variantSpec_.nodes().front(); + refSequence = refFlankingBase + regionSpec_.regionGraph().nodeSeq(refNodeId); + altSequence = refFlankingBase; + // Conversion from 0-based to 1-based coordinates + startPosition = referenceLocus.start(); + } + else if (variantSpec_.classification().subtype == VariantSubtype::kInsertion) + { + const string refFlankingBase + = reference_.getSequence(referenceLocus.chrom(), referenceLocus.start() - 1, referenceLocus.start()); + + const int altNodeId = variantSpec_.nodes().front(); + refSequence = refFlankingBase; + altSequence = refFlankingBase + regionSpec_.regionGraph().nodeSeq(altNodeId); + // Conversion from 0-based to 1-based coordinates + startPosition = referenceLocus.start(); + } + else + { + std::ostringstream encoding; + encoding << variantSpec_.classification().type << "/" << variantSpec_.classification().subtype; + throw std::logic_error("Unable to generate VCF record for " + encoding.str()); + } + + const string infoFields = "VARID=" + variantSpec_.id(); + + std::vector sampleFields; + std::vector sampleValues; + if (variantSpec_.classification().subtype == VariantSubtype::kSMN) + { + string genotype; + switch (smallVariantFindingsPtr->refAllelePresenceStatus()) + { + case AllelePresenceStatus::kAbsent: + genotype = "1"; + break; + case AllelePresenceStatus::kPresent: + genotype = "0"; + break; + case AllelePresenceStatus::kUncertain: + genotype = "."; + break; + } + sampleFields.push_back("GT"); + sampleValues.push_back(genotype); + sampleFields.push_back("SMN"); + sampleValues.push_back(streamToString(smallVariantFindingsPtr->refAllelePresenceStatus())); + } + else + { + auto genotype = smallVariantFindingsPtr->optionalGenotype(); + sampleFields.push_back("GT"); + sampleValues.push_back((genotype != boost::none) ? streamToString(*genotype) : "."); + } + + const string sampleField = boost::algorithm::join(sampleFields, ":"); + const string sampleValue = boost::algorithm::join(sampleValues, ":"); + vector line{ referenceLocus.chrom(), + streamToString(startPosition), + ".", + refSequence, + altSequence, + ".", + "PASS", + infoFields, + sampleField, + sampleValue }; + out_ << boost::algorithm::join(line, "\t") << std::endl; +} + +} diff --git a/output/VcfWriter.hh b/output/VcfWriter.hh new file mode 100755 index 0000000..ac23bbb --- /dev/null +++ b/output/VcfWriter.hh @@ -0,0 +1,84 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include +#include + +#include "common/Reference.hh" +#include "region_analysis/VariantFindings.hh" +#include "region_spec/LocusSpecification.hh" + +namespace ehunter +{ + +class VariantVcfWriter : public VariantFindingsVisitor +{ +public: + VariantVcfWriter( + const LocusSpecification& regionSpec, const VariantSpecification& variantSpec, int readLength, + Reference& reference, std::ostream& out) + : regionSpec_(regionSpec) + , variantSpec_(variantSpec) + , readLength_(readLength) + , reference_(reference) + , out_(out) + { + } + + ~VariantVcfWriter() = default; + void visit(const RepeatFindings* repeatFindingsPtr) override; + void visit(const SmallVariantFindings* smallVariantFindingsPtr) override; + +private: + const LocusSpecification& regionSpec_; + const VariantSpecification& variantSpec_; + const int readLength_; + Reference& reference_; + std::ostream& out_; +}; + +// TODO: Document the code after multi-unit repeat format is finalized (GT-598) +class VcfWriter +{ +public: + VcfWriter( + const std::string& sampleName, int readLength, const RegionCatalog& regionCatalog, + const SampleFindings& sampleFindings, Reference& reference); + + friend std::ostream& operator<<(std::ostream& out, VcfWriter& vcfWriter); + +private: + void writeHeader(std::ostream& out); + void writeBody(std::ostream& out); + + const std::string sampleName_; + const int readLength_; + const RegionCatalog& regionCatalog_; + const SampleFindings& sampleFindings_; + Reference& reference_; +}; + +std::ostream& operator<<(std::ostream& out, VcfWriter& vcfWriter); + +} diff --git a/output/VcfWriterHelpers.cpp b/output/VcfWriterHelpers.cpp new file mode 100755 index 0000000..8491d52 --- /dev/null +++ b/output/VcfWriterHelpers.cpp @@ -0,0 +1,123 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "output/VcfWriterHelpers.hh" + +#include + +#include +#include + +using std::deque; +using std::map; +using std::pair; +using std::set; +using std::string; +using std::stringstream; +using std::to_string; +using std::vector; + +namespace ehunter +{ + +template static string encodeSampleFields(const deque& fieldRecords) +{ + vector fieldRecordEncodings; + + for (const auto& record : fieldRecords) + { + stringstream stringStream; + stringStream << record; + fieldRecordEncodings.emplace_back(stringStream.str()); + } + + return boost::algorithm::join(fieldRecordEncodings, "/"); +} + +string VcfSampleFields::encode() const +{ + vector encoding; + encoding.push_back(encodeSampleFields(genotype_)); + encoding.push_back(encodeSampleFields(sources_)); + encoding.push_back(encodeSampleFields(alleleSizes_)); + encoding.push_back(encodeSampleFields(confidenceIntervals_)); + encoding.push_back(encodeSampleFields(spanningReadCounts_)); + encoding.push_back(encodeSampleFields(flankingReadCounts_)); + encoding.push_back(encodeSampleFields(repeatReadCounts_)); + + return boost::algorithm::join(encoding, ":"); +} + +void VcfSampleFields::addAlleleInfo( + int alleleSize, ReadType source, NumericInterval confidenceInterval, int spanningReadCount, int flankingReadCount, + int repeatReadCount) +{ + if (alleleSize == referenceSize_) + { + addRefAlleleInfo(alleleSize, source, confidenceInterval, spanningReadCount, flankingReadCount, repeatReadCount); + } + else + { + addAltAlleleInfo(alleleSize, source, confidenceInterval, spanningReadCount, flankingReadCount, repeatReadCount); + } +} + +void VcfSampleFields::addRefAlleleInfo( + int alleleSize, ReadType source, NumericInterval confidenceInterval, int spanningReadCount, int flankingReadCount, + int repeatReadCount) +{ + genotype_.push_front(0); + alleleSizes_.push_front(alleleSize); + sources_.push_front(source); + confidenceIntervals_.push_front(confidenceInterval); + spanningReadCounts_.push_front(spanningReadCount); + flankingReadCounts_.push_front(flankingReadCount); + repeatReadCounts_.push_front(repeatReadCount); +} + +void VcfSampleFields::addAltAlleleInfo( + int alleleSize, ReadType source, NumericInterval confidenceInterval, int spanningReadCount, int flankingReadCount, + int repeatReadCount) +{ + const int previousAlleleSize = genotype_.empty() ? -1 : genotype_.back(); + if (alleleSize <= previousAlleleSize) + { + throw std::logic_error( + "Allele of size " + to_string(alleleSize) + " cannot follow allele of size " + + to_string(previousAlleleSize)); + } + + int haplotypeNum = 1; + if (!genotype_.empty()) + { + const int previousAlleleSize = alleleSizes_.back(); + haplotypeNum = previousAlleleSize == alleleSize ? genotype_.back() : genotype_.back() + 1; + } + + genotype_.push_back(haplotypeNum); + alleleSizes_.push_back(alleleSize); + sources_.push_back(source); + confidenceIntervals_.push_back(confidenceInterval); + spanningReadCounts_.push_back(spanningReadCount); + flankingReadCounts_.push_back(flankingReadCount); + repeatReadCounts_.push_back(repeatReadCount); +} + +} diff --git a/output/VcfWriterHelpers.hh b/output/VcfWriterHelpers.hh new file mode 100755 index 0000000..9ffe241 --- /dev/null +++ b/output/VcfWriterHelpers.hh @@ -0,0 +1,68 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include +#include + +#include "common/Common.hh" +#include "region_analysis/VariantFindings.hh" +#include "region_spec/LocusSpecification.hh" + +namespace ehunter +{ + +class VcfSampleFields +{ +public: + VcfSampleFields(int referenceSize) + : referenceSize_(referenceSize) + { + } + + void addAlleleInfo( + int alleleSize, ReadType source, NumericInterval confidenceInterval, int spanningReadCount, + int flankingReadCount, int repeatReadCount); + + std::string encode() const; + +private: + void addRefAlleleInfo( + int alleleSize, ReadType source, NumericInterval confidenceInterval, int spanningReadCount, + int flankingReadCount, int repeatReadCount); + + void addAltAlleleInfo( + int alleleSize, ReadType source, NumericInterval confidenceInterval, int spanningReadCount, + int flankingReadCount, int repeatReadCount); + + int referenceSize_; + std::deque genotype_; + std::deque sources_; + std::deque alleleSizes_; + std::deque confidenceIntervals_; + std::deque spanningReadCounts_; + std::deque flankingReadCounts_; + std::deque repeatReadCounts_; +}; + +} diff --git a/purity/CMakeLists.txt b/purity/CMakeLists.txt deleted file mode 100644 index 620565c..0000000 --- a/purity/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -add_library(purity purity.cc) - -add_subdirectory(unit_tests) \ No newline at end of file diff --git a/purity/purity.cc b/purity/purity.cc deleted file mode 100644 index 118d23c..0000000 --- a/purity/purity.cc +++ /dev/null @@ -1,177 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include "purity/purity.h" - -#include -#include -#include -#include -#include - -#include "common/parameters.h" - -using std::cerr; -using std::endl; -using std::string; -using std::vector; - -namespace ehunter { - -static const string Rc(const string& seqStr) { - std::string revComplStr(seqStr); - typedef string::reverse_iterator ChRIter; - ChRIter revComplRIter(revComplStr.rbegin()); - char complCh(' '); - - for (const char baseCh : seqStr) { - switch (baseCh) { - case 'A': - complCh = 'T'; - break; - case 'C': - complCh = 'G'; - break; - case 'G': - complCh = 'C'; - break; - case 'T': - complCh = 'A'; - break; - default: - complCh = 'N'; - } - *revComplRIter++ = complCh; - } - - return revComplStr; -} - -double MatchRepeatRc(const vector>& units_shifts, - const string& bases, const string& quals, - size_t min_baseq) { - size_t best_offset_forward = 0; - double forward_score = - MatchRepeat(units_shifts, bases, quals, best_offset_forward, min_baseq); - const string bases_rc = Rc(bases); - string quals_rc = quals; - std::reverse(quals_rc.begin(), quals_rc.end()); - size_t best_offset_reverse = 0; - double reverse_score = MatchRepeat(units_shifts, bases_rc, quals_rc, - best_offset_reverse, min_baseq); - - return std::max(forward_score, reverse_score); -} - -double MatchRepeat(const vector>& units_shifts, - const string& bases, const string& quals, - size_t& match_offset, size_t min_baseq) { - double max_score = std::numeric_limits::lowest(); - size_t offset = 0; - for (const vector& units_shift : units_shifts) { - const double score = MatchRepeat(units_shift, bases, quals, min_baseq); - if (score > max_score) { - max_score = score; - match_offset = offset; - } - ++offset; - } - return max_score; -} - -vector> shift_units(const vector& units) { - size_t unit_len = units[0].length(); - vector extended_units; - for (const string& unit : units) { - extended_units.push_back(unit + unit); - } - - vector> shifted_units; - for (size_t offset = 0; offset != unit_len; ++offset) { - vector current_shift_units; - for (const string& extended_unit : extended_units) { - current_shift_units.push_back(extended_unit.substr(offset, unit_len)); - } - shifted_units.push_back(current_shift_units); - } - - return shifted_units; -} - -double MatchRepeat(const vector& units, const string& bases, - const string& quals, size_t min_baseq) { - const size_t unit_len = units[0].length(); - double score = 0; - size_t pos = 0; - while (pos + unit_len <= bases.length()) { - score += - MatchUnits(units, bases.begin() + pos, bases.begin() + pos + unit_len, - quals.begin() + pos, min_baseq); - pos += unit_len; - } - - if (pos != bases.length()) { - score += MatchUnits(units, bases.begin() + pos, bases.end(), - quals.begin() + pos, min_baseq); - } - - return score; -} - -double MatchUnits(const vector& units, - string::const_iterator bases_first, - string::const_iterator bases_last, - string::const_iterator quals_first, size_t min_baseq) { - double max_match_count = std::numeric_limits::lowest(); - const size_t kBaseQualOffset = 33; - const double kMatchScore = 1.0; - const double kLowqualMismatchScore = 0.5; - const double kMismatchPenalty = -1.0; - - for (const string& unit : units) { - string::const_iterator unit_first = unit.begin(); - string::const_iterator bases_first_copy = bases_first; - string::const_iterator quals_first_copy = quals_first; - - double match_count = 0; - while (bases_first_copy != bases_last) { - if (*bases_first_copy == *unit_first) { - match_count += kMatchScore; - } else if (*quals_first_copy - kBaseQualOffset < min_baseq) { - match_count += kLowqualMismatchScore; - } else { - match_count += kMismatchPenalty; - } - - ++bases_first_copy; - ++quals_first_copy; - ++unit_first; - } - - if (match_count > max_match_count) { - max_match_count = match_count; - } - } - - return max_match_count; -} -} // namespace ehunter diff --git a/purity/purity.h b/purity/purity.h deleted file mode 100644 index 9e3ca51..0000000 --- a/purity/purity.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#pragma once - -#include -#include - -namespace ehunter { -std::vector> shift_units( - const std::vector& units); - -double MatchRepeatRc(const std::vector>& units_shifts, - const std::string& bases, const std::string& quals, - size_t min_baseq = 20); - -double MatchRepeat(const std::vector>& units_shifts, - const std::string& bases, const std::string& quals, - size_t& match_offset, size_t min_baseq = 20); - -double MatchRepeat(const std::vector& units, - const std::string& bases, const std::string& quals, - size_t min_baseq = 20); - -double MatchUnits(const std::vector& units, - std::string::const_iterator bases_start, - std::string::const_iterator bases_end, - std::string::const_iterator quals_start, - size_t min_baseq = 20); -} // namespace ehunter diff --git a/purity/unit_tests/CMakeLists.txt b/purity/unit_tests/CMakeLists.txt deleted file mode 100644 index 20bb988..0000000 --- a/purity/unit_tests/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -add_executable(purity_test purity_test.cc) -target_link_libraries(purity_test purity gtest_main) -add_test(NAME purity_test COMMAND purity_test) \ No newline at end of file diff --git a/purity/unit_tests/purity_test.cc b/purity/unit_tests/purity_test.cc deleted file mode 100644 index db4d078..0000000 --- a/purity/unit_tests/purity_test.cc +++ /dev/null @@ -1,147 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include "purity/purity.h" -#include "gtest/gtest.h" - -#include -using std::string; -#include -using std::cerr; -using std::endl; -#include -using std::vector; - -using namespace ehunter; - -TEST(TestUnitMatching, MatchesUnitToItself) { - char qual_chars[] = {40, 40, 40, 40, 40, 40}; - string quals = qual_chars; - string bases = "GGCCCC"; - vector units = {"GGCCCC"}; - - EXPECT_DOUBLE_EQ(MatchUnits(units, bases.begin(), bases.end(), quals.begin()), - 6.0); -} - -TEST(TestUnitMatching, MatchesMultipleUnits) { - string quals = "PPPPPP"; - string bases = "AACTCC"; - vector units = {"GGCCCC", "AACTCC"}; - - EXPECT_DOUBLE_EQ(MatchUnits(units, bases.begin(), bases.end(), quals.begin()), - 6.0); -} - -TEST(TestUnitMatching, MatchesShortSequence) { - string quals = "PPP"; - string bases = "AAC"; - vector units = {"GGCCCC", "AACTCC"}; - - EXPECT_DOUBLE_EQ(MatchUnits(units, bases.begin(), bases.end(), quals.begin()), - 3.0); -} - -TEST(TestUnitMatching, MatchesLowqualBases) { - string quals = "(PP((("; - string bases = "AACCGG"; - vector units = {"GGCCCC", "AACTCC"}; - - EXPECT_DOUBLE_EQ(MatchUnits(units, bases.begin(), bases.end(), quals.begin()), - 4.5); -} - -TEST(TestUnitMatching, ScoreCanBeNegative) { - string quals = "PPPPPP"; - string bases = "AACCGG"; - vector units = {"ATTTTT", "AATTTT"}; - - EXPECT_DOUBLE_EQ(MatchUnits(units, bases.begin(), bases.end(), quals.begin()), - -2.0); -} - -TEST(TestRepeatMatching, RepeatMatches) { - string quals = "PPPPPPPP"; - string bases = "ACGATGAC"; - vector units = {"AAG", "ACG"}; - - EXPECT_DOUBLE_EQ(MatchRepeat(units, bases, quals), 6.0); -} - -TEST(TestRepeatMatching, MotifShorterByOne) { - string quals = "PPPPPPPP"; - string bases = "ACGATGAC"; - vector units = {"AAAATTT", "ACGATGA"}; - - EXPECT_DOUBLE_EQ(MatchRepeat(units, bases, quals), 6.0); -} - -TEST(TestRepeatMatching, EmptySequenceScoresZero) { - string quals; - string bases; - vector units = {"AAG", "ACG"}; - - EXPECT_DOUBLE_EQ(MatchRepeat(units, bases, quals), 0); -} - -TEST(TestRepeatMatching, SingletonScoresOne) { - string quals = "B"; - string bases = "G"; - vector units = {"G"}; - - EXPECT_DOUBLE_EQ(MatchRepeat(units, bases, quals), 1.0); -} - -TEST(TestRepeatMatching, MakeShiftedUnits) { - vector units = {"AAG", "ACG"}; - vector> shifted_units = { - {"AAG", "ACG"}, {"AGA", "CGA"}, {"GAA", "GAC"}}; - ASSERT_EQ(shift_units(units), shifted_units); -} - -TEST(TestRepeatMatching, RepeatMatchesWithShift) { - vector units = {"AAG", "ACG"}; - vector> units_shifts = shift_units(units); - string quals = "PPPPPPPP"; - string bases = "CGACGACG"; - - size_t best_offset = 0; - ASSERT_DOUBLE_EQ(MatchRepeat(units_shifts, bases, quals, best_offset), 8.0); -} - -TEST(TestRepeatMatching, CalculatesBestMatchOffset) { - vector units = {"AAG", "ACG"}; - vector> units_shifts = shift_units(units); - string quals = "PPPPPPPP"; - string bases = "CGACGACG"; - size_t offset = 0; - MatchRepeat(units_shifts, bases, quals, offset); - ASSERT_DOUBLE_EQ(offset, 1); -} - -TEST(TestRepeatMatching, RepeatMatchesReverseCompliment) { - vector units = {"AAG", "ACG"}; - vector> units_shifts = shift_units(units); - string quals = "((PPPPPP"; - string bases = "AATCGTCG"; - ASSERT_DOUBLE_EQ(MatchRepeatRc(units_shifts, bases, quals), 7.0); -} diff --git a/reads/CMakeLists.txt b/reads/CMakeLists.txt new file mode 100755 index 0000000..b0d4417 --- /dev/null +++ b/reads/CMakeLists.txt @@ -0,0 +1,4 @@ +file(GLOB SOURCES "*.cpp") +add_library(reads ${SOURCES}) +target_link_libraries(reads graphtools common) +add_subdirectory(tests) \ No newline at end of file diff --git a/reads/Read.cpp b/reads/Read.cpp new file mode 100755 index 0000000..751e176 --- /dev/null +++ b/reads/Read.cpp @@ -0,0 +1,67 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Mitch Bekritsky , Richard Shaw +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "reads/Read.hh" + +#include + +using std::string; + +namespace ehunter +{ + + +namespace reads +{ + +bool operator==(const Read& read_a, const Read& read_b) +{ + const bool is_id_equal = read_a.read_id == read_b.read_id; + const bool is_sequence_equal = read_a.sequence == read_b.sequence; + const bool is_mate_order_equal = read_a.is_first_mate == read_b.is_first_mate; + return (is_id_equal && is_sequence_equal && is_mate_order_equal); +} + +bool operator==(const LinearAlignmentStats& alignment_stats_a, const LinearAlignmentStats& alignment_stats_b) +{ + const bool is_chrom_id_equal = alignment_stats_a.chrom_id == alignment_stats_b.chrom_id; + const bool is_pos_equal = alignment_stats_a.pos == alignment_stats_b.pos; + const bool is_mapq_equal = alignment_stats_a.mapq == alignment_stats_b.mapq; + const bool is_mate_chrom_id_equal = alignment_stats_a.mate_chrom_id == alignment_stats_b.mate_chrom_id; + const bool is_mate_pos_equal = alignment_stats_a.mate_pos == alignment_stats_b.mate_pos; + const bool is_mapping_status_equal = alignment_stats_a.is_mapped == alignment_stats_b.is_mapped; + const bool is_mate_mapping_status_equal = alignment_stats_a.is_mate_mapped == alignment_stats_b.is_mate_mapped; + + return ( + is_chrom_id_equal && is_pos_equal && is_mapq_equal && is_mate_chrom_id_equal && is_mate_pos_equal + && is_mapping_status_equal && is_mate_mapping_status_equal); +} + +std::ostream& operator<<(std::ostream& os, const Read& read) +{ + os << read.read_id << " " << read.sequence; + return os; +} + +} // namespace reads + +} diff --git a/reads/Read.hh b/reads/Read.hh new file mode 100755 index 0000000..075174e --- /dev/null +++ b/reads/Read.hh @@ -0,0 +1,111 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include +#include + +#include "classification/AlignmentClassifier.hh" +#include "graphalign/GraphAlignment.hh" + +namespace ehunter +{ + +namespace reads +{ + + struct Read + { + Read() + : is_first_mate(false) + { + } + + Read(const std::string& new_read_id, const std::string& new_sequence) + : read_id(new_read_id) + , sequence(new_sequence) + , is_first_mate(false) + { + } + std::string read_id; + std::string sequence; + bool is_first_mate = false; + + std::string fragmentId() const + { + std::string fragment_id = read_id; + fragment_id.pop_back(); + fragment_id.pop_back(); + return fragment_id; + } + + const std::string& readId() const { return read_id; } + bool isSecondMate() const { return !is_first_mate; } + bool isSet() const { return !read_id.empty() && !sequence.empty(); } + }; + + struct LinearAlignmentStats + { + int32_t chrom_id = -1; + int32_t pos = -1; + int32_t mapq = -1; + int32_t mate_chrom_id = -1; + int32_t mate_pos = -1; + bool is_mapped = false; + bool is_mate_mapped = false; + }; + + using ReadIdToLinearAlignmentStats = std::unordered_map; + + bool operator==(const Read& read_a, const Read& read_b); + bool operator==(const LinearAlignmentStats& stats_a, const LinearAlignmentStats& core_info_b); + + class RepeatAlignmentStats + { + public: + RepeatAlignmentStats( + const GraphAlignment& canonical_alignment, AlignmentType canonical_alignment_type, + int32_t num_repeat_units_spanned) + : canonical_alignment_(canonical_alignment) + , canonical_alignment_type_(canonical_alignment_type) + , num_repeat_units_spanned_(num_repeat_units_spanned) + { + } + + const GraphAlignment& canonicalAlignment() const { return canonical_alignment_; } + AlignmentType canonicalAlignmentType() const { return canonical_alignment_type_; } + int32_t numRepeatUnitsSpanned() const { return num_repeat_units_spanned_; } + + private: + GraphAlignment canonical_alignment_; + AlignmentType canonical_alignment_type_; + int32_t num_repeat_units_spanned_; + }; + + using ReadIdToRepeatAlignmentStats = std::unordered_map; + + std::ostream& operator<<(std::ostream& os, const Read& read); + +} // namespace reads + +} diff --git a/reads/ReadPairs.cpp b/reads/ReadPairs.cpp new file mode 100755 index 0000000..2217ada --- /dev/null +++ b/reads/ReadPairs.cpp @@ -0,0 +1,136 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "reads/ReadPairs.hh" + +#include + +using std::string; +using std::vector; + +namespace ehunter +{ + +namespace reads +{ + + void ReadPairs::Add(const Read& read) + { + ReadPair& read_pair = read_pairs_[read.fragmentId()]; + const int32_t num_mates_original + = static_cast(read_pair.first_mate.isSet()) + static_cast(read_pair.second_mate.isSet()); + + if (read.is_first_mate && !read_pair.first_mate.isSet()) + { + read_pair.first_mate = read; + } + + if (read.isSecondMate() && !read_pair.second_mate.isSet()) + { + read_pair.second_mate = read; + } + + const int32_t num_mates_after_add + = static_cast(read_pair.first_mate.isSet()) + static_cast(read_pair.second_mate.isSet()); + + num_reads_ += num_mates_after_add - num_mates_original; + } + + void ReadPairs::AddMateToExistingRead(const Read& mate) + { + ReadPair& read_pair = read_pairs_.at(mate.fragmentId()); + if (mate.is_first_mate && !read_pair.first_mate.isSet()) + { + read_pair.first_mate = mate; + } + else if (mate.isSecondMate() && !read_pair.second_mate.isSet()) + { + read_pair.second_mate = mate; + } + else + { + throw std::logic_error("Unable to find read placement"); + } + } + + const ReadPair& ReadPairs::operator[](const string& fragment_id) const + { + if (read_pairs_.find(fragment_id) == read_pairs_.end()) + { + throw std::logic_error("Fragment " + fragment_id + " does not exist"); + } + return read_pairs_.at(fragment_id); + } + + ReadIdToReadReference ReadPairs::GetReads() + { + ReadIdToReadReference read_references; + + for (auto& kv : read_pairs_) + { + ReadPair& read_pair = kv.second; + if (read_pair.first_mate.isSet()) + { + const string& read_id = read_pair.first_mate.read_id; + std::reference_wrapper read_ref = read_pair.first_mate; + read_references.emplace(std::make_pair(read_id, read_ref)); + } + if (read_pair.second_mate.isSet()) + { + const string& read_id = read_pair.second_mate.read_id; + std::reference_wrapper read_ref = read_pair.second_mate; + read_references.emplace(std::make_pair(read_id, read_ref)); + } + } + + return read_references; + } + + int32_t ReadPairs::NumCompletePairs() const + { + int32_t numCompletePairs = 0; + for (const auto& fragmentIdAndReads : read_pairs_) + { + const ReadPair& reads = fragmentIdAndReads.second; + if (reads.first_mate.isSet() && reads.second_mate.isSet()) + { + ++numCompletePairs; + } + } + + return numCompletePairs; + } + + void ReadPairs::Clear() + { + read_pairs_.clear(); + num_reads_ = 0; + } + + bool operator==(const ReadPair& read_pair_a, const ReadPair& read_pair_b) + { + const bool are_first_mates_equal = read_pair_a.first_mate == read_pair_b.first_mate; + const bool are_second_mates_equal = read_pair_a.second_mate == read_pair_b.second_mate; + return (are_first_mates_equal && are_second_mates_equal); + } + +} // namespace reads + +} diff --git a/reads/ReadPairs.hh b/reads/ReadPairs.hh new file mode 100755 index 0000000..f5f5208 --- /dev/null +++ b/reads/ReadPairs.hh @@ -0,0 +1,85 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "reads/Read.hh" + +namespace ehunter +{ + +namespace reads +{ + + using ReadIdToReadReference = std::unordered_map>; + + struct ReadPair + { + Read first_mate; + Read second_mate; + }; + + bool operator==(const ReadPair& read_pair_a, const ReadPair& read_pair_b); + + /** + * Read pair container class + */ + class ReadPairs + { + public: + typedef std::unordered_map::const_iterator const_iterator; + typedef std::unordered_map::iterator iterator; + const_iterator begin() const { return read_pairs_.begin(); } + const_iterator end() const { return read_pairs_.end(); } + iterator begin() { return read_pairs_.begin(); } + iterator end() { return read_pairs_.end(); } + + ReadPairs() = default; + void Clear(); + void Add(const Read& read); + void AddMateToExistingRead(const Read& mate); + + const ReadPair& operator[](const std::string& fragment_id) const; + + int32_t NumReads() const { return num_reads_; } + int32_t NumCompletePairs() const; + + ReadIdToReadReference GetReads(); + + bool operator==(const ReadPairs& other) const + { + return (read_pairs_ == other.read_pairs_ && num_reads_ == other.num_reads_); + } + + private: + std::unordered_map read_pairs_; + int32_t num_reads_ = 0; + }; + +} // namespace reads + +} diff --git a/reads/tests/CMakeLists.txt b/reads/tests/CMakeLists.txt new file mode 100755 index 0000000..2e53de6 --- /dev/null +++ b/reads/tests/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(ReadTest ReadTest.cpp) +target_link_libraries(ReadTest reads gtest_main) +add_test(NAME ReadTest COMMAND ReadTest) diff --git a/reads/tests/ReadTest.cpp b/reads/tests/ReadTest.cpp new file mode 100755 index 0000000..5a192dd --- /dev/null +++ b/reads/tests/ReadTest.cpp @@ -0,0 +1,32 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "reads/Read.hh" + +#include "gtest/gtest.h" + +using namespace ehunter; +using namespace reads; + +TEST(ReadInitialization, TypicalCoreInfo_CoreInfoAddedToRead) +{ + Read read("frag1/2", "ATTC"); + EXPECT_EQ("frag1", read.fragmentId()); +} diff --git a/region_analysis/CMakeLists.txt b/region_analysis/CMakeLists.txt new file mode 100755 index 0000000..90aa749 --- /dev/null +++ b/region_analysis/CMakeLists.txt @@ -0,0 +1,4 @@ +file(GLOB SOURCES "*.cpp") +add_library(region_analysis ${SOURCES}) +target_link_libraries(region_analysis common input reads classification region_spec genotyping alignment filtering ${htslib_static} ${zlib_static}) +add_subdirectory(tests) \ No newline at end of file diff --git a/region_analysis/Definitions.hh b/region_analysis/Definitions.hh new file mode 100755 index 0000000..d5ad93e --- /dev/null +++ b/region_analysis/Definitions.hh @@ -0,0 +1,33 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include +#include +#include + +#include "graphalign/GraphAlignment.hh" + +namespace ehunter +{ + +using ReadIdToGraphAlignment = std::unordered_map; +using ReadIdToGraphAlignments = std::unordered_map>; + +} diff --git a/region_analysis/RegionAnalyzer.cpp b/region_analysis/RegionAnalyzer.cpp new file mode 100755 index 0000000..9dd005e --- /dev/null +++ b/region_analysis/RegionAnalyzer.cpp @@ -0,0 +1,288 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "region_analysis/RegionAnalyzer.hh" + +#include +#include + +#include "thirdparty/spdlog/spdlog.h" + +#include "graphalign/GraphAlignmentOperations.hh" +#include "graphutils/SequenceOperations.hh" + +#include "alignment/AlignmentFilters.hh" +#include "alignment/AlignmentTweakers.hh" +#include "alignment/GraphAlignmentOperations.hh" +#include "region_analysis/RepeatAnalyzer.hh" +#include "region_analysis/SmallVariantAnalyzer.hh" + +namespace ehunter +{ + +using boost::optional; +using graphtools::Operation; +using graphtools::OperationType; +using graphtools::splitStringByDelimiter; +using reads::LinearAlignmentStats; +using reads::Read; +using std::list; +using std::string; +using std::vector; + +static const string encodeReadPair(const Read& read, const Read& mate) +{ + return read.read_id + ": " + read.sequence + "\n" + mate.read_id + ": " + read.sequence; +} + +static string indentMultilineString(const string str, int32_t indentation_len) +{ + string indented_str; + const vector lines = splitStringByDelimiter(str, '\n'); + for (auto& line : lines) + { + if (!indented_str.empty()) + { + indented_str += '\n'; + } + indented_str += string(indentation_len, ' ') + line; + } + + return indented_str; +} + +static void outputAlignedRead(const Read& read, GraphAlignment alignment, std::ostream& out) +{ + const int32_t indentationSize = 2; + const string spacer(indentationSize, ' '); + out << spacer << "- name: " << read.read_id << std::endl; + out << spacer << " path: " << alignment.path() << std::endl; + out << spacer << " graph_cigar: " << alignment.generateCigar() << std::endl; + out << spacer << " alignment: |" << std::endl; + const string alignmentEncoding = prettyPrint(alignment, read.sequence); + out << indentMultilineString(alignmentEncoding, 3 * indentationSize) << std::endl; +} + +RegionAnalyzer::RegionAnalyzer( + const LocusSpecification& regionSpec, SampleParameters sampleParams, HeuristicParameters heuristicParams, + std::ostream& alignmentStream) + : regionSpec_(regionSpec) + , sampleParams_(sampleParams) + , heuristicParams_(heuristicParams) + , alignmentStream_(alignmentStream) + , orientationPredictor_(sampleParams.readLength(), ®ionSpec_.regionGraph()) + , graphAligner_( + ®ionSpec_.regionGraph(), heuristicParams.alignerType(), heuristicParams_.kmerLenForAlignment(), + heuristicParams_.paddingLength(), heuristicParams_.seedAffixTrimLength()) +{ + verboseLogger_ = spdlog::get("verbose"); + + for (const auto& variantSpec : regionSpec_.variantSpecs()) + { + if (variantSpec.classification().type == VariantType::kRepeat) + { + const auto& graph = regionSpec_.regionGraph(); + const int repeatNodeId = variantSpec.nodes().front(); + const string& repeatUnit = graph.nodeSeq(repeatNodeId); + const int repeatUnitLength = repeatUnit.length(); + const int maxNumUnitsInRead = std::ceil(sampleParams_.readLength() / static_cast(repeatUnitLength)); + + weightedPurityCalculators.emplace(std::make_pair(repeatUnit, WeightedPurityCalculator(repeatUnit))); + + if (variantSpec.classification().subtype == VariantSubtype::kRareRepeat) + { + if (optionalUnitOfRareRepeat_) + { + const string errorMessage + = "Region " + regionSpec_.regionId() + " is not permitted to have more than one rare variant"; + throw std::logic_error(errorMessage); + } + optionalUnitOfRareRepeat_ = repeatUnit; + } + + variantAnalyzerPtrs_.emplace_back(new RepeatAnalyzer( + variantSpec.id(), regionSpec.expectedAlleleCount(), regionSpec.regionGraph(), repeatNodeId, + sampleParams_.haplotypeDepth(), maxNumUnitsInRead)); + } + else if (variantSpec.classification().type == VariantType::kSmallVariant) + { + variantAnalyzerPtrs_.emplace_back(new SmallVariantAnalyzer( + variantSpec.id(), variantSpec.classification().subtype, regionSpec.expectedAlleleCount(), + regionSpec.regionGraph(), variantSpec.nodes(), variantSpec.optionalRefNode(), + sampleParams_.haplotypeDepth())); + } + else + { + std::stringstream encoding; + encoding << variantSpec.classification().type << "/" << variantSpec.classification().subtype; + throw std::logic_error("Missing logic to create an analyzer for " + encoding.str()); + } + } +} + +void RegionAnalyzer::processMates(reads::Read read, reads::Read mate) +{ + optional readAlignment = alignRead(read); + optional mateAlignment = alignRead(mate); + + int kMinNonRepeatAlignmentScore = sampleParams_.readLength() / 7.5; + kMinNonRepeatAlignmentScore = std::max(kMinNonRepeatAlignmentScore, 3); + if (!checkIfLocallyPlacedReadPair(readAlignment, mateAlignment, kMinNonRepeatAlignmentScore)) + { + if (verboseLogger_) + { + verboseLogger_->info("Not locally placed read pair"); + verboseLogger_->info(encodeReadPair(read, mate)); + } + return; + } + + if (readAlignment && mateAlignment) + { + outputAlignedRead(read, *readAlignment, alignmentStream_); + outputAlignedRead(mate, *mateAlignment, alignmentStream_); + + for (auto& variantAnalyzerPtr : variantAnalyzerPtrs_) + { + variantAnalyzerPtr->processMates(read, *readAlignment, mate, *mateAlignment); + } + } + else if (verboseLogger_) + { + const string readStatus = readAlignment ? "Able" : "Unable"; + const string mateStatus = mateAlignment ? "Able" : "Unable"; + verboseLogger_->info("{} to align {}: {}", readStatus, read.readId(), read.sequence); + verboseLogger_->info("{} to align {}: {}", mateStatus, mate.readId(), mate.sequence); + } +} + +void RegionAnalyzer::processOfftargetMates(reads::Read read1, reads::Read read2) +{ + if (!optionalUnitOfRareRepeat_) + { + const string errorMessage + = "Cannot process offtarget mates for " + regionSpec_.regionId() + " because repeat unit is not set"; + throw std::logic_error(errorMessage); + } + + const string& repeatUnit = *optionalUnitOfRareRepeat_; + + const auto& weightedPurityCalculator = weightedPurityCalculators.at(repeatUnit); + const bool isFirstReadInrepeat = weightedPurityCalculator.score(read1.sequence) >= 0.90; + const bool isSecondReadInrepeat = weightedPurityCalculator.score(read2.sequence) >= 0.90; + + if (isFirstReadInrepeat && isSecondReadInrepeat) + { + std::cerr << "Found IRR pair " << read1.fragmentId() << std::endl; + processMates(std::move(read1), std::move(read2)); + } +} + +boost::optional RegionAnalyzer::alignRead(Read& read) const +{ + OrientationPrediction predictedOrientation = orientationPredictor_.predict(read.sequence); + + if (predictedOrientation == OrientationPrediction::kAlignsInReverseComplementOrientation) + { + read.sequence = graphtools::reverseComplement(read.sequence); + } + else if (predictedOrientation == OrientationPrediction::kDoesNotAlign) + { + return boost::optional(); + } + + const list alignments = graphAligner_.align(read.sequence); + + if (alignments.empty()) + { + return boost::optional(); + } + + GraphAlignment canonicalAlignment = computeCanonicalAlignment(alignments); + + if (checkIfPassesAlignmentFilters(canonicalAlignment)) + { + // const int kShrinkLength = 10; + // shrinkUncertainPrefix(kShrinkLength, read.sequence, canonicalAlignment); + // shrinkUncertainSuffix(kShrinkLength, read.sequence, canonicalAlignment); + + return canonicalAlignment; + } + else + { + return boost::optional(); + } +} + +bool RegionAnalyzer::checkIfPassesAlignmentFilters(const GraphAlignment& alignment) const +{ + const Operation& firstOperation = alignment.alignments().front().operations().front(); + const int frontSoftclipLen = firstOperation.type() == OperationType::kSoftclip ? firstOperation.queryLength() : 0; + + const Operation& lastOperation = alignment.alignments().back().operations().back(); + const int backSoftclipLen = lastOperation.type() == OperationType::kSoftclip ? lastOperation.queryLength() : 0; + + const int clippedQueryLength = alignment.queryLength() - frontSoftclipLen - backSoftclipLen; + const int referenceLength = alignment.referenceLength(); + + const int percentQueryMatches = (100 * alignment.numMatches()) / clippedQueryLength; + const int percentReferenceMatches = (100 * alignment.numMatches()) / referenceLength; + + if (percentQueryMatches >= 80 && percentReferenceMatches >= 80) + { + return true; + } + else + { + return false; + } +} + +RegionFindings RegionAnalyzer::genotype() +{ + RegionFindings regionResults; + + for (auto& variantAnalyzerPtr : variantAnalyzerPtrs_) + { + std::unique_ptr variantFindingsPtr = variantAnalyzerPtr->analyze(); + regionResults.emplace(std::make_pair(variantAnalyzerPtr->variantId(), std::move(variantFindingsPtr))); + } + + return regionResults; +} + +vector> initializeRegionAnalyzers( + const RegionCatalog& RegionCatalog, const SampleParameters& sampleParams, + const HeuristicParameters& heuristicParams, std::ostream& alignmentStream) +{ + vector> regionAnalyzers; + + for (const auto& regionIdAndRegionSpec : RegionCatalog) + { + const LocusSpecification& regionSpec = regionIdAndRegionSpec.second; + + // alignmentStream << regionSpec.regionId() << ":" << std::endl; + regionAnalyzers.emplace_back(new RegionAnalyzer(regionSpec, sampleParams, heuristicParams, alignmentStream)); + } + + return regionAnalyzers; +} + +} diff --git a/region_analysis/RegionAnalyzer.hh b/region_analysis/RegionAnalyzer.hh new file mode 100755 index 0000000..368d1ee --- /dev/null +++ b/region_analysis/RegionAnalyzer.hh @@ -0,0 +1,96 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include +#include +#include +#include + +#include + +#include "thirdparty/spdlog/fmt/ostr.h" +#include "thirdparty/spdlog/spdlog.h" + +#include "graphalign/GappedAligner.hh" +#include "graphalign/KmerIndex.hh" + +#include "alignment/SoftclippingAligner.hh" +#include "common/Parameters.hh" +#include "filtering/OrientationPredictor.hh" +#include "reads/Read.hh" +#include "region_analysis/VariantAnalyzer.hh" +#include "region_analysis/VariantFindings.hh" +#include "region_spec/LocusSpecification.hh" +#include "stats/WeightedPurityCalculator.hh" + +#pragma once + +namespace ehunter +{ + +class RegionAnalyzer +{ +public: + RegionAnalyzer( + const LocusSpecification& regionSpec, SampleParameters sampleParams, HeuristicParameters heuristicParams, + std::ostream& alignmentStream); + + RegionAnalyzer(const RegionAnalyzer&) = delete; + RegionAnalyzer& operator=(const RegionAnalyzer&) = delete; + RegionAnalyzer(RegionAnalyzer&&) = default; + RegionAnalyzer& operator=(RegionAnalyzer&&) = default; + + const std::string& regionId() const { return regionSpec_.regionId(); } + const LocusSpecification& regionSpec() const { return regionSpec_; } + + void processMates(reads::Read read, reads::Read mate); + void processOfftargetMates(reads::Read read1, reads::Read read2); + bool checkIfPassesSequenceFilters(const std::string& sequence) const; + bool checkIfPassesAlignmentFilters(const graphtools::GraphAlignment& alignment) const; + bool checkIfPassesAlignmentFilters() const; // Public for unit testing + + RegionFindings genotype(); + + bool operator==(const RegionAnalyzer& other) const; + +private: + boost::optional alignRead(reads::Read& read) const; + + LocusSpecification regionSpec_; + SampleParameters sampleParams_; + HeuristicParameters heuristicParams_; + + std::ostream& alignmentStream_; + OrientationPredictor orientationPredictor_; + SoftclippingAligner graphAligner_; + + std::unordered_map weightedPurityCalculators; + + std::vector> variantAnalyzerPtrs_; + boost::optional optionalUnitOfRareRepeat_; + + std::shared_ptr verboseLogger_; +}; + +std::vector> initializeRegionAnalyzers( + const RegionCatalog& RegionCatalog, const SampleParameters& sampleParams, + const HeuristicParameters& heuristicParams, std::ostream& alignmentStream); + +} diff --git a/region_analysis/RepeatAnalyzer.cpp b/region_analysis/RepeatAnalyzer.cpp new file mode 100755 index 0000000..d552cce --- /dev/null +++ b/region_analysis/RepeatAnalyzer.cpp @@ -0,0 +1,189 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "region_analysis/RepeatAnalyzer.hh" + +#include "thirdparty/spdlog/spdlog.h" + +#include "graphalign/GraphAlignmentOperations.hh" +#include "graphutils/SequenceOperations.hh" + +#include "alignment/AlignmentFilters.hh" +#include "alignment/GraphAlignmentOperations.hh" +#include "genotyping/RepeatGenotyper.hh" + +namespace ehunter +{ + +using boost::optional; +using graphtools::GraphAlignment; +using graphtools::NodeId; +using graphtools::prettyPrint; +using graphtools::splitStringByDelimiter; +using reads::Read; +using reads::RepeatAlignmentStats; +using std::list; +using std::string; +using std::vector; + +static bool checkIfAlignmentIsConfident( + NodeId repeatNodeId, const GraphAlignment& alignment, const RepeatAlignmentStats& alignmentStats) +{ + const bool doesReadAlignWellOverLeftFlank = checkIfUpstreamAlignmentIsGood(repeatNodeId, alignment); + const bool doesReadAlignWellOverRightFlank = checkIfDownstreamAlignmentIsGood(repeatNodeId, alignment); + + if (alignmentStats.canonicalAlignmentType() == AlignmentType::kFlanksRepeat) + { + if (!doesReadAlignWellOverLeftFlank && !doesReadAlignWellOverRightFlank) + { + return false; + } + } + + if (alignmentStats.canonicalAlignmentType() == AlignmentType::kSpansRepeat) + { + if (!doesReadAlignWellOverRightFlank || !doesReadAlignWellOverRightFlank) + { + return false; + } + } + + return true; +} + +void RepeatAnalyzer::processMates( + const Read& read, const GraphAlignment& readAlignment, const Read& mate, const GraphAlignment& mateAlignment) +{ + RepeatAlignmentStats readAlignmentStats = classifyReadAlignment(readAlignment); + RepeatAlignmentStats mateAlignmentStats = classifyReadAlignment(mateAlignment); + + const bool isReadAlignmentConfident + = checkIfAlignmentIsConfident(repeatNodeId(), readAlignment, readAlignmentStats); + const bool isMateAlignmentConfident + = checkIfAlignmentIsConfident(repeatNodeId(), mateAlignment, mateAlignmentStats); + + if (isReadAlignmentConfident) + { + std::cerr << read.readId() << " is " << readAlignmentStats.canonicalAlignmentType() << " for variant " + << variantId_ << std::endl; + summarizeAlignmentsToReadCounts(readAlignmentStats); + } + else if (verboseLogger_) + { + std::cerr << read.readId() << " could not be confidently aligned " << std::endl; + std::cerr << prettyPrint(readAlignment, read.sequence) << std::endl; + verboseLogger_->info( + "Not a confident alignment for repeat node {}\n{}", repeatNodeId(), + prettyPrint(readAlignment, read.sequence)); + } + + if (isMateAlignmentConfident) + { + std::cerr << mate.readId() << " is " << mateAlignmentStats.canonicalAlignmentType() << " for variant " + << std::endl; + summarizeAlignmentsToReadCounts(mateAlignmentStats); + } + else if (verboseLogger_) + { + std::cerr << mate.readId() << " could not be confidently aligned " << std::endl; + std::cerr << prettyPrint(mateAlignment, mate.sequence) << std::endl; + verboseLogger_->info( + "Not a confident alignment for repeat node {}\n{}", repeatNodeId(), + prettyPrint(mateAlignment, mate.sequence)); + } +} + +RepeatAlignmentStats RepeatAnalyzer::classifyReadAlignment(const graphtools::GraphAlignment& alignment) +{ + AlignmentType alignmentType = alignmentClassifier_.Classify(alignment); + int32_t numRepeatUnitsOverlapped = countFullOverlaps(repeatNodeId(), alignment); + numRepeatUnitsOverlapped = std::min(numRepeatUnitsOverlapped, maxNumUnitsInRead_); + + return RepeatAlignmentStats(alignment, alignmentType, numRepeatUnitsOverlapped); +} + +void RepeatAnalyzer::summarizeAlignmentsToReadCounts(const RepeatAlignmentStats& repeatAlignmentStats) +{ + switch (repeatAlignmentStats.canonicalAlignmentType()) + { + case AlignmentType::kSpansRepeat: + countsOfSpanningReads_.incrementCountOf(repeatAlignmentStats.numRepeatUnitsSpanned()); + break; + case AlignmentType::kFlanksRepeat: + countsOfFlankingReads_.incrementCountOf(repeatAlignmentStats.numRepeatUnitsSpanned()); + break; + case AlignmentType::kInsideRepeat: + countsOfInrepeatReads_.incrementCountOf(repeatAlignmentStats.numRepeatUnitsSpanned()); + break; + default: + break; + } +} + +std::unique_ptr RepeatAnalyzer::analyze() const +{ + const vector candidateAlleleSizes = generateCandidateAlleleSizes(); + optional repeatGenotype = genotypeCommonRepeat(candidateAlleleSizes); + + std::unique_ptr variantFiningsPtr( + new RepeatFindings(countsOfSpanningReads_, countsOfFlankingReads_, countsOfInrepeatReads_, repeatGenotype)); + return variantFiningsPtr; +} + +vector RepeatAnalyzer::generateCandidateAlleleSizes() const +{ + vector candidateAlleleSizes = countsOfSpanningReads_.getElementsWithNonzeroCounts(); + const int32_t longestSpanning = candidateAlleleSizes.empty() + ? 0 + : *std::max_element(candidateAlleleSizes.begin(), candidateAlleleSizes.end()); + + const vector repeatSizesInFlankingReads = countsOfFlankingReads_.getElementsWithNonzeroCounts(); + const int32_t longestFlanking = repeatSizesInFlankingReads.empty() + ? 0 + : *std::max_element(repeatSizesInFlankingReads.begin(), repeatSizesInFlankingReads.end()); + + const vector repeatSizesInInrepeatReads = countsOfInrepeatReads_.getElementsWithNonzeroCounts(); + const int32_t longestInrepeat = repeatSizesInInrepeatReads.empty() + ? 0 + : *std::max_element(repeatSizesInInrepeatReads.begin(), repeatSizesInInrepeatReads.end()); + + const int32_t longestNonSpanning = std::max(longestFlanking, longestInrepeat); + + if (longestSpanning < longestNonSpanning) + { + candidateAlleleSizes.push_back(longestNonSpanning); + } + + return candidateAlleleSizes; +} + +optional RepeatAnalyzer::genotypeCommonRepeat(const vector& candidateAlleleSizes) const +{ + const int32_t repeatUnitLen = repeatUnit_.length(); + const double propCorrectMolecules = 0.97; + + RepeatGenotyper repeatGenotyper( + haplotypeDepth_, expectedAlleleCount_, repeatUnitLen, maxNumUnitsInRead_, propCorrectMolecules, + countsOfSpanningReads_, countsOfFlankingReads_, countsOfInrepeatReads_); + + return repeatGenotyper.genotypeRepeat(candidateAlleleSizes); +} + +} diff --git a/region_analysis/RepeatAnalyzer.hh b/region_analysis/RepeatAnalyzer.hh new file mode 100755 index 0000000..15ebbfd --- /dev/null +++ b/region_analysis/RepeatAnalyzer.hh @@ -0,0 +1,86 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include + +#include + +#include "thirdparty/spdlog/fmt/ostr.h" +#include "thirdparty/spdlog/spdlog.h" + +#include "graphalign/GraphAlignment.hh" +#include "graphcore/Graph.hh" + +#include "classification/AlignmentClassifier.hh" +#include "genotyping/RepeatGenotype.hh" +#include "reads/Read.hh" +#include "region_analysis/VariantAnalyzer.hh" + +namespace ehunter +{ + +class RepeatAnalyzer : public VariantAnalyzer +{ +public: + RepeatAnalyzer( + std::string variantId, AlleleCount expectedAlleleCount, const graphtools::Graph& graph, + graphtools::NodeId repeatNodeId, double haplotypeDepth, int32_t maxNumUnitsInRead) + : VariantAnalyzer(std::move(variantId), expectedAlleleCount, graph, { repeatNodeId }) + , maxNumUnitsInRead_(maxNumUnitsInRead) + , haplotypeDepth_(haplotypeDepth) + , repeatUnit_(graph.nodeSeq(repeatNodeId)) + , alignmentClassifier_(graph, repeatNodeId) + { + verboseLogger_ = spdlog::get("verbose"); + } + + ~RepeatAnalyzer() = default; + + void processMates( + const reads::Read& read, const graphtools::GraphAlignment& readAlignment, const reads::Read& mate, + const graphtools::GraphAlignment& mateAlignment) override; + + std::unique_ptr analyze() const override; + +private: + graphtools::NodeId repeatNodeId() const { return nodeIds_.front(); } + boost::optional genotypeCommonRepeat(const std::vector& candidateAlleleSizes) const; + reads::RepeatAlignmentStats classifyReadAlignment(const graphtools::GraphAlignment& alignment); + void summarizeAlignmentsToReadCounts(const reads::RepeatAlignmentStats& repeatAlignmentStats); + std::vector generateCandidateAlleleSizes() const; + + const int32_t maxNumUnitsInRead_; + const double haplotypeDepth_; + + const std::string repeatUnit_; + RepeatAlignmentClassifier alignmentClassifier_; + + CountTable countsOfSpanningReads_; + CountTable countsOfFlankingReads_; + CountTable countsOfInrepeatReads_; + + std::shared_ptr verboseLogger_; +}; + +} diff --git a/region_analysis/SmallVariantAnalyzer.cpp b/region_analysis/SmallVariantAnalyzer.cpp new file mode 100755 index 0000000..c416fd9 --- /dev/null +++ b/region_analysis/SmallVariantAnalyzer.cpp @@ -0,0 +1,100 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "region_analysis/SmallVariantAnalyzer.hh" + +#include + +#include + +using boost::optional; +using graphtools::NodeId; +using std::string; +using std::vector; + +namespace ehunter +{ + +void SmallVariantAnalyzer::processMates( + const reads::Read& /*read*/, const graphtools::GraphAlignment& readAlignment, const reads::Read& /*mate*/, + const graphtools::GraphAlignment& mateAlignment) +{ + alignmentClassifier_.classify(readAlignment); + alignmentClassifier_.classify(mateAlignment); +} + +int SmallVariantAnalyzer::countReadsSupportingNode(graphtools::NodeId nodeId) const +{ + if (nodeId == ClassifierOfAlignmentsToVariant::kInvalidNodeId) + { + return alignmentClassifier_.numBypassingReads(); + } + + const auto& spanningCounts = alignmentClassifier_.countsOfSpanningReads(); + const auto& upstreamFlankingCounts = alignmentClassifier_.countsOfReadsFlankingUpstream(); + const auto& downstreamFlankingCounts = alignmentClassifier_.countsOfReadsFlankingDownstream(); + + const int numReadsSupportingUpstreamFlank = upstreamFlankingCounts.countOf(nodeId) + spanningCounts.countOf(nodeId); + const int numReadsSupportingDownstreamFlank + = downstreamFlankingCounts.countOf(nodeId) + spanningCounts.countOf(nodeId); + + return (numReadsSupportingUpstreamFlank + numReadsSupportingDownstreamFlank) / 2; +} + +std::unique_ptr SmallVariantAnalyzer::analyze() const +{ + NodeId refNode = optionalRefNode_ ? *optionalRefNode_ : ClassifierOfAlignmentsToVariant::kInvalidNodeId; + NodeId altNode = ClassifierOfAlignmentsToVariant::kInvalidNodeId; + + switch (variantSubtype_) + { + case VariantSubtype::kInsertion: + altNode = nodeIds_.front(); + break; + case VariantSubtype::kDeletion: + altNode = ClassifierOfAlignmentsToVariant::kInvalidNodeId; + break; + case VariantSubtype::kSwap: + altNode = (refNode == nodeIds_.front()) ? nodeIds_.back() : nodeIds_.front(); + break; + case VariantSubtype::kSMN: + if (refNode != nodeIds_.front()) + throw std::logic_error("Invalid SMN specification"); + altNode = nodeIds_.back(); + break; + default: + std::ostringstream encoding; + encoding << variantSubtype_; + throw std::logic_error("Invalid small variant subtype: " + encoding.str()); + } + + const int refNodeSupport = countReadsSupportingNode(refNode); + const int altNodeSupport = countReadsSupportingNode(altNode); + + auto genotype = smallVariantGenotyper_.genotype(refNodeSupport, altNodeSupport); + + AllelePresenceStatus refAlleleStatus = allelePresenceChecker_.check(refNodeSupport, altNodeSupport); + AllelePresenceStatus altAlleleStatus = allelePresenceChecker_.check(altNodeSupport, refNodeSupport); + + return std::unique_ptr( + new SmallVariantFindings(refNodeSupport, altNodeSupport, refAlleleStatus, altAlleleStatus, genotype)); +} + +} diff --git a/region_analysis/SmallVariantAnalyzer.hh b/region_analysis/SmallVariantAnalyzer.hh new file mode 100755 index 0000000..0c373e1 --- /dev/null +++ b/region_analysis/SmallVariantAnalyzer.hh @@ -0,0 +1,80 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include + +#include "thirdparty/spdlog/spdlog.h" + +#include "classification/ClassifierOfAlignmentsToVariant.hh" +#include "genotyping/AllelePresenceChecker.hh" +#include "genotyping/SmallVariantGenotyper.hh" +#include "region_analysis/VariantAnalyzer.hh" +#include "region_spec/VariantSpecification.hh" + +namespace ehunter +{ + +class SmallVariantAnalyzer : public VariantAnalyzer +{ +public: + SmallVariantAnalyzer( + std::string variantId, VariantSubtype variantSubtype, AlleleCount expectedAlleleCount, + const graphtools::Graph& graph, std::vector nodeIds, + boost::optional optionalRefNode, double haplotypeDepth) + : VariantAnalyzer(std::move(variantId), expectedAlleleCount, graph, std::move(nodeIds)) + , variantSubtype_(variantSubtype) + , haplotypeDepth_(haplotypeDepth) + , optionalRefNode_(optionalRefNode) + , alignmentClassifier_(nodeIds_) + , smallVariantGenotyper_(haplotypeDepth_, expectedAlleleCount_) + , allelePresenceChecker_(haplotypeDepth_) + { + verboseLogger_ = spdlog::get("verbose"); + // Only indels are allowed + assert(nodeIds_.size() <= 2); + } + + ~SmallVariantAnalyzer() = default; + + std::unique_ptr analyze() const override; + + void processMates( + const reads::Read& read, const graphtools::GraphAlignment& readAlignment, const reads::Read& mate, + const graphtools::GraphAlignment& mateAlignment) override; + + double haplotypeDepth() const { return haplotypeDepth_; } + +protected: + int countReadsSupportingNode(graphtools::NodeId nodeId) const; + + VariantSubtype variantSubtype_; + double haplotypeDepth_; + boost::optional optionalRefNode_; + + ClassifierOfAlignmentsToVariant alignmentClassifier_; + SmallVariantGenotyper smallVariantGenotyper_; + AllelePresenceChecker allelePresenceChecker_; + + std::shared_ptr verboseLogger_; +}; + +} diff --git a/region_analysis/VariantAnalyzer.cpp b/region_analysis/VariantAnalyzer.cpp new file mode 100755 index 0000000..fed1109 --- /dev/null +++ b/region_analysis/VariantAnalyzer.cpp @@ -0,0 +1,21 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "region_analysis/VariantAnalyzer.hh" diff --git a/region_analysis/VariantAnalyzer.hh b/region_analysis/VariantAnalyzer.hh new file mode 100755 index 0000000..1a0ae77 --- /dev/null +++ b/region_analysis/VariantAnalyzer.hh @@ -0,0 +1,70 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include + +#include "graphalign/GraphAlignment.hh" +#include "graphcore/Graph.hh" + +#include "common/Common.hh" +#include "reads/Read.hh" +#include "region_analysis/VariantFindings.hh" + +namespace ehunter +{ + +class VariantAnalyzer +{ +public: + VariantAnalyzer( + std::string variantId, AlleleCount expectedAlleleCount, const graphtools::Graph& graph, + std::vector nodeIds) + : variantId_(std::move(variantId)) + , expectedAlleleCount_(expectedAlleleCount) + , graph_(graph) + , nodeIds_(std::move(nodeIds)) + { + } + virtual ~VariantAnalyzer() = default; + + virtual void processMates( + const reads::Read& read, const graphtools::GraphAlignment& readAlignment, const reads::Read& mate, + const graphtools::GraphAlignment& mateAlignment) + = 0; + + virtual std::unique_ptr analyze() const = 0; + + const std::string& variantId() const { return variantId_; } + AlleleCount expectedAlleleCount() const { return expectedAlleleCount_; } + const graphtools::Graph& graph() const { return graph_; } + const std::vector& nodeIds() const { return nodeIds_; } + +protected: + std::string variantId_; + AlleleCount expectedAlleleCount_; + const graphtools::Graph& graph_; + std::vector nodeIds_; +}; + +} diff --git a/region_analysis/VariantFindings.cpp b/region_analysis/VariantFindings.cpp new file mode 100755 index 0000000..e497a21 --- /dev/null +++ b/region_analysis/VariantFindings.cpp @@ -0,0 +1,45 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "region_analysis/VariantFindings.hh" + +using std::string; + +namespace ehunter +{ + +std::ostream& operator<<(std::ostream& out, const RepeatFindings& repeatFindings) +{ + out << "Genotype: "; + if (repeatFindings.optionalGenotype()) + { + out << *repeatFindings.optionalGenotype(); + } + else + { + out << "N/A"; + } + out << "; table of spanning reads: " << repeatFindings.countsOfSpanningReads() + << "; table of flanking reads: " << repeatFindings.countsOfFlankingReads() + << "; table of inrepeat reads: " << repeatFindings.countsOfInrepeatReads(); + return out; +} + +} diff --git a/region_analysis/VariantFindings.hh b/region_analysis/VariantFindings.hh new file mode 100755 index 0000000..1a94a7e --- /dev/null +++ b/region_analysis/VariantFindings.hh @@ -0,0 +1,126 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include + +#include + +#include "common/CountTable.hh" +#include "genotyping/AllelePresenceChecker.hh" +#include "genotyping/RepeatGenotype.hh" +#include "genotyping/SmallVariantGenotype.hh" + +namespace ehunter +{ + +class VariantFindings; +class RepeatFindings; +class SmallVariantFindings; + +struct VariantFindingsVisitor +{ + virtual void visit(const RepeatFindings* findingsPtr) = 0; + virtual void visit(const SmallVariantFindings* findingsPtr) = 0; +}; + +class VariantFindings +{ +public: + virtual ~VariantFindings() = default; + virtual void accept(VariantFindingsVisitor* visitorPtr) = 0; +}; + +using RegionFindings = std::unordered_map>; +using SampleFindings = std::unordered_map; + +class RepeatFindings : public VariantFindings +{ +public: + RepeatFindings( + CountTable countsOfSpanningReads, CountTable countsOfFlankingReads, CountTable countsOfInrepeatReads, + boost::optional optionalGenotype) + : countsOfSpanningReads_(std::move(countsOfSpanningReads)) + , countsOfFlankingReads_(std::move(countsOfFlankingReads)) + , countsOfInrepeatReads_(std::move(countsOfInrepeatReads)) + , optionalGenotype_(std::move(optionalGenotype)) + { + } + + ~RepeatFindings() override = default; + void accept(VariantFindingsVisitor* visitorPtr) override { visitorPtr->visit(this); } + + const CountTable& countsOfSpanningReads() const { return countsOfSpanningReads_; } + const CountTable& countsOfFlankingReads() const { return countsOfFlankingReads_; } + const CountTable& countsOfInrepeatReads() const { return countsOfInrepeatReads_; } + const boost::optional& optionalGenotype() const { return optionalGenotype_; } + + bool operator==(const RepeatFindings& other) const + { + return countsOfSpanningReads_ == other.countsOfSpanningReads_ + && countsOfFlankingReads_ == other.countsOfFlankingReads_ + && countsOfInrepeatReads_ == other.countsOfInrepeatReads_ && optionalGenotype_ == other.optionalGenotype_; + } + +private: + CountTable countsOfSpanningReads_; + CountTable countsOfFlankingReads_; + CountTable countsOfInrepeatReads_; + boost::optional optionalGenotype_; +}; + +class SmallVariantFindings : public VariantFindings +{ +public: + SmallVariantFindings( + int numRefReads, int numAltReads, + AllelePresenceStatus refAlleleStatus, AllelePresenceStatus altAlleleStatus, + boost::optional optionalGenotype) + : numRefReads_(numRefReads) + , numAltReads_(numAltReads) + , refAlleleStatus_(refAlleleStatus) + , altAlleleStatus_(altAlleleStatus) + , optionalGenotype_(std::move(optionalGenotype)) + { + } + + ~SmallVariantFindings() override = default; + void accept(VariantFindingsVisitor* visitorPtr) override { visitorPtr->visit(this); } + + int numRefReads() const { return numRefReads_; } + int numAltReads() const { return numAltReads_; } + const boost::optional& optionalGenotype() const { return optionalGenotype_; } + + AllelePresenceStatus refAllelePresenceStatus() const { return refAlleleStatus_; } + AllelePresenceStatus altAllelePresenceStatus() const { return altAlleleStatus_; } + +private: + int numRefReads_; + int numAltReads_; + AllelePresenceStatus refAlleleStatus_; + AllelePresenceStatus altAlleleStatus_; + boost::optional optionalGenotype_; +}; + +std::ostream& operator<<(std::ostream& out, const RepeatFindings& repeatFindings); + +} diff --git a/region_analysis/tests/CMakeLists.txt b/region_analysis/tests/CMakeLists.txt new file mode 100755 index 0000000..8335add --- /dev/null +++ b/region_analysis/tests/CMakeLists.txt @@ -0,0 +1,7 @@ +add_executable(RepeatAnalyzerTest RepeatAnalyzerTest.cpp) +target_link_libraries(RepeatAnalyzerTest region_analysis gtest gmock_main) +add_test(NAME RepeatAnalyzerTest COMMAND RepeatAnalyzerTest) + +add_executable(RegionAnalyzerTest RegionAnalyzerTest.cpp) +target_link_libraries(RegionAnalyzerTest region_analysis gtest gmock_main) +add_test(NAME RegionAnalyzerTest COMMAND RegionAnalyzerTest) diff --git a/region_analysis/tests/RegionAnalyzerTest.cpp b/region_analysis/tests/RegionAnalyzerTest.cpp new file mode 100755 index 0000000..34b8f4f --- /dev/null +++ b/region_analysis/tests/RegionAnalyzerTest.cpp @@ -0,0 +1,76 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "region_analysis/RegionAnalyzer.hh" + +#include "gtest/gtest.h" + +#include "input/GraphBlueprint.hh" +#include "input/RegionGraph.hh" +#include "region_spec/LocusSpecification.hh" + +using namespace ehunter; + +using graphtools::Graph; +using reads::Read; +using std::string; +using std::vector; + +class AlignerTests : public ::testing::TestWithParam +{ +}; + +// TODO: Throw an error if there are no valid extensions? +TEST_P(AlignerTests, RegionAnalysis_ShortSingleUnitRepeat_Genotyped) +{ + Graph graph = makeRegionGraph(decodeFeaturesFromRegex("ATTCGA(C)*ATGTCG")); + vector referenceRegions = { Region("chr1:1-2") }; + + LocusSpecification regionSpec("region", referenceRegions, AlleleCount::kTwo, graph); + VariantClassification classification(VariantType::kRepeat, VariantSubtype::kCommonRepeat); + regionSpec.addVariantSpecification("repeat", classification, Region("chr1:1-2"), { 1 }, 1); + + SampleParameters sampleParams("dummy_sample", Sex::kFemale, 10, 5.0); + HeuristicParameters heuristicParams(false, 1000, 20, true, GetParam(), 4, 1, 5); + + RegionAnalyzer regionAnalyzer(regionSpec, sampleParams, heuristicParams, std::cerr); + + regionAnalyzer.processMates(Read("read1/1", "CGACCCATGT"), Read("read1/2", "GACCCATGTC")); + regionAnalyzer.processMates(Read("read2/1", "CGACATGT"), Read("read2/2", "GACATGTC")); + + RegionFindings regionFindings = regionAnalyzer.genotype(); + + std::unique_ptr repeatFindingsPtr(new RepeatFindings( + CountTable({ { 1, 2 }, { 3, 2 } }), CountTable(), CountTable(), RepeatGenotype(1, { 1, 3 }))); + RegionFindings expectedFindings; + expectedFindings.emplace(std::make_pair("repeat", std::move(repeatFindingsPtr))); + + ASSERT_EQ(expectedFindings, regionFindings); +} + +TEST_P(AlignerTests, RegionAnalysis_ShortMultiUnitRepeat_Genotyped) +{ + // const int32_t kmerLenForReadOrientation = 5; + // const int32_t kmerLenForAlignment = 4; + // const int32_t paddingLength = 1; +} + +// INSTANTIATE_TEST_CASE_P( +// AlignerTestsInst, AlignerTests, ::testing::Values(std::string("path-aligner"), std::string("dag-aligner")), ); diff --git a/region_analysis/tests/RepeatAnalyzerTest.cpp b/region_analysis/tests/RepeatAnalyzerTest.cpp new file mode 100755 index 0000000..362aebd --- /dev/null +++ b/region_analysis/tests/RepeatAnalyzerTest.cpp @@ -0,0 +1,63 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "region_analysis/RepeatAnalyzer.hh" + +#include "gtest/gtest.h" + +#include "graphalign/GraphAlignment.hh" +#include "graphalign/GraphAlignmentOperations.hh" + +#include "input/GraphBlueprint.hh" +#include "input/RegionGraph.hh" + +using graphtools::Graph; +using graphtools::GraphAlignment; + +using namespace ehunter; + +TEST(DISABLE_RegionAnalysis, ShortSingleUnitRepeat_Genotyped) +{ + Graph graph = makeRegionGraph(decodeFeaturesFromRegex("ATTCGA(C)*ATGTCG")); + + GraphAlignment alignmentA1 = decodeGraphAlignment(3, "0[3M]1[1M]1[1M]1[1M]2[4M]", &graph); + GraphAlignment alignmentA2 = decodeGraphAlignment(4, "0[2M]1[1M]1[1M]1[1M]2[5M]", &graph); + GraphAlignment alignmentB1 = decodeGraphAlignment(3, "0[3M]1[1M]2[4M]", &graph); + GraphAlignment alignmentB2 = decodeGraphAlignment(4, "0[2M]1[1M]2[5M]", &graph); + GraphAlignment alignmentC1 = decodeGraphAlignment(2, "0[4M]1[1M]", &graph); + GraphAlignment alignmentC2 = decodeGraphAlignment(0, "1[1M]1[1M]2[5M]", &graph); + + AlleleCount expectedAlleleCount = AlleleCount::kTwo; + graphtools::NodeId repeatNodeId = 1; + const int32_t maxNumUnitsInRead = 10; + const double haplotypeDepth = 5.0; + + RepeatAnalyzer repeatAnalyzer( + "repeat0", expectedAlleleCount, graph, repeatNodeId, haplotypeDepth, maxNumUnitsInRead); + // repeatAnalyzer.processMates({ alignmentA1 }, { alignmentA2 }); + // repeatAnalyzer.processMates({ alignmentB1 }, { alignmentB2 }); + // repeatAnalyzer.processMates({ alignmentC1 }, { alignmentC2 }); + // RepeatFindings repeatFindings = repeatAnalyzer.analyzeCommonRepeat(); + + // RepeatGenotype repeatGenotype(1, { 1, 3 }); + // RepeatFindings expectedFindings( + // CountTable({ { 1, 1 }, { 2, 1 } }), CountTable({ { 1, 2 }, { 3, 2 } }), repeatGenotype); + // ASSERT_EQ(expectedFindings, repeatFindings); +} diff --git a/region_spec/CMakeLists.txt b/region_spec/CMakeLists.txt new file mode 100755 index 0000000..af20014 --- /dev/null +++ b/region_spec/CMakeLists.txt @@ -0,0 +1,3 @@ +file(GLOB SOURCES "*.cpp") +add_library(region_spec ${SOURCES}) +target_link_libraries(region_spec common graphtools) \ No newline at end of file diff --git a/region_spec/LocusSpecification.cpp b/region_spec/LocusSpecification.cpp new file mode 100755 index 0000000..6894120 --- /dev/null +++ b/region_spec/LocusSpecification.cpp @@ -0,0 +1,95 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Mitch Bekritsky , Richard Shaw +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "region_spec/LocusSpecification.hh" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "thirdparty/json/json.hpp" +#include "thirdparty/spdlog/spdlog.h" + +#include "common/Common.hh" +#include "common/Reference.hh" + +using boost::optional; +using graphtools::NodeId; +using std::map; +using std::ostream; +using std::string; +using std::to_string; +using std::vector; + +using Json = nlohmann::json; + +namespace spd = spdlog; + +namespace ehunter +{ +LocusSpecification::LocusSpecification( + RegionId regionId, std::vector referenceLoci, AlleleCount expectedAlleleCount, + graphtools::Graph regionGraph) + : regionId_(std::move(regionId)) + , referenceLoci_(std::move(referenceLoci)) + , expectedAlleleCount_(expectedAlleleCount) + , regionGraph_(std::move(regionGraph)) +{ +} + +void LocusSpecification::addVariantSpecification( + std::string id, VariantClassification classification, Region referenceLocus, vector nodes, + optional refNode) +{ + variantSpecs_.emplace_back(std::move(id), classification, std::move(referenceLocus), std::move(nodes), refNode); +} + +const VariantSpecification& LocusSpecification::getVariantSpecById(const std::string& variantSpecId) const +{ + for (const auto& variantSpec : variantSpecs_) + { + if (variantSpec.id() == variantSpecId) + { + return variantSpec; + } + } + + throw std::logic_error("There is no variant " + variantSpecId + " in locus " + regionId_); +} + +/* +ostream& operator<<(ostream& out, const LocusSpecification& LocusSpecification) +{ + for (const auto& component : LocusSpecification.regionBlueprint()) + { + out << component; + } + + return out; +} */ + +} diff --git a/region_spec/LocusSpecification.hh b/region_spec/LocusSpecification.hh new file mode 100755 index 0000000..0e48f92 --- /dev/null +++ b/region_spec/LocusSpecification.hh @@ -0,0 +1,86 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Mitch Bekritsky , Richard Shaw +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "graphcore/Graph.hh" +#include "thirdparty/json/json.hpp" + +#include "common/Common.hh" +#include "common/GenomicRegion.hh" +#include "common/Reference.hh" +#include "region_spec/VariantSpecification.hh" + +namespace ehunter +{ + +using RegionId = std::string; + +class LocusSpecification +{ +public: + LocusSpecification( + RegionId regionId, std::vector referenceLoci, AlleleCount expectedAlleleCount, + graphtools::Graph regionGraph); + + const RegionId& regionId() const { return regionId_; } + /* + * List of all regions in the reference this graph describes + * i.e. where we expect relevant reads to align + */ + const std::vector& referenceLoci() const { return referenceLoci_; } + /* + * List of regions that additional relevant reads might be found + * Require filtering or special considerations + */ + const std::vector& offtargetLoci() const { return offtargetLoci_; } + void setOfftargetLoci(const std::vector& offtargetLoci) { offtargetLoci_ = offtargetLoci; } + const graphtools::Graph& regionGraph() const { return regionGraph_; } + AlleleCount expectedAlleleCount() const { return expectedAlleleCount_; } + + const std::vector& variantSpecs() const { return variantSpecs_; } + void addVariantSpecification( + std::string id, VariantClassification classification, Region referenceLocus, + std::vector nodes, boost::optional optionalRefNode); + + const VariantSpecification& getVariantSpecById(const std::string& variantSpecId) const; + +private: + std::string regionId_; + std::vector referenceLoci_; + AlleleCount expectedAlleleCount_; + std::vector offtargetLoci_; + graphtools::Graph regionGraph_; + std::vector variantSpecs_; +}; + +using RegionCatalog = std::map; + +} diff --git a/region_spec/VariantSpecification.cpp b/region_spec/VariantSpecification.cpp new file mode 100755 index 0000000..388b566 --- /dev/null +++ b/region_spec/VariantSpecification.cpp @@ -0,0 +1,123 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Mitch Bekritsky , Richard Shaw +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "region_spec/VariantSpecification.hh" + +#include + +using std::string; +using std::to_string; + +namespace ehunter +{ + +void VariantSpecification::assertConsistency() const +{ + const bool variantIsRepeat = classification_.type == VariantType::kRepeat; + const bool variantIsDeletionOrSwap = classification_.type == VariantType::kSmallVariant + && (classification_.subtype == VariantSubtype::kDeletion || classification_.subtype == VariantSubtype::kSwap + || classification_.subtype == VariantSubtype::kSMN); + const bool variantIsInsertion + = classification_.type == VariantType::kSmallVariant && classification_.subtype == VariantSubtype::kInsertion; + + bool variantIsValid = false; + + if (variantIsRepeat) + { + variantIsValid = classification_.subtype == VariantSubtype::kCommonRepeat + || classification_.subtype == VariantSubtype::kRareRepeat; + } + else if (variantIsDeletionOrSwap) + { + variantIsValid = optionalRefNode_ != boost::none; + } + else if (variantIsInsertion) + { + variantIsValid = optionalRefNode_ == boost::none; + } + + if (!variantIsValid) + { + std::ostringstream encoding; + encoding << *this; + throw std::logic_error("Definition of variant " + encoding.str() + " is inconsistent"); + } +} + +std::ostream& operator<<(std::ostream& out, VariantType type) +{ + switch (type) + { + case VariantType::kSmallVariant: + out << "SmallVariant"; + break; + case VariantType::kRepeat: + out << "Repeat"; + break; + } + return out; +} + +std::ostream& operator<<(std::ostream& out, VariantSubtype subtype) +{ + switch (subtype) + { + case VariantSubtype::kRareRepeat: + out << "RareRepeat"; + break; + case VariantSubtype::kCommonRepeat: + out << "Repeat"; + break; + case VariantSubtype::kDeletion: + out << "Deletion"; + break; + case VariantSubtype::kInsertion: + out << "Insertion"; + break; + case VariantSubtype::kSwap: + out << "Swap"; + break; + case VariantSubtype::kSMN: + out << "SMN"; + break; + + } + + return out; +} + +std::ostream& operator<<(std::ostream& out, VariantClassification classification) +{ + out << classification.type << "/" << classification.subtype; + return out; +} + +std::ostream& operator<<(std::ostream& out, const VariantSpecification& variantSpec) +{ + const string refNodeEncoding = variantSpec.optionalRefNode() ? to_string(*variantSpec.optionalRefNode()) : "None"; + out << "ID=" << variantSpec.id() << ";classification=" << variantSpec.classification() + << ";ReferenceLocus=" << variantSpec.referenceLocus() << ";optionalRefNode=" << refNodeEncoding; + + return out; +} + +} diff --git a/region_spec/VariantSpecification.hh b/region_spec/VariantSpecification.hh new file mode 100755 index 0000000..9fc73af --- /dev/null +++ b/region_spec/VariantSpecification.hh @@ -0,0 +1,115 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Mitch Bekritsky , Richard Shaw +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include +#include + +#include + +#include "graphcore/Graph.hh" + +#include "common/Common.hh" +#include "common/GenomicRegion.hh" + +namespace ehunter +{ + +enum class VariantType +{ + kRepeat, + kSmallVariant +}; + +enum class VariantSubtype +{ + kCommonRepeat, + kRareRepeat, + kInsertion, + kDeletion, + kSwap, + kSMN +}; + + +struct VariantClassification +{ + VariantClassification(VariantType type, VariantSubtype subtype) + : type(type) + , subtype(subtype) + { + } + + bool operator==(const VariantClassification& other) const + { + return (other.type == type && other.subtype == subtype); + } + + VariantType type; + VariantSubtype subtype; +}; + +class VariantSpecification +{ +public: + VariantSpecification( + std::string id, VariantClassification classification, Region referenceLocus, + std::vector nodes, boost::optional optionalRefNode) + : id_(std::move(id)) + , classification_(classification) + , referenceLocus_(std::move(referenceLocus)) + , nodes_(std::move(nodes)) + , optionalRefNode_(optionalRefNode) + { + assertConsistency(); + } + + const std::string& id() const { return id_; } + VariantClassification classification() const { return classification_; } + const Region& referenceLocus() const { return referenceLocus_; } + const std::vector& nodes() const { return nodes_; } + const boost::optional& optionalRefNode() const { return optionalRefNode_; } + + bool operator==(const VariantSpecification& other) const + { + return id_ == other.id_ && classification_ == other.classification() && nodes_ == other.nodes_; + } + + void assertConsistency() const; + +private: + std::string id_; + VariantClassification classification_; + Region referenceLocus_; + std::vector nodes_; + boost::optional optionalRefNode_; +}; + +std::ostream& operator<<(std::ostream& out, VariantType type); +std::ostream& operator<<(std::ostream& out, VariantSubtype subtype); +std::ostream& operator<<(std::ostream& out, VariantClassification classification); +std::ostream& operator<<(std::ostream& out, const VariantSpecification& variantSpec); + +} diff --git a/rep_align/CMakeLists.txt b/rep_align/CMakeLists.txt deleted file mode 100644 index 80fa38e..0000000 --- a/rep_align/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_library(rep_align rep_align.cc) -target_link_libraries(rep_align purity common) - -add_subdirectory(unit_tests) \ No newline at end of file diff --git a/rep_align/rep_align.cc b/rep_align/rep_align.cc deleted file mode 100644 index 2cbbc56..0000000 --- a/rep_align/rep_align.cc +++ /dev/null @@ -1,404 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include -#include -#include -#include - -#include "include/read_group.h" -#include "purity/purity.h" -#include "rep_align/rep_align.h" - -using std::string; -using std::vector; -using std::cerr; -using std::endl; - -namespace ehunter { - -size_t CountUnitsAtOffset(const vector &units, const string &bases, - size_t offset) { - const size_t unit_len = units[0].length(); - size_t num_units = 0; - - while (offset + unit_len <= bases.length()) { - for (const string &unit : units) { - if (std::equal(bases.begin() + offset, bases.begin() + offset + unit_len, - unit.begin())) { - ++num_units; - break; - } - } - offset += unit_len; - } - - return num_units; -} - -size_t GetOffsetMostUnits(const vector &units, const string &bases, - size_t *max_unit_count) { - const size_t unit_len = units[0].length(); - size_t offset_with_most_occurances = 0; - *max_unit_count = 0; - - for (size_t offset = 0; offset != unit_len; ++offset) { - size_t unit_count_at_offset = CountUnitsAtOffset(units, bases, offset); - if (unit_count_at_offset > *max_unit_count) { - *max_unit_count = unit_count_at_offset; - offset_with_most_occurances = offset; - } - } - return offset_with_most_occurances; -} - -static const string RevComp(const string &bases) { - string bases_rc = bases; - string::reverse_iterator rev_iter = bases_rc.rbegin(); - char complement_base(' '); - - for (const char base : bases) { - switch (base) { - case 'A': - complement_base = 'T'; - break; - case 'C': - complement_base = 'G'; - break; - case 'G': - complement_base = 'C'; - break; - case 'T': - complement_base = 'A'; - break; - case '/': - complement_base = '/'; - break; - default: - complement_base = 'N'; - } - - *rev_iter++ = complement_base; - } - - return bases_rc; -} - -static bool PerfectMatch(const string &target_kmer, - const vector &units) { - assert(target_kmer.length() == units[0].length()); - - for (const string &unit : units) { - if (unit == target_kmer) { - return true; - } - } - return false; -} - -bool AlignLeftFlank(const vector &units, const string &left_flank, - const string &bases, const string &quals, - size_t offset_most_units, size_t min_baseq, - double min_wp_score, size_t *left_flank_len, - double *left_flank_score) { - const size_t unit_len = units[0].length(); - - *left_flank_len = 0; - for (size_t offset = offset_most_units; offset + unit_len < bases.length(); - offset += unit_len) { - const string cur_kmer = bases.substr(offset, unit_len); - if (PerfectMatch(cur_kmer, units)) { - const string bases_pref = bases.substr(0, offset); - const string quals_pref = quals.substr(0, offset); - const string left_flank_pref = - left_flank.substr(left_flank.length() - offset, offset); - const vector left_flank_pref_units = {left_flank_pref}; - *left_flank_score = - MatchUnits(left_flank_pref_units, bases_pref.begin(), - bases_pref.end(), quals_pref.begin(), min_baseq); - if (*left_flank_score / bases_pref.length() >= min_wp_score) { - const string bases_pref_rc = RevComp(bases_pref); - const string quals_pref_rc(quals_pref.rbegin(), quals_pref.rend()); - - vector units_rc; - for (const string &unit : units) { - units_rc.push_back(RevComp(unit)); - } - float prefix_repeat_score = - MatchRepeat(units_rc, bases_pref_rc, quals_pref_rc, min_baseq); - - if (*left_flank_score >= 2 + prefix_repeat_score) { - *left_flank_len = offset; - return true; - } - } - } - } - *left_flank_score = 0; - return false; -} - -bool AlignRightFlank(const vector &units, const string &right_flank, - const string &bases, const string &quals, - size_t offset_most_units, size_t min_baseq, - double min_wp_score, size_t *right_flank_len, - double *right_flank_score) { - vector units_rc; - for (const string &unit : units) { - units_rc.push_back(RevComp(unit)); - } - const string left_flank = RevComp(right_flank); - const string bases_rc = RevComp(bases); - const string quals_re(quals.rbegin(), quals.rend()); - const size_t offset_most_units_re = - (bases.length() - offset_most_units) % units[0].length(); - - bool is_found = AlignLeftFlank(units_rc, left_flank, bases_rc, quals_re, - offset_most_units_re, min_baseq, min_wp_score, - right_flank_len, right_flank_score); - return is_found; -} - -bool IsSpanningOrFlankingRead(const Parameters ¶ms, - const RepeatSpec &repeat_spec, - const string &bases, const string &quals, - RepeatAlign *rep_align) { - const vector &units = repeat_spec.units_shifts[0]; - size_t max_unit_count = 0; - size_t offset_most_units = GetOffsetMostUnits(units, bases, &max_unit_count); - - double left_flank_score = 0; - double right_flank_score = 0; - - const double kFlankMinWpScore = 0.7; - - const bool matches_left_flank = - AlignLeftFlank(units, repeat_spec.left_flank, bases, quals, - offset_most_units, params.min_baseq(), kFlankMinWpScore, - &rep_align->left_flank_len, &left_flank_score); - - const bool matches_right_flank = - AlignRightFlank(units, repeat_spec.right_flank, bases, quals, - offset_most_units, params.min_baseq(), kFlankMinWpScore, - &rep_align->right_flank_len, &right_flank_score); - - if (bases.length() < rep_align->left_flank_len + rep_align->right_flank_len) { - return false; - } - - // Read prefix matching left flank or empty if none detected. - const string bases_prefix = bases.substr(0, rep_align->left_flank_len); - const string quals_prefix = quals.substr(0, rep_align->left_flank_len); - - const size_t middle_len = - bases.length() - rep_align->right_flank_len - rep_align->left_flank_len; - const string bases_middle = - bases.substr(rep_align->left_flank_len, middle_len); - const string quals_middle = - quals.substr(rep_align->left_flank_len, middle_len); - - const string bases_suffix = bases.substr( - bases.length() - rep_align->right_flank_len, rep_align->right_flank_len); - const string quals_suffix = quals.substr( - bases.length() - rep_align->right_flank_len, rep_align->right_flank_len); - - double repeat_score = - MatchRepeat(units, bases_middle, quals_middle, params.min_baseq()); - - const size_t non_repeat_len = - rep_align->left_flank_len + rep_align->right_flank_len; - rep_align->size = (bases.length() - non_repeat_len) / units[0].length(); - rep_align->read.bases = bases; - rep_align->read.quals = quals; - - const double read_wp = - (left_flank_score + repeat_score + right_flank_score) / bases.length(); - - if (read_wp >= params.min_wp()) { - if (matches_left_flank && matches_right_flank) { - rep_align->type = RepeatAlign::Type::kSpanning; - return true; - } else if (matches_left_flank || matches_right_flank) { - rep_align->type = RepeatAlign::Type::kFlanking; - return true; - } - } - rep_align->size = 0; - - return false; -} - -bool IsSpanningOrFlankingReadRc(const Parameters ¶ms, - const RepeatSpec &repeat_spec, - const string &bases, const string &quals, - RepeatAlign *rep_align) { - const bool forward_match = IsSpanningOrFlankingRead( - params, repeat_spec, bases, quals, &(*rep_align)); - if (forward_match) { - return true; - } - - const string bases_rc = RevComp(bases); - const string quals_rc(quals.rbegin(), quals.rend()); - - const bool reverse_match = IsSpanningOrFlankingRead( - params, repeat_spec, bases_rc, quals_rc, &(*rep_align)); - - return reverse_match; -} - -static double ScoreSpanningAlign(size_t min_baseq, const vector &units, - const string &left_flank, - const string &right_flank, const string &bases, - const string &quals, size_t left_flank_len, - size_t right_flank_len) { - const string bases_prefix = bases.substr(0, left_flank_len); - const string quals_prefix = quals.substr(0, left_flank_len); - - assert(bases.length() >= left_flank_len + right_flank_len); - const size_t repeat_len = bases.length() - left_flank_len - right_flank_len; - - const string bases_repeat = bases.substr(left_flank_len, repeat_len); - const string quals_repeat = quals.substr(left_flank_len, repeat_len); - - const string bases_suffix = - bases.substr(bases.length() - right_flank_len, right_flank_len); - const string quals_suffix = - quals.substr(quals.length() - right_flank_len, right_flank_len); - - double repeat_score = - MatchRepeat(units, bases_repeat, quals_repeat, min_baseq); - - const string left_flank_pref = - left_flank.substr(left_flank.length() - left_flank_len, left_flank_len); - const vector left_flank_pref_units = {left_flank_pref}; - double left_flank_score = - MatchUnits(left_flank_pref_units, bases_prefix.begin(), - bases_prefix.end(), quals_prefix.begin(), min_baseq); - - const string right_flank_pref = right_flank.substr(0, right_flank_len); - const vector right_flank_pref_units = {right_flank_pref}; - double right_flank_score = - MatchUnits(right_flank_pref_units, bases_suffix.begin(), - bases_suffix.end(), quals_suffix.begin(), min_baseq); - - return (left_flank_score + repeat_score + right_flank_score) / bases.length(); -} - -static size_t FindTopRightFlankLen(size_t min_baseq, - const vector &units, - const string &left_flank, - const string &right_flank, - const string &bases, const string &quals, - size_t cur_size, size_t cur_left_len) { - size_t unit_len = units[0].length(); - double top_wp = 0; - size_t top_right_len = 0; - - for (size_t test_size = 1; test_size != cur_size + 1; ++test_size) { - const size_t test_repeat_len = test_size * unit_len; - - assert(bases.length() >= cur_left_len + test_repeat_len); - size_t test_right_len = bases.length() - cur_left_len - test_repeat_len; - const double test_wp = - ScoreSpanningAlign(min_baseq, units, left_flank, right_flank, bases, - quals, cur_left_len, test_right_len); - if (test_wp > top_wp) { - top_right_len = test_right_len; - top_wp = test_wp; - } - } - - return top_right_len; -} - -static size_t FindTopLeftFlankLen(size_t min_baseq, const vector &units, - const string &left_flank, - const string &right_flank, - const string &bases, const string &quals, - size_t cur_size, size_t cur_right_len) { - size_t unit_len = units[0].length(); - double top_wp = 0; - size_t top_left_len = 0; - size_t top_size = 0; - - for (size_t test_size = 1; test_size != cur_size + 1; ++test_size) { - const size_t test_repeat_len = test_size * unit_len; - assert(bases.length() >= cur_right_len + test_repeat_len); - size_t test_left_len = bases.length() - cur_right_len - test_repeat_len; - const double test_wp = - ScoreSpanningAlign(min_baseq, units, left_flank, right_flank, bases, - quals, test_left_len, cur_right_len); - if (test_wp > top_wp) { - top_size = test_size; - top_left_len = test_left_len; - top_wp = test_wp; - } - } - - return top_left_len; -} - -bool AlignRead(const Parameters ¶ms, const RepeatSpec &repeat_spec, - const string &bases, const string &quals, - RepeatAlign *rep_align) { - const bool aligns = IsSpanningOrFlankingReadRc(params, repeat_spec, bases, - quals, &(*rep_align)); - - if (!aligns || rep_align->type != RepeatAlign::Type::kSpanning) { - return aligns; - } - - const string &oriented_bases = rep_align->read.bases; - const string &oriented_quals = rep_align->read.quals; - - // Search for a better alignment. - const vector &units = repeat_spec.units_shifts[0]; - const size_t unit_len = units[0].length(); - - const size_t top_left_len = FindTopLeftFlankLen( - params.min_baseq(), units, repeat_spec.left_flank, - repeat_spec.right_flank, oriented_bases, oriented_quals, rep_align->size, - rep_align->right_flank_len); - - assert(oriented_bases.length() >= top_left_len + rep_align->right_flank_len); - size_t cur_size = - (oriented_bases.length() - top_left_len - rep_align->right_flank_len) / - unit_len; - - const size_t top_right_len = - FindTopRightFlankLen(params.min_baseq(), units, repeat_spec.left_flank, - repeat_spec.right_flank, oriented_bases, - oriented_quals, cur_size, top_left_len); - - cur_size = - (oriented_bases.length() - top_left_len - top_right_len) / unit_len; - - if (top_left_len != rep_align->left_flank_len || - top_right_len != rep_align->right_flank_len) { - rep_align->left_flank_len = top_left_len; - rep_align->right_flank_len = top_right_len; - rep_align->size = cur_size; - } - return true; -} -} // namespace ehunter diff --git a/rep_align/rep_align.h b/rep_align/rep_align.h deleted file mode 100644 index e9791f3..0000000 --- a/rep_align/rep_align.h +++ /dev/null @@ -1,91 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#pragma once - -#include -#include - -#include "common/common.h" -#include "common/parameters.h" -#include "common/repeat_spec.h" - -namespace ehunter { -struct RepeatAlign { - enum class Type { - kSpanning, - kFlanking, - kAnchored, - kAlignedIrrPair, - kUnalignedIrrPair, - kUnalignedIrrSingleton - }; - Read read; - Read mate; - size_t left_flank_len; - size_t right_flank_len; - Type type; - size_t size; -}; - -size_t CountUnitsAtOffset(const std::vector &units, - const std::string &bases, size_t offset); - -size_t GetOffsetMostUnits(const std::vector &units, - const std::string &bases, size_t *max_unit_count); - -// Aligns read to left flank of the repeat; the alignment is considered valid -// if the raw wp score is 2 greater than the alignment of the same piece of the -// read against the repeat. -bool AlignLeftFlank(const std::vector &units, - const std::string &left_flank, const std::string &bases, - const std::string &quals, size_t offset_most_units, - size_t min_baseq, double min_wp_score, - size_t *left_flank_len, double *left_flank_score); - -// Counterpart of AlignLeftFlank for the right flank of the repeat. -bool AlignRightFlank(const std::vector &units, - const std::string &right_flank, const std::string &bases, - const std::string &quals, size_t offset_most_units, - size_t min_baseq, double min_wp_score, - size_t *right_flank_len, double *right_flank_score); - -// Tries to align the read to the repeat in forward orientation. Returns true if -// an alignment was found and populates rep_align with the alignment info. -bool IsSpanningOrFlankingRead(const Parameters ¶ms, - const RepeatSpec &repeat_spec, - const std::string &bases, - const std::string &quals, RepeatAlign *rep_align); - -// Tries to align the read in forward and reverse orientations. -bool IsSpanningOrFlankingReadRc(const Parameters ¶ms, - const RepeatSpec &repeat_spec, - const std::string &bases, - const std::string &quals, - RepeatAlign *rep_align); - -// Returns true if a flanking or a spanning read alignment was found; rep_align -// holds the actual repeat alignment info. -bool AlignRead(const Parameters ¶ms, const RepeatSpec &repeat_spec, - const std::string &bases, const std::string &quals, - RepeatAlign *rep_align); -} // namespace ehunter diff --git a/rep_align/unit_tests/CMakeLists.txt b/rep_align/unit_tests/CMakeLists.txt deleted file mode 100644 index 6e376c5..0000000 --- a/rep_align/unit_tests/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -add_executable(rep_align_test rep_align_test.cc) -target_link_libraries(rep_align_test rep_align gtest_main) -add_test(NAME rep_align_test COMMAND rep_align_test) \ No newline at end of file diff --git a/rep_align/unit_tests/rep_align_test.cc b/rep_align/unit_tests/rep_align_test.cc deleted file mode 100644 index c931aa7..0000000 --- a/rep_align/unit_tests/rep_align_test.cc +++ /dev/null @@ -1,318 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include -#include -#include - -#include "common/parameters.h" -#include "common/repeat_spec.h" -#include "gtest/gtest.h" -#include "include/read_group.h" -#include "purity/purity.h" -#include "rep_align/rep_align.h" - -using std::cerr; -using std::endl; -using std::string; -using std::vector; - -using namespace ehunter; - -TEST(CountUnitsAtOffset, UnitlessSeq_CountZero) { - const vector units = {"AT"}; - const string untless_seq = "CGGCGGCGGCGG"; - const size_t offset = 0; - EXPECT_EQ(CountUnitsAtOffset(units, untless_seq, offset), 0); -} - -TEST(CountUnitsAtOffset, UnitsAtOffsetZero_Counted) { - const vector units = {"AT"}; - const string seq = "ATGGATCTATA"; - const size_t offset = 0; - EXPECT_EQ(CountUnitsAtOffset(units, seq, offset), 3); -} - -TEST(CountUnitsAtOffset, MultiunitsAtOffset_Counted) { - const vector units = {"AA", "TT"}; - const string seq = "CCAACCTTCCAAC"; - const size_t offset = 2; - EXPECT_EQ(CountUnitsAtOffset(units, seq, offset), 3); -} - -TEST(CountUnitsAtOffset, UnitsAtOffset_CountedWhenOffsetSetCorrectly) { - const vector units = {"AT"}; - const string seq = "CATCCATC"; - const size_t right_offset = 1; - const size_t wrong_offset = 0; - EXPECT_EQ(CountUnitsAtOffset(units, seq, right_offset), 2); - EXPECT_EQ(CountUnitsAtOffset(units, seq, wrong_offset), 0); -} - -//**** GetOffsetMostUnits **** - -TEST(GetOffsetMostUnits, SingleUnitString_Computed) { - const vector units = {"CGG"}; - const string seq = "AACGGAAACGGACGGAACGGAAAAA"; - size_t unit_count = 0; - const size_t offset = GetOffsetMostUnits(units, seq, &unit_count); - EXPECT_EQ(offset, 2); - EXPECT_EQ(unit_count, 3); -} - -TEST(GetOffsetMostUnits, MultiUnitString_Computed) { - const vector units = {"CGG", "AAA"}; - const string seq = "AACGGAAACGGACGGAACGGAAAAA"; - size_t unit_count = 0; - const size_t offset = GetOffsetMostUnits(units, seq, &unit_count); - EXPECT_EQ(offset, 2); - EXPECT_EQ(unit_count, 5); -} - -//**** AlignLeftFlank **** - -TEST(AlignLeftFlank, PrefixHasNoUnits_Detected) { - const string bases = "CGCGATAT"; - const string quals = "QQQQQQQQ"; - const string prefix = "CGCGCGCGCG"; - const vector units = {"AT"}; - const size_t offset_most_units = 0; - const size_t min_baseq = 20; - const double min_wp_score = 0.9; - - double left_flank_score = 0; - size_t left_flank_len = 0; - ASSERT_TRUE(AlignLeftFlank(units, prefix, bases, quals, offset_most_units, - min_baseq, min_wp_score, &left_flank_len, - &left_flank_score)); - ASSERT_EQ(left_flank_len, 4); - EXPECT_DOUBLE_EQ(left_flank_score, 4.0); -} - -TEST(AlignLeftFlank, LeftFlankTooSimilarToRepeatToMatch_Rejected) { - // ----RRRR - const string bases = "ATAAATAT"; - const string quals = "QQQ(QQQQ"; - const string prefix = "AAAAATAA"; - const vector units = {"AT"}; - const size_t offset_most_units = 0; - const size_t min_baseq = 20; - const double min_wp_score = 0.9; - - double left_flank_score = 0; - size_t left_flank_len = 0; - ASSERT_FALSE(AlignLeftFlank(units, prefix, bases, quals, offset_most_units, - min_baseq, min_wp_score, &left_flank_len, - &left_flank_score)); -} - -TEST(AlignLeftFlank, InRepeatRead_Rejected) { - const string bases = "ATATATATATATATA"; - const string quals = "QQQQQQQQQQQQQQQ"; - const string prefix = "CGCGCGCGCGCGCGCGCGCG"; - const vector units = {"AT"}; - const size_t offset_most_units = 0; - const size_t min_baseq = 20; - const double min_wp_score = 0.9; - - double left_flank_score = 0; - size_t left_flank_len = 0; - ASSERT_FALSE(AlignLeftFlank(units, prefix, bases, quals, offset_most_units, - min_baseq, min_wp_score, &left_flank_len, - &left_flank_score)); - EXPECT_EQ(left_flank_len, 0); - EXPECT_DOUBLE_EQ(left_flank_score, 0.0); -} - -//**** AlignRightFlank **** - -TEST(AlignRightFlank, SuffixHasNoUnits_Detected) { - const string bases = "ATATCGC"; - const string quals = "QQQQQQQ"; - const string prefix = "CGCGCGCGC"; - const vector units = {"AT"}; - const size_t offset_most_units = 0; - const size_t min_baseq = 20; - const double min_wp_score = 0.9; - - double right_flank_score = 0; - size_t repeat_offset = 0; - ASSERT_TRUE(AlignRightFlank(units, prefix, bases, quals, offset_most_units, - min_baseq, min_wp_score, &repeat_offset, - &right_flank_score)); - ASSERT_EQ(repeat_offset, 3); - ASSERT_DOUBLE_EQ(right_flank_score, 3.0); -} - -//**** IsSpanningOrFlankingRead **** - -TEST(IsSpanningOrFlankingRead, UnambigousSpanningRead_Detected) { - // ------RRRRRR--- - const string bases = "CGCGCGATATATGGG"; - const string quals = "QQQQQQQQQQQQQQQ"; - - RepeatSpec repeat_spec; - repeat_spec.left_flank = "CCGCGCGCGCGCGCG"; - repeat_spec.right_flank = "GGGGGGGGGGGGGGG"; - const vector units = {"AT"}; - repeat_spec.units_shifts = shift_units(units); - - Parameters params; - params.set_min_baseq(20); - params.set_min_wp(0.9); - - RepeatAlign ra; - ASSERT_TRUE(IsSpanningOrFlankingRead(params, repeat_spec, bases, quals, &ra)); - ASSERT_EQ(ra.type, RepeatAlign::Type::kSpanning); - EXPECT_EQ(ra.left_flank_len, 6); - EXPECT_EQ(ra.right_flank_len, 3); -} - -/* -TEST(IsSpanningOrFlankingRead, UnitlessRead_Rejected) { - const string bases = "ATATATATATATATA"; - const string quals = "QQQQQQQQQQQQQQQ"; - const string left_flank = "CGCGCGCGCGGCGGCG"; - const string right_flank = "GGGGGGGGGGGGGGGGGG"; - const vector units = {"CG"}; - const vector> units_shifts = shift_units(units); - const size_t min_baseq = 20; - const float min_wp_score = 0.9; - - RepeatAlign ra; - ASSERT_FALSE(IsSpanningOrFlankingRead(units_shifts, min_baseq, min_wp_score, - left_flank, right_flank, bases, quals, - &ra)); - EXPECT_EQ(ra.size, 0); -} - -TEST(IsSpanningOrFlankingRead, SpanningReadWrongRepeatUnit_Rejected) { - // ------RRRRRR--- - const string bases = "CGCGCGATCCATGGG"; - const string quals = "QQQQQQQQQQQQQQQ"; - const string left_flank = "CCCCCCGCGCGCGCG"; - const string right_flank = "GGGGGGGGGGGGGGG"; - const vector units = {"AT"}; - const vector> units_shifts = shift_units(units); - const size_t min_baseq = 20; - const float min_wp_score = 0.9; - - RepeatAlign ra; - ASSERT_FALSE(IsSpanningOrFlankingRead(units_shifts, min_baseq, min_wp_score, - left_flank, right_flank, bases, quals, - &ra)); -} - -TEST(IsSpanningOrFlankingRead, LeftFlankingRead_Detected) { - // --------RRRRRRRRRRR - const string bases = "AAAAAAAACCGCCGCCGCC"; - const string quals = "QQQQQQQQQQQQQQQQQQQ"; - const string left_flank = "AAAAAAAAAAAAAAAAAAA"; - const string right_flank = "GGGGGGGGGGGGGGGGGGG"; - const vector units = {"CCG"}; - const vector> units_shifts = shift_units(units); - const size_t min_baseq = 20; - const float min_wp_score = 0.9; - - size_t left_flank_len = 0; - size_t right_flank_len = 0; - RepeatAlign::Type read_type; - - RepeatAlign ra; - ASSERT_TRUE(IsSpanningOrFlankingRead(units_shifts, min_baseq, min_wp_score, - left_flank, right_flank, bases, quals, - &ra)); - ASSERT_EQ(ra.type, RepeatAlign::Type::kFlanking); - EXPECT_EQ(ra.left_flank_len, 8); - EXPECT_EQ(ra.right_flank_len, 0); -} - -TEST(IsSpanningOrFlankingRead, RightFlankingRead_Detected) { - // RRRRRRRRRRRR------- - const string bases = "GGCCCCGGCCCCGGGGGGG"; - const string quals = "QQQQQQQQQQQQQQQQQQQ"; - const string left_flank = "AAAAAAAAAAAAAAAAAAA"; - const string right_flank = "GGGGGGGGGGGGGGGGGGG"; - const vector units = {"GGCCCC"}; - const vector> units_shifts = shift_units(units); - const size_t min_baseq = 20; - const float min_wp_score = 0.9; - - size_t left_flank_len = 0; - size_t right_flank_len = 0; - RepeatAlign::Type read_type; - - RepeatAlign ra; - ASSERT_TRUE(IsSpanningOrFlankingRead(units_shifts, min_baseq, min_wp_score, - left_flank, right_flank, bases, quals, - &ra)); - ASSERT_EQ(ra.type, RepeatAlign::Type::kFlanking); - EXPECT_EQ(ra.left_flank_len, 0); - EXPECT_EQ(ra.right_flank_len, 7); - EXPECT_EQ(ra.size, 2); -} - -TEST(IsSpanningOrFlankingReadRc, ReverseComplimentedSpanningRead_Detected) { - // ------RRRRRR--- - const string bases = "CCCATATATCGCGCG"; - const string quals = "QQQQQQQQQQQQ((("; - const string left_flank = "CCGCGCGCGCGCGCG"; - const string right_flank = "GGGGGGGGGGGGGGG"; - const vector units = {"AT"}; - const vector> units_shifts = shift_units(units); - - const size_t min_baseq = 20; - const double min_wp_score = 0.9; - - RepeatAlign ra; - - ASSERT_TRUE(IsSpanningOrFlankingReadRc(units_shifts, min_baseq, min_wp_score, - left_flank, right_flank, bases, quals, - &ra)); - ASSERT_EQ(ra.read.bases, "CGCGCGATATATGGG"); - ASSERT_EQ(ra.read.quals, "(((QQQQQQQQQQQQ"); - ASSERT_EQ(ra.type, RepeatAlign::Type::kSpanning); - EXPECT_EQ(ra.left_flank_len, 6); - EXPECT_EQ(ra.right_flank_len, 3); -} - -TEST(IsSpanningOrFlankingReadNew, RealCagRepeatWithRightFlankProblem_Detected) { - // -----------------RRRRRRRRR----------------- - const string bases = "AGTCCCTCAAGTCCTTCCAGCAGCAGCAACAGCCGCCGCCGCC"; - const string quals = "QQQQQQQQQ(QQQQQ(QQQQQQQQQQQQQQQQQQQQQQQQQQQ"; - const string left_flank = "CCTGGAAAAGCTGATGAAGGCCTTCGAGTCCCTCAAGTCCTTC"; - const string right_flank = "CAACAGCCGCCACCGCCGCCGCCGCCGCCGCCGCCTCCTCAG"; - const vector units = {"CAG"}; - const vector> units_shifts = shift_units(units); - - const size_t min_baseq = 20; - const double min_wp_score = 0.9; - - RepeatAlign ra; - - ASSERT_TRUE(IsSpanningOrFlankingRead(units_shifts, min_baseq, min_wp_score, - left_flank, right_flank, bases, quals, - &ra)); - ASSERT_EQ(ra.type, RepeatAlign::Type::kSpanning); - EXPECT_EQ(ra.left_flank_len, 17); - EXPECT_EQ(ra.right_flank_len, 17); -} */ \ No newline at end of file diff --git a/sample_analysis/CMakeLists.txt b/sample_analysis/CMakeLists.txt new file mode 100755 index 0000000..182607d --- /dev/null +++ b/sample_analysis/CMakeLists.txt @@ -0,0 +1,3 @@ +file(GLOB SOURCES "*.cpp") +add_library(sample_analysis ${SOURCES}) +target_link_libraries(sample_analysis region_analysis common) diff --git a/sample_analysis/HtsFileSeeker.cpp b/sample_analysis/HtsFileSeeker.cpp new file mode 100755 index 0000000..dcd3426 --- /dev/null +++ b/sample_analysis/HtsFileSeeker.cpp @@ -0,0 +1,167 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "sample_analysis/HtsFileSeeker.hh" + +#include +#include + +#include "sample_analysis/HtsHelpers.hh" + +using std::string; + +namespace ehunter +{ + +namespace htshelpers +{ + + HtsFileSeeker::HtsFileSeeker(const string& htsFilePath) + : htsFilePath_(htsFilePath) + { + openFile(); + loadHeader(); + loadIndex(); + htsAlignmentPtr_ = bam_init1(); + } + + HtsFileSeeker::~HtsFileSeeker() + { + bam_destroy1(htsAlignmentPtr_); + htsAlignmentPtr_ = nullptr; + + if (htsRegionPtr_) + { + hts_itr_destroy(htsRegionPtr_); + htsRegionPtr_ = nullptr; + } + + hts_idx_destroy(htsIndexPtr_); + htsIndexPtr_ = nullptr; + + bam_hdr_destroy(htsHeaderPtr_); + htsHeaderPtr_ = nullptr; + + sam_close(htsFilePtr_); + htsFilePtr_ = nullptr; + } + + void HtsFileSeeker::openFile() + { + htsFilePtr_ = sam_open(htsFilePath_.c_str(), "r"); + + if (!htsFilePtr_) + { + throw std::runtime_error("Failed to read BAM file " + htsFilePath_); + } + } + + void HtsFileSeeker::loadHeader() + { + htsHeaderPtr_ = sam_hdr_read(htsFilePtr_); + + if (!htsHeaderPtr_) + { + throw std::runtime_error("Failed to read header of " + htsFilePath_); + } + + const int32_t numContigs = htsHeaderPtr_->n_targets; + + for (int32_t contigInd = 0; contigInd != numContigs; ++contigInd) + { + const string contig = htsHeaderPtr_->target_name[contigInd]; + contigNames_.push_back(contig); + } + } + + void HtsFileSeeker::loadIndex() + { + htsIndexPtr_ = sam_index_load(htsFilePtr_, htsFilePath_.c_str()); + + if (!htsIndexPtr_) + { + throw std::runtime_error("Failed to read index of " + htsFilePath_); + } + } + + void HtsFileSeeker::closeRegion() + { + if (htsRegionPtr_) + { + hts_itr_destroy(htsRegionPtr_); + htsRegionPtr_ = nullptr; + } + } + + void HtsFileSeeker::setRegion(const Region& region) + { + closeRegion(); + + const string regionEncoding = region.ToString(); + htsRegionPtr_ = sam_itr_querys(htsIndexPtr_, htsHeaderPtr_, regionEncoding.c_str()); + + if (htsRegionPtr_ == nullptr) + { + throw std::runtime_error("Failed to extract reads from " + regionEncoding); + } + + status_ = Status::kStreamingReads; + } + + bool HtsFileSeeker::trySeekingToNextPrimaryAlignment() + { + if (status_ != Status::kStreamingReads) + { + return false; + } + + int32_t returnCode = 0; + + while ((returnCode = sam_itr_next(htsFilePtr_, htsRegionPtr_, htsAlignmentPtr_)) >= 0) + { + const bool isPrimaryAlignment = !(htsAlignmentPtr_->core.flag & htshelpers::kIsNotPrimaryLine); + + if (isPrimaryAlignment) + { + return true; + } + } + + status_ = Status::kFinishedStreaming; + + if (returnCode < -1) + { + throw std::runtime_error("Failed to extract a record from " + htsFilePath_); + } + + return false; + } + + reads::Read HtsFileSeeker::decodeRead(reads::LinearAlignmentStats& alignmentStats) const + { + reads::Read read; + DecodeAlignedRead(htsAlignmentPtr_, read, alignmentStats); + + return read; + } + +} + +} diff --git a/sample_analysis/HtsFileSeeker.hh b/sample_analysis/HtsFileSeeker.hh new file mode 100755 index 0000000..044cfd8 --- /dev/null +++ b/sample_analysis/HtsFileSeeker.hh @@ -0,0 +1,83 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include + +extern "C" +{ +#include "htslib/hts.h" +#include "htslib/sam.h" +} + +#include "common/GenomicRegion.hh" +#include "reads/Read.hh" + +namespace ehunter +{ + +namespace htshelpers +{ + + class HtsFileSeeker + { + public: + HtsFileSeeker(const std::string& htsFilePath); + ~HtsFileSeeker(); + void setRegion(const Region& region); + bool trySeekingToNextPrimaryAlignment(); + + int32_t currentReadChromIndex() const; + const std::string& currentReadChrom() const; + int32_t currentReadPosition() const; + int32_t currentMateChromIndex() const; + const std::string& currentMateChrom() const; + int32_t currentMatePosition() const; + + reads::Read decodeRead(reads::LinearAlignmentStats& alignmentStats) const; + + private: + enum class Status + { + kStreamingReads, + kFinishedStreaming + }; + + void openFile(); + void loadHeader(); + void loadIndex(); + void closeRegion(); + + const std::string htsFilePath_; + std::vector contigNames_; + Status status_ = Status::kFinishedStreaming; + + htsFile* htsFilePtr_ = nullptr; + bam_hdr_t* htsHeaderPtr_ = nullptr; + hts_idx_t* htsIndexPtr_ = nullptr; + hts_itr_t* htsRegionPtr_ = nullptr; + bam1_t* htsAlignmentPtr_ = nullptr; + }; + +} + +} \ No newline at end of file diff --git a/sample_analysis/HtsFileStreamer.cpp b/sample_analysis/HtsFileStreamer.cpp new file mode 100755 index 0000000..c9ec084 --- /dev/null +++ b/sample_analysis/HtsFileStreamer.cpp @@ -0,0 +1,128 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "sample_analysis/HtsFileStreamer.hh" + +#include "sample_analysis/HtsHelpers.hh" + +using std::string; + +namespace ehunter +{ + +namespace htshelpers +{ + + void HtsFileStreamer::openHtsFile() + { + htsFilePtr_ = sam_open(htsFilePath_.c_str(), "r"); + + if (!htsFilePtr_) + { + throw std::runtime_error("Failed to read BAM file " + htsFilePath_); + } + } + + void HtsFileStreamer::loadHeader() + { + htsHeaderPtr_ = sam_hdr_read(htsFilePtr_); + + if (!htsHeaderPtr_) + { + throw std::runtime_error("Failed to read header of " + htsFilePath_); + } + + const int32_t numChroms = htsHeaderPtr_->n_targets; + + for (int32_t chromInd = 0; chromInd != numChroms; ++chromInd) + { + const string chrom = htsHeaderPtr_->target_name[chromInd]; + chromNames_.push_back(chrom); + } + } + + void HtsFileStreamer::prepareForStreamingAlignments() { htsAlignmentPtr_ = bam_init1(); } + + bool HtsFileStreamer::trySeekingToNextPrimaryAlignment() + { + if (status_ != Status::kStreamingReads) + { + return false; + } + + int32_t returnCode = 0; + + while ((returnCode = sam_read1(htsFilePtr_, htsHeaderPtr_, htsAlignmentPtr_)) >= 0) + { + const bool isPrimaryAlignment = !(htsAlignmentPtr_->core.flag & htshelpers::kIsNotPrimaryLine); + + if (isPrimaryAlignment) + { + return true; + } + } + + status_ = Status::kFinishedStreaming; + + if (returnCode < -1) + { + throw std::runtime_error("Failed to extract a record from " + htsFilePath_); + } + + return false; + } + + int32_t HtsFileStreamer::currentReadChromIndex() const { return htsAlignmentPtr_->core.tid; } + const std::string& HtsFileStreamer::currentReadChrom() const { return chromNames_[currentReadChromIndex()]; } + int32_t HtsFileStreamer::currentReadPosition() const { return htsAlignmentPtr_->core.pos; } + + int32_t HtsFileStreamer::currentMateChromIndex() const { return htsAlignmentPtr_->core.mtid; } + const std::string& HtsFileStreamer::currentMateChrom() const { return chromNames_[currentMateChromIndex()]; } + int32_t HtsFileStreamer::currentMatePosition() const { return htsAlignmentPtr_->core.mpos; } + + bool HtsFileStreamer::isStreamingAlignedReads() const + { + return status_ != Status::kFinishedStreaming && currentReadChromIndex() != -1; + } + + reads::Read HtsFileStreamer::decodeRead() const + { + reads::Read read; + reads::LinearAlignmentStats alignmentStats; + DecodeAlignedRead(htsAlignmentPtr_, read, alignmentStats); + + return read; + } + + HtsFileStreamer::~HtsFileStreamer() + { + bam_destroy1(htsAlignmentPtr_); + htsAlignmentPtr_ = nullptr; + + bam_hdr_destroy(htsHeaderPtr_); + htsHeaderPtr_ = nullptr; + + sam_close(htsFilePtr_); + htsFilePtr_ = nullptr; + } + +} + +} diff --git a/sample_analysis/HtsFileStreamer.hh b/sample_analysis/HtsFileStreamer.hh new file mode 100755 index 0000000..d612fa9 --- /dev/null +++ b/sample_analysis/HtsFileStreamer.hh @@ -0,0 +1,86 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include + +extern "C" +{ +#include "htslib/hts.h" +#include "htslib/sam.h" +} + +#include "reads/Read.hh" + +namespace ehunter +{ + +namespace htshelpers +{ + + class HtsFileStreamer + { + public: + HtsFileStreamer(const std::string& htsFilePath) + : htsFilePath_(htsFilePath) + { + openHtsFile(); + loadHeader(); + prepareForStreamingAlignments(); + } + ~HtsFileStreamer(); + + bool trySeekingToNextPrimaryAlignment(); + + int32_t currentReadChromIndex() const; + const std::string& currentReadChrom() const; + int32_t currentReadPosition() const; + int32_t currentMateChromIndex() const; + const std::string& currentMateChrom() const; + int32_t currentMatePosition() const; + + bool isStreamingAlignedReads() const; + + reads::Read decodeRead() const; + + private: + enum class Status + { + kStreamingReads, + kFinishedStreaming + }; + + void openHtsFile(); + void loadHeader(); + void prepareForStreamingAlignments(); + + const std::string htsFilePath_; + std::vector chromNames_; + Status status_ = Status::kStreamingReads; + + htsFile* htsFilePtr_ = nullptr; + bam1_t* htsAlignmentPtr_ = nullptr; + bam_hdr_t* htsHeaderPtr_ = nullptr; + }; + +} +} diff --git a/sample_analysis/HtsHelpers.cpp b/sample_analysis/HtsHelpers.cpp new file mode 100755 index 0000000..4885a5c --- /dev/null +++ b/sample_analysis/HtsHelpers.cpp @@ -0,0 +1,113 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "sample_analysis/HtsHelpers.hh" + +#include +#include +#include +#include +#include + +#include "thirdparty/spdlog/spdlog.h" + +#include "common/SequenceOperations.hh" + +using std::string; + +namespace spd = spdlog; + +namespace ehunter +{ + +namespace htshelpers +{ + + void DecodeQuals(bam1_t* hts_align_ptr, string& quals) + { + uint8_t* hts_quals_ptr = bam_get_qual(hts_align_ptr); + const int32_t readLen = hts_align_ptr->core.l_qseq; + quals.resize(readLen); + + uint8_t* test_hts_quals_ptr = hts_quals_ptr; + + for (int32_t index = 0; index < readLen; ++index) + { + quals[index] = static_cast(33 + test_hts_quals_ptr[index]); + } + } + + void DecodeBases(bam1_t* hts_align_ptr, string& bases) + { + uint8_t* hts_seq_ptr = bam_get_seq(hts_align_ptr); + const int32_t readLen = hts_align_ptr->core.l_qseq; + bases.resize(readLen); + + for (int32_t index = 0; index < readLen; ++index) + { + bases[index] = seq_nt16_str[bam_seqi(hts_seq_ptr, index)]; + } + } + + void DecodeAlignedRead(bam1_t* hts_align_ptr, reads::Read& read, reads::LinearAlignmentStats& alignment_stats) + { + alignment_stats.chrom_id = hts_align_ptr->core.tid; + alignment_stats.pos = hts_align_ptr->core.pos; + alignment_stats.mapq = hts_align_ptr->core.qual; + alignment_stats.mate_chrom_id = hts_align_ptr->core.mtid; + alignment_stats.mate_pos = hts_align_ptr->core.mpos; + + uint32_t sam_flag = hts_align_ptr->core.flag; + alignment_stats.is_mapped = !(sam_flag & SamFlags::kIsUnmapped); + alignment_stats.is_mate_mapped = !(sam_flag & SamFlags::kIsMateUnmapped); + + read.is_first_mate = sam_flag & SamFlags::kIsFirstMate; + + const string fragment_id = bam_get_qname(hts_align_ptr); + read.read_id = fragment_id + "/" + (read.is_first_mate ? "1" : "2"); + + string bases; + DecodeBases(hts_align_ptr, bases); + string quals; + DecodeQuals(hts_align_ptr, quals); + + read.sequence = lowercaseLowQualityBases(bases, quals); + } + + void DecodeUnalignedRead(bam1_t* hts_align_ptr, reads::Read& read) + { + const uint32_t sam_flag = hts_align_ptr->core.flag; + read.is_first_mate = sam_flag & SamFlags::kIsFirstMate; + + const string fragment_id = bam_get_qname(hts_align_ptr); + read.read_id = fragment_id + "/" + (read.is_first_mate ? "1" : "2"); + + string bases; + DecodeBases(hts_align_ptr, bases); + + string quals; + DecodeQuals(hts_align_ptr, quals); + + read.sequence = lowercaseLowQualityBases(bases, quals); + } + +} // namespace htshelpers + +} diff --git a/include/bam_index.h b/sample_analysis/HtsHelpers.hh old mode 100644 new mode 100755 similarity index 53% rename from include/bam_index.h rename to sample_analysis/HtsHelpers.hh index ff8d96b..4600816 --- a/include/bam_index.h +++ b/sample_analysis/HtsHelpers.hh @@ -2,9 +2,7 @@ // Expansion Hunter // Copyright (c) 2016 Illumina, Inc. // -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle +// Author: Egor Dolzhenko // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -22,25 +20,34 @@ #pragma once -#include -#include - +extern "C" +{ #include "htslib/hts.h" +#include "htslib/sam.h" +} + +#include "reads/Read.hh" + +namespace ehunter +{ + +namespace htshelpers +{ -namespace ehunter { + enum SamFlags + { + kIsUnmapped = 0x4, + kIsMateUnmapped = 0x8, + kIsFirstMate = 0x40, + kIsSecondMate = 0x80, + // kSecondaryAlign = 0x100, + // kSupplementaryAlignment = 0x800, + kIsNotPrimaryLine = 0x900 + }; -class BamIndex { - public: - BamIndex(const std::string& bam_path); - ~BamIndex(); + void DecodeAlignedRead(bam1_t* hts_align_ptr, reads::Read& read, reads::LinearAlignmentStats& alignment_stats); + void DecodeUnalignedRead(bam1_t* hts_align_ptr, reads::Read& read); - bool GetChrReadCounts(std::vector& chrom_names, - std::vector& chrom_lens, - std::vector& mapped_read_counts, - std::vector& unmapped_read_counts) const; +} // namespace htshelpers - private: - htsFile* hts_file_ptr_; - std::string bam_path_; -}; -} // namespace ehunter +} diff --git a/sample_analysis/HtsSeekingSampleAnalyzer.cpp b/sample_analysis/HtsSeekingSampleAnalyzer.cpp new file mode 100755 index 0000000..50e5267 --- /dev/null +++ b/sample_analysis/HtsSeekingSampleAnalyzer.cpp @@ -0,0 +1,203 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "sample_analysis/HtsSeekingSampleAnalyzer.hh" + +#include +#include +#include +#include + +#include + +#include "thirdparty/spdlog/spdlog.h" + +#include "reads/ReadPairs.hh" +#include "region_analysis/RegionAnalyzer.hh" +#include "sample_analysis/HtsFileSeeker.hh" +#include "sample_analysis/IndexBasedDepthEstimate.hh" +#include "sample_analysis/MateExtractor.hh" + +namespace ehunter +{ + +using boost::optional; +using htshelpers::HtsFileSeeker; +using reads::LinearAlignmentStats; +using reads::Read; +using reads::ReadPairs; +using std::ostream; +using std::string; +using std::unordered_map; +using std::vector; + +using AlignmentStatsCatalog = unordered_map; + +static ReadPairs +collectReads(const vector& regions, AlignmentStatsCatalog& alignmentStatsCatalog, HtsFileSeeker& fileHopper) +{ + ReadPairs readPairs; + auto console = spdlog::get("console") ? spdlog::get("console") : spdlog::stderr_color_mt("console"); + int preReads = 0; + for (const auto& region : regions) + { + fileHopper.setRegion(region); + while (fileHopper.trySeekingToNextPrimaryAlignment()) + { + LinearAlignmentStats alignmentStats; + Read read = fileHopper.decodeRead(alignmentStats); + alignmentStatsCatalog.emplace(std::make_pair(read.readId(), alignmentStats)); + readPairs.Add(std::move(read)); + } + console->info("Collected {} reads from {}", readPairs.NumReads() - preReads, region); + preReads += readPairs.NumReads(); + } + return readPairs; +} + +bool checkIfMatesWereMappedNearby(const LinearAlignmentStats& alignmentStats) +{ + const int maxMateDistance = 1000; + if ((alignmentStats.chrom_id == alignmentStats.mate_chrom_id) + && (std::abs(alignmentStats.pos - alignmentStats.mate_pos) < maxMateDistance)) + { + return true; + } + return false; +} + +void recoverMates(const string& htsFilePath, const AlignmentStatsCatalog& alignmentStatsCatalog, ReadPairs& readPairs) +{ + htshelpers::MateExtractor mateExtractor(htsFilePath); + + for (auto& fragmentIdAndReadPair : readPairs) + { + reads::ReadPair& readPair = fragmentIdAndReadPair.second; + + if (readPair.first_mate.isSet() && readPair.second_mate.isSet()) + { + continue; + } + + assert(readPair.first_mate.isSet() || readPair.second_mate.isSet()); + const Read& read = readPair.first_mate.isSet() ? readPair.first_mate : readPair.second_mate; + + const auto alignmentStatsIterator = alignmentStatsCatalog.find(read.readId()); + if (alignmentStatsIterator == alignmentStatsCatalog.end()) + { + throw std::logic_error("Cannot recover mate of uncatalogued read"); + } + const LinearAlignmentStats& alignmentStats = alignmentStatsIterator->second; + + if (!checkIfMatesWereMappedNearby(alignmentStats)) + { + Read mate = mateExtractor.extractMate(read, alignmentStats); + if (mate.isSet()) + { + readPairs.AddMateToExistingRead(mate); + } + else + { + auto console = spdlog::get("console") ? spdlog::get("console") : spdlog::stderr_color_mt("console"); + console->warn("Could not recover the mate of {}", read.readId()); + } + } + } +} + +static RegionFindings analyzeRegion( + const ReadPairs& readPairs, const ReadPairs& offtargetReadPairs, const LocusSpecification& regionSpec, + const SampleParameters& sampleParams, const HeuristicParameters& heuristicParams, ostream& alignmentStream) +{ + alignmentStream << regionSpec.regionId() << ":" << std::endl; + RegionAnalyzer regionAnalyzer(regionSpec, sampleParams, heuristicParams, alignmentStream); + + for (const auto fragmentIdAndReads : readPairs) + { + const auto& readPair = fragmentIdAndReads.second; + if (readPair.first_mate.isSet() && readPair.second_mate.isSet()) + { + regionAnalyzer.processMates(readPair.first_mate, readPair.second_mate); + } + } + + for (const auto fragmentIdAndReads : offtargetReadPairs) + { + const auto& readPair = fragmentIdAndReads.second; + if (readPair.first_mate.isSet() && readPair.second_mate.isSet()) + { + regionAnalyzer.processOfftargetMates(readPair.first_mate, readPair.second_mate); + } + } + + return regionAnalyzer.genotype(); +} + +SampleFindings htsSeekingSampleAnalysis( + const InputPaths& inputPaths, SampleParameters& sampleParams, const HeuristicParameters& heuristicParams, + const RegionCatalog& regionCatalog, std::ostream& alignmentStream) +{ + auto console = spdlog::get("console") ? spdlog::get("console") : spdlog::stderr_color_mt("console"); + + if (!sampleParams.isHaplotypeDepthSet()) + { + const double depth = estimateDepthFromHtsIndex(inputPaths.htsFile(), sampleParams.readLength()); + + const double kMinDepthAllowed = 10.0; + if (depth < kMinDepthAllowed) + { + throw std::invalid_argument("Read depth must be at least " + std::to_string(kMinDepthAllowed)); + } + + sampleParams.setHaplotypeDepth(depth / 2); + console->info("Depth is set to {}", depth); + } + + HtsFileSeeker htsFileSeeker(inputPaths.htsFile()); + + SampleFindings sampleFindings; + for (const auto& regionIdAndRegionSpec : regionCatalog) + { + const string& regionId = regionIdAndRegionSpec.first; + const LocusSpecification& regionSpec = regionIdAndRegionSpec.second; + vector targetRegions; + const auto& referenceLoci = regionSpec.referenceLoci(); + auto extendRegion = [=](Region region) { return region.extend(heuristicParams.regionExtensionLength()); }; + std::transform(referenceLoci.begin(), referenceLoci.end(), std::back_inserter(targetRegions), extendRegion); + AlignmentStatsCatalog readAlignmentStats; + ReadPairs targetReadPairs = collectReads(targetRegions, readAlignmentStats, htsFileSeeker); + recoverMates(inputPaths.htsFile(), readAlignmentStats, targetReadPairs); + console->info("Collected {} read pairs from target regions", targetReadPairs.NumCompletePairs()); + + AlignmentStatsCatalog offtargetReadAlignmentStatsCatalog; + ReadPairs offtargetReadPairs + = collectReads(regionSpec.offtargetLoci(), offtargetReadAlignmentStatsCatalog, htsFileSeeker); + recoverMates(inputPaths.htsFile(), offtargetReadAlignmentStatsCatalog, offtargetReadPairs); + console->info("Collected {} read pairs from offtarget regions", offtargetReadPairs.NumCompletePairs()); + + auto regionFindings = analyzeRegion( + targetReadPairs, offtargetReadPairs, regionSpec, sampleParams, heuristicParams, alignmentStream); + sampleFindings.emplace(std::make_pair(regionId, std::move(regionFindings))); + } + + return sampleFindings; +} + +} diff --git a/sample_analysis/HtsSeekingSampleAnalyzer.hh b/sample_analysis/HtsSeekingSampleAnalyzer.hh new file mode 100755 index 0000000..2371196 --- /dev/null +++ b/sample_analysis/HtsSeekingSampleAnalyzer.hh @@ -0,0 +1,37 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include + +#include "common/Parameters.hh" +#include "region_analysis/VariantFindings.hh" +#include "region_spec/LocusSpecification.hh" + +namespace ehunter +{ + +SampleFindings htsSeekingSampleAnalysis( + const InputPaths& inputPaths, SampleParameters& sampleParams, const HeuristicParameters& heuristicParams, + const RegionCatalog& regionCatalog, std::ostream& alignmentStream); + +} diff --git a/sample_analysis/HtsStreamingSampleAnalyzer.cpp b/sample_analysis/HtsStreamingSampleAnalyzer.cpp new file mode 100755 index 0000000..bb2b042 --- /dev/null +++ b/sample_analysis/HtsStreamingSampleAnalyzer.cpp @@ -0,0 +1,66 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "sample_analysis/HtsStreamingSampleAnalyzer.hh" + +#include +#include + +#include "region_analysis/RegionAnalyzer.hh" +#include "sample_analysis/HtsFileStreamer.hh" +#include "sample_analysis/HtsHelpers.hh" +#include "sample_analysis/LocationBasedDispatcher.hh" + +using std::map; +using std::string; +using std::vector; + +using std::unordered_map; + +namespace ehunter +{ + +SampleFindings htslibStreamingSampleAnalyzer( + const InputPaths& inputPaths, const SampleParameters& sampleParams, const HeuristicParameters& heuristicParams, + const RegionCatalog& regionCatalog, std::ostream& alignmentStream) +{ + vector> locusAnalyzers + = initializeRegionAnalyzers(regionCatalog, sampleParams, heuristicParams, alignmentStream); + LocationBasedDispatcher locationBasedDispatcher(locusAnalyzers, heuristicParams.regionExtensionLength()); + + htshelpers::HtsFileStreamer readStreamer(inputPaths.htsFile()); + while (readStreamer.trySeekingToNextPrimaryAlignment() && readStreamer.isStreamingAlignedReads()) + { + locationBasedDispatcher.dispatch( + readStreamer.currentReadChrom(), readStreamer.currentReadPosition(), readStreamer.currentMateChrom(), + readStreamer.currentMatePosition(), readStreamer.decodeRead()); + } + + SampleFindings sampleFindings; + for (auto& locusAnalyzer : locusAnalyzers) + { + auto locusFindings = locusAnalyzer->genotype(); + sampleFindings.emplace(std::make_pair(locusAnalyzer->regionId(), std::move(locusFindings))); + } + + return sampleFindings; +} + +} diff --git a/sample_analysis/HtsStreamingSampleAnalyzer.hh b/sample_analysis/HtsStreamingSampleAnalyzer.hh new file mode 100755 index 0000000..667a12d --- /dev/null +++ b/sample_analysis/HtsStreamingSampleAnalyzer.hh @@ -0,0 +1,38 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include + +#include "common/Parameters.hh" +#include "region_analysis/VariantFindings.hh" +#include "region_spec/LocusSpecification.hh" + +namespace ehunter +{ + +SampleFindings htslibStreamingSampleAnalyzer( + const InputPaths& inputPaths, const SampleParameters& sampleParams, const HeuristicParameters& heuristicParams, + const RegionCatalog& regionCatalog, std::ostream& alignmentStream); + +} diff --git a/sample_analysis/IndexBasedDepthEstimate.cpp b/sample_analysis/IndexBasedDepthEstimate.cpp new file mode 100755 index 0000000..07d808e --- /dev/null +++ b/sample_analysis/IndexBasedDepthEstimate.cpp @@ -0,0 +1,101 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "sample_analysis/IndexBasedDepthEstimate.hh" + +#include +#include +#include + +#include +#include +#include + +extern "C" +{ +#include "htslib/hts.h" +#include "htslib/sam.h" +} + +using std::string; +using std::unordered_set; +using namespace boost::accumulators; + +namespace ehunter +{ + +static bool isAutosome(const string& contigName) +{ + static unordered_set autosomeNames + = { "chr1", "chr2", "chr3", "chr4", "chr5", "chr6", "chr7", "chr8", "chr9", "chr10", "chr11", + "chr12", "chr13", "chr14", "chr15", "chr16", "chr17", "chr18", "chr19", "chr20", "chr21", "chr22", + "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", + "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22" }; + + auto contigNameIter = autosomeNames.find(contigName); + if (contigNameIter != autosomeNames.end()) + { + return true; + } + + return false; +} + +double estimateDepthFromHtsIndex(const std::string& htsFilePath, int readLength) +{ + htsFile* htsFilePtr = sam_open(htsFilePath.c_str(), "r"); + if (!htsFilePtr) + { + throw std::runtime_error("Failed to open HTS file " + htsFilePath); + } + + bam_hdr_t* htsHeaderPtr = sam_hdr_read(htsFilePtr); + if (!htsHeaderPtr) + { + throw std::runtime_error("Failed to load header of " + htsFilePath); + } + + hts_idx_t* htsIndexPtr = sam_index_load(htsFilePtr, htsFilePath.c_str()); + if (!htsIndexPtr) + { + throw std::runtime_error("Failed to load index of " + htsFilePath); + } + + const int numContigs = htsHeaderPtr->n_targets; + + accumulator_set> contigDepths; + + for (int contigIndex = 0; contigIndex != numContigs; ++contigIndex) + { + const auto contigName = htsHeaderPtr->target_name[contigIndex]; + const int64_t contigLength = htsHeaderPtr->target_len[contigIndex]; + uint64_t numMappedReads, numUnmappedReads; + hts_idx_get_stat(htsIndexPtr, contigIndex, &numMappedReads, &numUnmappedReads); + if (isAutosome(contigName)) + { + const double contigDepth = (readLength * numMappedReads) / static_cast(contigLength); + contigDepths(contigDepth); + } + } + + return median(contigDepths); +} + +} diff --git a/sample_analysis/IndexBasedDepthEstimate.hh b/sample_analysis/IndexBasedDepthEstimate.hh new file mode 100755 index 0000000..54115cf --- /dev/null +++ b/sample_analysis/IndexBasedDepthEstimate.hh @@ -0,0 +1,30 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include + +namespace ehunter +{ + +double estimateDepthFromHtsIndex(const std::string& htsFilePath, int readLength); + +} diff --git a/sample_analysis/LocationBasedAnalyzerFinder.cpp b/sample_analysis/LocationBasedAnalyzerFinder.cpp new file mode 100755 index 0000000..475a0a3 --- /dev/null +++ b/sample_analysis/LocationBasedAnalyzerFinder.cpp @@ -0,0 +1,121 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "sample_analysis/LocationBasedAnalyzerFinder.hh" + +#include +#include + +using boost::optional; +using std::map; +using std::string; +using std::unordered_map; +using std::vector; + +namespace ehunter +{ + +LocationBasedAnalyzerFinder::LocationBasedAnalyzerFinder( + vector>& locusAnalyzers, int searchRadius) +{ + unordered_map> contigToIntervals; + for (auto& locusAnalyzer : locusAnalyzers) + { + const LocusSpecification& locusSpec = locusAnalyzer->regionSpec(); + for (auto& refRegion : locusSpec.referenceLoci()) + { + auto targetRegion = refRegion.extend(searchRadius); + LocusTypeAndAnalyzer payload(LocusType::kTargetLocus, locusAnalyzer.get()); + contigToIntervals[targetRegion.chrom()].emplace_back(targetRegion.start(), targetRegion.end(), payload); + } + for (const auto& offtargetLocus : locusSpec.offtargetLoci()) + { + LocusTypeAndAnalyzer payload(LocusType::kOfftargetLocus, locusAnalyzer.get()); + contigToIntervals[offtargetLocus.chrom()].emplace_back( + offtargetLocus.start(), offtargetLocus.end(), payload); + } + } + + for (auto& contigAndIntervals : contigToIntervals) + { + const string& contig = contigAndIntervals.first; + auto intervals = contigAndIntervals.second; + intervalTrees_.emplace(std::make_pair(contig, AnalyzerIntervalTree(std::move(intervals)))); + } +} + +optional LocationBasedAnalyzerFinder::query( + const string& readChrom, int32_t readPosition, const string& mateChrom, int32_t matePosition) +{ + auto optionalReadLocusTypeAndAnalyzer = tryGettingLocusAnalyzer(readChrom, readPosition); + auto optionalMateLocusTypeAndAnalyzer = tryGettingLocusAnalyzer(mateChrom, matePosition); + + if (optionalReadLocusTypeAndAnalyzer && optionalReadLocusTypeAndAnalyzer->locusType == LocusType::kTargetLocus) + { + return optionalReadLocusTypeAndAnalyzer; + } + + if (optionalMateLocusTypeAndAnalyzer && optionalMateLocusTypeAndAnalyzer->locusType == LocusType::kTargetLocus) + { + return optionalMateLocusTypeAndAnalyzer; + } + + if (optionalReadLocusTypeAndAnalyzer) + { + return optionalReadLocusTypeAndAnalyzer; + } + + if (optionalMateLocusTypeAndAnalyzer) + { + return optionalMateLocusTypeAndAnalyzer; + } + + return optional(); +} + +optional +LocationBasedAnalyzerFinder::tryGettingLocusAnalyzer(const string& readChrom, int32_t readPosition) const +{ + const auto contigTreeIterator = intervalTrees_.find(readChrom); + + if (contigTreeIterator == intervalTrees_.end()) + { + return optional(); + } + + const vector& relevantRegionAnalyzers + = contigTreeIterator->second.findOverlapping(readPosition, readPosition + 1); + + if (relevantRegionAnalyzers.size() > 1) + { + throw std::logic_error("Repeat catalog must contain non-overlapping regions"); + } + + if (!relevantRegionAnalyzers.empty()) + { + return relevantRegionAnalyzers.front().value; + } + else + { + return optional(); + } +} + +} diff --git a/sample_analysis/LocationBasedAnalyzerFinder.hh b/sample_analysis/LocationBasedAnalyzerFinder.hh new file mode 100755 index 0000000..67ec02f --- /dev/null +++ b/sample_analysis/LocationBasedAnalyzerFinder.hh @@ -0,0 +1,72 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include +#include + +#include + +#include "thirdparty/intervaltree/IntervalTree.h" + +#include "reads/Read.hh" +#include "region_analysis/RegionAnalyzer.hh" + +namespace ehunter +{ + +enum class LocusType +{ + kOfftargetLocus, + kTargetLocus +}; + +struct LocusTypeAndAnalyzer +{ + LocusTypeAndAnalyzer(LocusType locusType, RegionAnalyzer* locusAnalyzerPtr) + : locusType(locusType) + , locusAnalyzerPtr(locusAnalyzerPtr) + { + } + LocusType locusType; + RegionAnalyzer* locusAnalyzerPtr; +}; + +using IntervalWithLocusTypeAndAnalyzer = Interval; +using AnalyzerIntervalTree = IntervalTree; +using AnalyzerIntervalTrees = std::unordered_map; + +class LocationBasedAnalyzerFinder +{ +public: + LocationBasedAnalyzerFinder(std::vector>& locusAnalyzers, int searchRadius); + boost::optional + query(const std::string& readChrom, int32_t readPosition, const std::string& mateChrom, int32_t matePosition); + +private: + boost::optional + tryGettingLocusAnalyzer(const std::string& readChrom, int32_t readPosition) const; + + AnalyzerIntervalTrees intervalTrees_; +}; + +} diff --git a/sample_analysis/LocationBasedDispatcher.cpp b/sample_analysis/LocationBasedDispatcher.cpp new file mode 100755 index 0000000..369988a --- /dev/null +++ b/sample_analysis/LocationBasedDispatcher.cpp @@ -0,0 +1,71 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "sample_analysis/LocationBasedDispatcher.hh" + +using std::string; +using std::vector; + +namespace ehunter +{ + +LocationBasedDispatcher::LocationBasedDispatcher( + std::vector>& locusAnalyzers, int searchRadius) + : locationBasedAnalyzerFinder_(locusAnalyzers, searchRadius) +{ +} + +void LocationBasedDispatcher::dispatch( + const std::string& readChrom, int32_t readPosition, const std::string& mateChrom, int32_t matePosition, + reads::Read read) +{ + // Check if the read is in the hash and act accordingly + const auto mateIterator = unpairedReads_.find(read.fragmentId()); + if (mateIterator == unpairedReads_.end()) + { + unpairedReads_.emplace(std::make_pair(read.fragmentId(), std::move(read))); + return; + } + + reads::Read& mate = mateIterator->second; + auto optionalRegionTypeAndAnalyzer + = locationBasedAnalyzerFinder_.query(readChrom, readPosition, mateChrom, matePosition); + + if (optionalRegionTypeAndAnalyzer) + { + RegionAnalyzer& regionAnalyzer = *optionalRegionTypeAndAnalyzer->locusAnalyzerPtr; + string fragmentId = mate.fragmentId(); + if (optionalRegionTypeAndAnalyzer->locusType == LocusType::kTargetLocus) + { + regionAnalyzer.processMates(std::move(read), std::move(mate)); + } + else + { + regionAnalyzer.processOfftargetMates(std::move(read), std::move(mate)); + } + unpairedReads_.erase(fragmentId); + } + else + { + unpairedReads_.erase(mate.fragmentId()); + } +} + +} diff --git a/sample_analysis/LocationBasedDispatcher.hh b/sample_analysis/LocationBasedDispatcher.hh new file mode 100755 index 0000000..2b9bd44 --- /dev/null +++ b/sample_analysis/LocationBasedDispatcher.hh @@ -0,0 +1,48 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include + +#include "reads/Read.hh" +#include "region_analysis/RegionAnalyzer.hh" +#include "sample_analysis/LocationBasedAnalyzerFinder.hh" + +namespace ehunter +{ + +class LocationBasedDispatcher +{ +public: + LocationBasedDispatcher(std::vector>& locusAnalyzers, int searchRadius); + void dispatch( + const std::string& readChrom, int32_t readPosition, const std::string& mateChrom, int32_t matePosition, + reads::Read read); + +private: + LocationBasedAnalyzerFinder locationBasedAnalyzerFinder_; + + using ReadCatalog = std::unordered_map; + ReadCatalog unpairedReads_; +}; + +} diff --git a/sample_analysis/MateExtractor.cpp b/sample_analysis/MateExtractor.cpp new file mode 100755 index 0000000..7808441 --- /dev/null +++ b/sample_analysis/MateExtractor.cpp @@ -0,0 +1,139 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "sample_analysis/MateExtractor.hh" + +#include + +#include "sample_analysis/HtsHelpers.hh" + +namespace ehunter +{ + +using reads::LinearAlignmentStats; +using reads::Read; +using std::string; + +namespace htshelpers +{ + MateExtractor::MateExtractor(const string& htsFilePath) + : htsFilePath_(htsFilePath) + { + openFile(); + loadHeader(); + loadIndex(); + htsAlignmentPtr_ = bam_init1(); + } + + MateExtractor::~MateExtractor() + { + bam_destroy1(htsAlignmentPtr_); + htsAlignmentPtr_ = nullptr; + + hts_idx_destroy(htsIndexPtr_); + htsIndexPtr_ = nullptr; + + bam_hdr_destroy(htsHeaderPtr_); + htsHeaderPtr_ = nullptr; + + sam_close(htsFilePtr_); + htsFilePtr_ = nullptr; + } + + void MateExtractor::openFile() + { + htsFilePtr_ = sam_open(htsFilePath_.c_str(), "r"); + + if (!htsFilePtr_) + { + throw std::runtime_error("Failed to read BAM file " + htsFilePath_); + } + } + + void MateExtractor::loadHeader() + { + htsHeaderPtr_ = sam_hdr_read(htsFilePtr_); + + if (!htsHeaderPtr_) + { + throw std::runtime_error("Failed to read header of " + htsFilePath_); + } + + const int32_t numContigs = htsHeaderPtr_->n_targets; + + for (int32_t contigInd = 0; contigInd != numContigs; ++contigInd) + { + const string contig = htsHeaderPtr_->target_name[contigInd]; + contigNames_.push_back(contig); + } + } + + void MateExtractor::loadIndex() + { + htsIndexPtr_ = sam_index_load(htsFilePtr_, htsFilePath_.c_str()); + + if (!htsIndexPtr_) + { + throw std::runtime_error("Failed to read index of " + htsFilePath_); + } + } + + Read MateExtractor::extractMate(const Read& read, const LinearAlignmentStats& alignmentStats) + { + const int32_t searchRegionContigId + = alignmentStats.is_mate_mapped ? alignmentStats.mate_chrom_id : alignmentStats.chrom_id; + + const int32_t searchRegionStart = alignmentStats.is_mate_mapped ? alignmentStats.mate_pos : alignmentStats.pos; + const int32_t searchRegionEnd = searchRegionStart + 1; + + hts_itr_t* htsRegionPtr_ + = sam_itr_queryi(htsIndexPtr_, searchRegionContigId, searchRegionStart, searchRegionEnd); + + if (!htsRegionPtr_) + { + const string contigName = contigNames_[searchRegionContigId]; + const string regionEncoding + = contigName + ":" + std::to_string(searchRegionStart) + "-" + std::to_string(searchRegionEnd); + + throw std::logic_error("Unable to jump to " + regionEncoding + " to recover a mate"); + } + + while (sam_itr_next(htsFilePtr_, htsRegionPtr_, htsAlignmentPtr_) >= 0) + { + LinearAlignmentStats putativeMateAlignmentStats; + Read putativeMate; + htshelpers::DecodeAlignedRead(htsAlignmentPtr_, putativeMate, putativeMateAlignmentStats); + + const bool belongToSameFragment = read.fragmentId() == putativeMate.fragmentId(); + const bool formProperPair = read.is_first_mate != putativeMate.is_first_mate; + if (belongToSameFragment && formProperPair) + { + hts_itr_destroy(htsRegionPtr_); + return putativeMate; + } + } + hts_itr_destroy(htsRegionPtr_); + + return Read(); + } + +} + +} diff --git a/sample_analysis/MateExtractor.hh b/sample_analysis/MateExtractor.hh new file mode 100755 index 0000000..f82f0c5 --- /dev/null +++ b/sample_analysis/MateExtractor.hh @@ -0,0 +1,63 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include +#include + +extern "C" +{ +#include "htslib/hts.h" +#include "htslib/sam.h" +} + +#include "reads/Read.hh" + +namespace ehunter +{ + +namespace htshelpers +{ + class MateExtractor + { + public: + MateExtractor(const std::string& htsFilePath); + ~MateExtractor(); + + reads::Read extractMate(const reads::Read& read, const reads::LinearAlignmentStats& alignmentStats); + + private: + void openFile(); + void loadHeader(); + void loadIndex(); + + const std::string htsFilePath_; + std::vector contigNames_; + + htsFile* htsFilePtr_ = nullptr; + bam_hdr_t* htsHeaderPtr_ = nullptr; + hts_idx_t* htsIndexPtr_ = nullptr; + bam1_t* htsAlignmentPtr_ = nullptr; + }; + +} + +} diff --git a/src/ExpansionHunter.cpp b/src/ExpansionHunter.cpp new file mode 100755 index 0000000..6dfab5a --- /dev/null +++ b/src/ExpansionHunter.cpp @@ -0,0 +1,117 @@ +// +// Expansion Hunter +// Copyright (c) 2016 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Mitch Bekritsky , Richard Shaw +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include +#include +#include +#include +#include +#include +#include + +#include "thirdparty/spdlog/fmt/ostr.h" +#include "thirdparty/spdlog/spdlog.h" + +#include "common/Parameters.hh" +#include "input/CatalogLoading.hh" +#include "input/ParameterLoading.hh" +#include "input/SampleStats.hh" +#include "output/JsonWriter.hh" +#include "output/VcfWriter.hh" +#include "region_analysis/VariantFindings.hh" +#include "sample_analysis/HtsSeekingSampleAnalyzer.hh" +#include "sample_analysis/HtsStreamingSampleAnalyzer.hh" +#include "src/Version.hh" + +namespace spd = spdlog; + +using namespace ehunter; + +int main(int argc, char** argv) +{ + auto console = spd::stderr_color_mt("console"); + console->set_pattern("%Y-%m-%dT%H:%M:%S,[%v]"); + + try + { + console->info("Starting {}", kProgramVersion); + + auto optionalProgramParameters = tryLoadingProgramParameters(argc, argv); + if (!optionalProgramParameters) + { + return 0; + } + ProgramParameters& params = *optionalProgramParameters; + + if (params.heuristics().verboseLogging()) + { + auto verboseConsole = spdlog::stdout_color_mt("verbose"); + verboseConsole->set_pattern("%Y-%m-%dT%H:%M:%S\n%v"); + console->info("Verbose logging enabled"); + } + + SampleParameters& sampleParams = params.sample(); + + console->info("Analyzing sample {}", sampleParams.id()); + console->info("Read length is set to {}", sampleParams.readLength()); + + const InputPaths& inputPaths = params.inputPaths(); + + console->info("Initializing reference {}", inputPaths.reference()); + FastaReference reference(inputPaths.reference()); + + console->info("Loading variant catalog from disk {}", inputPaths.catalog()); + const RegionCatalog regionCatalog + = loadRegionCatalogFromDisk(inputPaths.catalog(), reference, sampleParams.sex()); + + console->info("Running sample analysis"); + const HeuristicParameters& heuristicParams = params.heuristics(); + const OutputPaths& outputPaths = params.outputPaths(); + Outputs outputs(outputPaths.vcf(), outputPaths.json(), outputPaths.log()); + + SampleFindings sampleFindings; + if (isBamFile(inputPaths.htsFile())) + { + sampleFindings + = htsSeekingSampleAnalysis(inputPaths, sampleParams, heuristicParams, regionCatalog, outputs.log()); + } + else + { + sampleFindings = htslibStreamingSampleAnalyzer( + inputPaths, sampleParams, heuristicParams, regionCatalog, outputs.log()); + } + + console->info("Writing output to disk"); + VcfWriter vcfWriter(sampleParams.id(), sampleParams.readLength(), regionCatalog, sampleFindings, reference); + outputs.vcf() << vcfWriter; + + JsonWriter jsonWriter(sampleParams.id(), sampleParams.readLength(), regionCatalog, sampleFindings); + outputs.json() << jsonWriter; + } + catch (const std::exception& e) + { + console->error(e.what()); + return 1; + } + + return 0; +} diff --git a/common/timestamp.h b/src/Version.hh old mode 100644 new mode 100755 similarity index 91% rename from common/timestamp.h rename to src/Version.hh index bd23209..b39eb26 --- a/common/timestamp.h +++ b/src/Version.hh @@ -22,9 +22,11 @@ #pragma once -#include #include -namespace ehunter { -std::string TimeStamp(); +namespace ehunter +{ + +const std::string kProgramVersion = "Expansion Hunter v3.0.0-rc1"; + } diff --git a/src/bam_file.cc b/src/bam_file.cc deleted file mode 100644 index 9447c93..0000000 --- a/src/bam_file.cc +++ /dev/null @@ -1,532 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include "include/bam_file.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "htslib/bgzf.h" -#include "htslib/hts.h" -#include "htslib/sam.h" - -#include "common/parameters.h" -#include "common/ref_genome.h" -#include "common/timestamp.h" -#include "include/bam_index.h" - -typedef boost::tokenizer> Tokenizer; -using boost::lexical_cast; - -using std::string; -using std::cerr; -using std::endl; -using std::vector; -using std::sort; - -namespace ehunter { -BamFile::BamFile() - : format_(kUnknownFormat), - hts_file_ptr_(0), - hts_bam_hdr_ptr_(0), - hts_idx_ptr_(0), - hts_itr_ptr_(0), - hts_bam_align_ptr_(0), - jump_to_unaligned_(false), - at_file_end_(false) {} - -BamFile::~BamFile() { - if (hts_bam_align_ptr_) { - bam_destroy1(hts_bam_align_ptr_); - hts_bam_align_ptr_ = 0; - } - - Close(); -} - -void BamFile::Init(const string &path, const string &reference) { - (void)reference; - - path_ = path; - // Open a BAM file for reading. - hts_file_ptr_ = sam_open(path.c_str(), "r"); - - if (!hts_file_ptr_) { - throw std::runtime_error("BamFile::Init: Failed to read BAM file '" + path + - "'"); - } - - if (hts_file_ptr_->format.format == bam) { - format_ = kBamFile; - } - -#ifndef DISABLE_CRAM - if (hts_file_ptr_->format.format == cram) { - format_ = kCramFile; - - const string reference_index = reference + ".fai"; - if (!boost::filesystem::exists(reference_index)) { - throw std::runtime_error("Reference index does not exist: " + - reference_index); - } - - if (hts_set_fai_filename(hts_file_ptr_, reference_index.c_str()) != 0) { - throw std::runtime_error("Failed to set reference index"); - } - } -#endif - - string input_format = "Unknown"; - if (format_ == kBamFile) { - input_format = "BAM"; -#ifndef DISABLE_CRAM - } else if (format_ == kCramFile) { - input_format = "CRAM"; -#endif - } - - cerr << TimeStamp() << ",[Input format: " << input_format << "]" << endl; - - // Read hdr and set up ref_vec_ - hts_bam_hdr_ptr_ = sam_hdr_read(hts_file_ptr_); - - if (!hts_bam_hdr_ptr_) { - throw std::runtime_error("BamFile::Init: Failed to read BAM header: '" + - path + "'"); - } - - const int chrom_count = hts_bam_hdr_ptr_->n_targets; - - for (int chrom_ind = 0; chrom_ind < chrom_count; ++chrom_ind) { - ref_vec_.push_back(string(hts_bam_hdr_ptr_->target_name[chrom_ind])); - } - - // Load the index - hts_idx_ptr_ = sam_index_load(hts_file_ptr_, path.c_str()); - - if (!hts_idx_ptr_) { - throw std::runtime_error("BamFile::Init: Failed to read BAM index '" + - path + "'"); - } -} - -bool BamFile::Close() { - if (hts_file_ptr_) { - if (hts_bam_hdr_ptr_) { - bam_hdr_destroy(hts_bam_hdr_ptr_); - hts_bam_hdr_ptr_ = 0; - } - - CloseRegion(); - - if (hts_idx_ptr_) { - hts_idx_destroy(hts_idx_ptr_); - hts_idx_ptr_ = 0; - } - - sam_close(hts_file_ptr_); - hts_file_ptr_ = 0; - } - - jump_to_unaligned_ = false; - at_file_end_ = false; - - return true; -} - -// Set bam file to a specific range from which the reads will be extracted. -bool BamFile::SetRegionToRange(const Region &gRange) { - // If we were in unaligned pairs reset first. - if (jump_to_unaligned_) { - jump_to_unaligned_ = false; - } - - // If we were in another region, close that. - if (hts_itr_ptr_ != 0) { - CloseRegion(); - } - - hts_itr_ptr_ = - sam_itr_querys(hts_idx_ptr_, hts_bam_hdr_ptr_, gRange.ToString().c_str()); - - if (hts_itr_ptr_ == 0) { - throw std::runtime_error("Failed to set target region: '" + - gRange.ToString() + "'"); - return false; - } - - at_file_end_ = false; - - return true; -} - -bool BamFile::CloseRegion() { - if (hts_itr_ptr_) { - hts_itr_destroy(hts_itr_ptr_); - hts_itr_ptr_ = 0; - } - - at_file_end_ = false; - - return true; -} - -// Hack to get to the unaligned pairs (at the end of the BAM) quickly. -// Find the offset of the last block containing aligned reads, then skip -// reads until reach unaligned ones. -// Based on Isis UnalignedReadExtractor::JumpToUnalignedReads. - -bool BamFile::JumpToUnaligned() { - // If we were in another region, close that. - if (hts_itr_ptr_ != 0) { - CloseRegion(); - hts_itr_ptr_ = 0; - } - - if (hts_file_ptr_->format.format == bam) { - const bool hasUnalignedPairs(hts_idx_get_n_no_coor(hts_idx_ptr_) > 0); - - if (!hasUnalignedPairs) { - return false; - } - - hts_itr_ptr_ = sam_itr_querys(hts_idx_ptr_, hts_bam_hdr_ptr_, "*"); - - if (hts_itr_ptr_ == 0) { - throw std::runtime_error("Failed to extract an unaligned read"); - } - - jump_to_unaligned_ = true; -#ifndef DISABLE_CRAM - } else if (hts_file_ptr_->format.format == cram) { - jump_to_unaligned_ = true; -#endif - } else { - throw std::logic_error("Unknown format"); - } - - return true; -} - -bool BamFile::GetRead(Align &align) { - if (jump_to_unaligned_) { - if (hts_file_ptr_->format.format == bam) { - return GetUnalignedPrRead(align); -#ifndef DISABLE_CRAM - } else if (hts_file_ptr_->format.format == cram) { - return cram_suppliment.GetUnalignedRead(align); -#endif - } else { - throw std::logic_error("Unknown format"); - } - } - - if (!hts_file_ptr_) { - throw std::logic_error("BamFile::GetRead BAM file is not open"); - } - - if (at_file_end_) { - return false; - } - - if (!hts_bam_align_ptr_) { - hts_bam_align_ptr_ = bam_init1(); - } - - int readRet = 0; - assert(hts_itr_ptr_); - readRet = GetNextGoodRead(); - - if (readRet == -1) { - at_file_end_ = true; // EOF - return false; - } - - if (readRet < -1) { - throw std::runtime_error("Failed to extract read from BAM file"); - } - - if (!GetAlignFromHtsAlign(hts_bam_align_ptr_, align)) { - throw std::runtime_error("Failed to process read from BAM file"); - } - - return true; -} - -// Try to get an aligned mate. -bool BamFile::GetAlignedMate(const Align &align, Align &mate_align) { - Region mateRegion; - int32_t tid = 0; - int32_t beg = 0; - int32_t end = 0; - - if (align.IsMateMapped()) { - tid = align.mate_chrom_id; - beg = align.mate_pos; - end = align.mate_pos + 1; - } else { - tid = align.chrom_id; - beg = align.pos; - end = align.pos + 1; - } - - hts_itr_t *iter; - iter = sam_itr_queryi(hts_idx_ptr_, tid, beg, end); - if (!iter) { - cerr << "[Failed to parse " + mateRegion.ToString() << endl; - return false; - } - while (sam_itr_next(hts_file_ptr_, iter, hts_bam_align_ptr_) >= 0) { - if (!GetAlignFromHtsAlign(hts_bam_align_ptr_, mate_align)) { - hts_itr_destroy(iter); - throw std::runtime_error("Failed to process read from BAM file"); - } - if ((mate_align.name == align.name) && - (mate_align.IsFirstMate() != align.IsFirstMate())) { - hts_itr_destroy(iter); - return true; - } - } - hts_itr_destroy(iter); - return false; -} - -bool BamFile::GetUnalignedPrRead(Align &align) { - if (at_file_end_) { - return false; - } - if (!hts_itr_ptr_) { - throw std::runtime_error("GetUnalignedPrRead but hts_itr_ptr_ 0"); - } - if (!hts_bam_align_ptr_) { - hts_bam_align_ptr_ = bam_init1(); - } - - bool found_unaligned = false; - - // Skip any aligned reads. - while (sam_itr_next(hts_file_ptr_, hts_itr_ptr_, hts_bam_align_ptr_) > 0) { - // unaligned pair -> 0x4 unmapped + 0x8 mate unmapped -> 0xC - if ((hts_bam_align_ptr_->core.flag & 0xC) == 0xC) { - found_unaligned = true; - break; - } - } - - if (!found_unaligned) { - at_file_end_ = true; - return false; - } - - // Have retrieved an unaligned read. Copy the bits needed to align. - if (!GetAlignFromHtsAlign(hts_bam_align_ptr_, align, - true)) { // assumeUnaligned=T - throw std::runtime_error("Failed to process read from BAM file."); - } - - return true; -} - -int BamFile::GetNextGoodRead() { - bool is_primary_align = false; - int return_value = 0; - - while (!is_primary_align) { - return_value = - sam_itr_next(hts_file_ptr_, hts_itr_ptr_, hts_bam_align_ptr_); - if (return_value < 0) { - // low-level reading failed so report the return code. - return return_value; - } - const bool is_supplimentary = - hts_bam_align_ptr_->core.flag & kSupplimentaryAlign; - const bool is_secondary = hts_bam_align_ptr_->core.flag & kSecondaryAlign; - is_primary_align = (!is_supplimentary) && (!is_secondary); - } - return return_value; -} - -size_t CountValidBases(const string &bases) { - const size_t n_count = std::count(bases.begin(), bases.end(), 'N'); - const size_t valid_base_count = bases.length() - n_count; - - return valid_base_count; -} - -static bool IsAutosome(const string &chrom_name) { - static std::unordered_set autosome_names = { - "chr1", "chr2", "chr3", "chr4", "chr5", "chr6", "chr7", "chr8", - "chr9", "chr10", "chr11", "chr12", "chr13", "chr14", "chr15", "chr16", - "chr17", "chr18", "chr19", "chr20", "chr21", "chr22", "1", "2", - "3", "4", "5", "6", "7", "8", "9", "10", - "11", "12", "13", "14", "15", "16", "17", "18", - "19", "20", "21", "22"}; - - auto search = autosome_names.find(chrom_name); - if (search != autosome_names.end()) { - return true; - } - return false; -} - -double BamFile::CalcMedianDepth(Parameters ¶meters, size_t read_len) { - if (read_len == 0) { - throw std::logic_error("Read length must be non-zero: " + - lexical_cast(read_len)); - } - - RefGenome ref_genome(parameters.genome_path()); - BamIndex bam_index(parameters.bam_path()); - - vector mapped_read_counts; - vector unmapped_read_counts; - - vector chrom_names; - vector chrom_lens; - if (!bam_index.GetChrReadCounts(chrom_names, chrom_lens, mapped_read_counts, - unmapped_read_counts)) { - throw std::runtime_error("Failed to get chrom read depths from index of " + - parameters.bam_path()); - } - - const int chrom_count = chrom_names.size(); - -#ifndef DISABLE_CRAM - if (format_ == kCramFile) { - mapped_read_counts = - cram_suppliment.CountAlignedReads(parameters.bam_path(), chrom_count); - for (int i = 0; i < (int)chrom_names.size(); ++i) { - cerr << chrom_names[i] << " " << chrom_lens[i] << " " - << mapped_read_counts[i] << endl; - } - } -#endif - - typedef std::pair ChromIndDepth; - typedef vector ChromIndDepths; - ChromIndDepths chrom_ind_depths; - - string chrom_bases; - - for (int chrom_ind = 0; chrom_ind < chrom_count; ++chrom_ind) { - const string &chrom_name = chrom_names[chrom_ind]; - - if (!IsAutosome(chrom_name)) { - continue; - } - - cerr << TimeStamp() << ",[Using " << chrom_name << " to calculate depth]" - << endl; - - ref_genome.ExtractSeq(chrom_name, &chrom_bases); - - const double read_depth = - static_cast(mapped_read_counts[chrom_ind]) * read_len / - CountValidBases(chrom_bases); - - chrom_ind_depths.push_back(ChromIndDepth(chrom_ind, read_depth)); - } - - if (chrom_ind_depths.empty()) { - throw std::runtime_error( - "Error: No contigs named chr1-chr22 or 1-22 " - "found; consider setting the depth manually"); - } - - sort(chrom_ind_depths.begin(), chrom_ind_depths.end(), - boost::bind(&std::pair::second, _1) > - boost::bind(&std::pair::second, _2)); - - const size_t autosome_count = chrom_ind_depths.size(); - const bool autosome_count_is_odd = (autosome_count % 2) == 1; - const size_t half_autosome_count = autosome_count / 2; - - const double median_autosome_depth = - (autosome_count_is_odd - ? (chrom_ind_depths[half_autosome_count].second) - : ((chrom_ind_depths[half_autosome_count - 1].second + - chrom_ind_depths[half_autosome_count].second) / - 2.0)); - return median_autosome_depth; -} - -vector CramFile::CountAlignedReads(const string &cram_path, - int num_chroms) { - vector read_counts(num_chroms, 0); - file_ptr_ = sam_open(cram_path.c_str(), "r"); - - if (!file_ptr_) { - throw std::runtime_error("Failed to read the input file"); - } - -#ifndef DISABLE_CRAM - if (file_ptr_->format.format != cram) { - throw std::runtime_error(cram_path + " is not a CRAM file"); - } -#endif - - header_ptr_ = sam_hdr_read(file_ptr_); - if (!header_ptr_) { - throw std::runtime_error("Could not read header of " + cram_path); - } - - align_ptr_ = bam_init1(); - - found_unaligned_reads_ = false; - int ret; - while ((ret = sam_read1(file_ptr_, header_ptr_, align_ptr_)) >= 0) { - if (align_ptr_->core.tid == -1) { // Reached unaligned reads. - found_unaligned_reads_ = true; - cerr << "[Found unaligend reads]" << endl; - break; - } - // Counting mapped reads only. - if ((align_ptr_->core.flag & 0x0004) == 0) { - ++read_counts[align_ptr_->core.tid]; - } - } - - return read_counts; -} - -bool CramFile::GetUnalignedRead(Align &align) { - int ret = sam_read1(file_ptr_, header_ptr_, align_ptr_); - if (ret < 0) { - return false; - } - if (!GetAlignFromHtsAlign(align_ptr_, align, true)) { - throw std::runtime_error("Failed to process read from BAM file."); - } - return true; -} -} // namespace ehunter diff --git a/src/bam_index.cc b/src/bam_index.cc deleted file mode 100644 index fdf1bae..0000000 --- a/src/bam_index.cc +++ /dev/null @@ -1,108 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include "include/bam_index.h" - -#include - -#include -using std::cerr; -using std::endl; -#include -using std::vector; -#include -#include -#include -#include -#include -using std::string; - -#include "htslib/sam.h" - -namespace ehunter { -BamIndex::BamIndex(const string& bam_path) : bam_path_(bam_path) { - // Set up hts_file_ptr_ (BAM/CRAM file pointer) - hts_file_ptr_ = sam_open(bam_path_.c_str(), "r"); - - if (!hts_file_ptr_) { - cerr << "[ERROR]: Could not open '" << bam_path_ << "'" << endl; - throw std::runtime_error("BamIndex: Could not open '" + bam_path_ + "'"); - } -} - -BamIndex::~BamIndex() { - sam_close(hts_file_ptr_); - hts_file_ptr_ = 0; -} - -bool BamIndex::GetChrReadCounts(vector& chrom_names, - vector& chrom_lens, - vector& mapped_read_counts, - vector& unmapped_read_counts) const { - chrom_names.clear(); - chrom_lens.clear(); - mapped_read_counts.clear(); - unmapped_read_counts.clear(); - - bam_hdr_t* hts_bam_hdr_ptr = sam_hdr_read(hts_file_ptr_); - - if (hts_bam_hdr_ptr == 0) { - cerr << "[ERROR]: GetChrReadCounts: " - << "Failed to read header of BAM '" << bam_path_ << "'" << endl; - return false; - } - - hts_idx_t* hts_idx_ptr = sam_index_load(hts_file_ptr_, bam_path_.c_str()); - - if (hts_idx_ptr == 0) { - cerr << "[ERROR]: GetChrReadCounts : Failed to open index of BAM '" - << bam_path_ << "'" << std::endl; - return false; - } - - const int chrom_count(hts_bam_hdr_ptr->n_targets); - - uint64_t mapped_count = 0; - uint64_t unmapped_count = 0; - - for (int chrom_ind = 0; chrom_ind < chrom_count; ++chrom_ind) { - chrom_names.push_back(string(hts_bam_hdr_ptr->target_name[chrom_ind])); - chrom_lens.push_back(hts_bam_hdr_ptr->target_len[chrom_ind]); - - hts_idx_get_stat(hts_idx_ptr, chrom_ind, &mapped_count, &unmapped_count); - - mapped_read_counts.push_back((size_t)mapped_count); - unmapped_read_counts.push_back((size_t)unmapped_count); - } - - // Unaligned pairs - chrom_names.push_back("*"); - chrom_lens.push_back(0); - mapped_read_counts.push_back(0); - unmapped_read_counts.push_back((size_t)hts_idx_get_n_no_coor(hts_idx_ptr)); - - bam_hdr_destroy(hts_bam_hdr_ptr); - hts_idx_destroy(hts_idx_ptr); - - return true; -} -} // namespace ehunter diff --git a/src/expansion_hunter.cc b/src/expansion_hunter.cc deleted file mode 100644 index eedaa7f..0000000 --- a/src/expansion_hunter.cc +++ /dev/null @@ -1,522 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "common/parameters.h" -#include "common/ref_genome.h" -#include "common/timestamp.h" -#include "genotyping/repeat_genotyper.h" -#include "include/bam_file.h" -#include "include/bam_index.h" -#include "include/irr_counting.h" -#include "include/json_output.h" -#include "include/read_group.h" -#include "include/region_findings.h" -#include "include/vcf_output.h" -#include "include/version.h" -#include "purity/purity.h" -#include "rep_align/rep_align.h" - -using std::array; -using std::cerr; -using std::endl; -using std::map; -using std::pair; -using std::string; -using std::unordered_set; -using std::vector; - -namespace ehunter { -// Returns the length of the first read in a BAM file. -size_t CalcReadLen(const string &bam_path) { - // Open a BAM file for reading. - samFile *file_ptr = sam_open(bam_path.c_str(), "r"); - if (!file_ptr) { - throw std::runtime_error("Failed to read BAM file '" + bam_path + "'"); - } - bam_hdr_t *header_ptr = sam_hdr_read(file_ptr); - if (!header_ptr) { - throw std::runtime_error("BamFile::Init: Failed to read BAM header: '" + - bam_path + "'"); - } - - enum { kSupplimentaryAlign = 0x800, kSecondaryAlign = 0x100 }; - - size_t read_len = 99; - bam1_t *align_ptr = bam_init1(); - int ret; - while ((ret = sam_read1(file_ptr, header_ptr, align_ptr)) >= 0) { - const bool is_supplimentary = align_ptr->core.flag & kSupplimentaryAlign; - const bool is_secondary = align_ptr->core.flag & kSecondaryAlign; - const bool is_primary_align = (!is_supplimentary) && (!is_secondary); - if (is_primary_align) { - read_len = align_ptr->core.l_qseq; - break; - } - } - - if (ret < 0) { - throw std::runtime_error("Failed to extract a read from BAM file"); - } - - bam_destroy1(align_ptr); - bam_hdr_destroy(header_ptr); - sam_close(file_ptr); - - return read_len; -} - -// Search for reads spanning the entire repeat sequence. -void FindShortRepeats(const Parameters ¶meters, - const RepeatSpec &repeat_spec, AlignPairs &align_pairs, - vector &read_groups, - vector *flanking_repaligns) { - map> size_spanning_repaligns; - flanking_repaligns->clear(); - - // Align each read to the repeat. - for (auto &kv : align_pairs) { - AlignPair &frag = kv.second; - - for (Align &align : frag) { - RepeatAlign rep_align; - const bool aligns = AlignRead(parameters, repeat_spec, align.bases, - align.quals, &rep_align); - rep_align.read.name = align.name; - - if (!aligns) { - continue; - } - - // Not pretty, but lets downstream code know that this is not an IRR. - align.status = kFlankingRead; - - if (rep_align.type == RepeatAlign::Type::kSpanning) { - size_spanning_repaligns[rep_align.size].push_back(rep_align); - } else { - assert(rep_align.type == RepeatAlign::Type::kFlanking); - flanking_repaligns->push_back(rep_align); - } - } - } - - for (const auto &size_repaligns : size_spanning_repaligns) { - RepeatReadGroup read_group; - read_group.read_type = ReadType::kSpanning; - read_group.size = size_repaligns.first; - read_group.num_supporting_reads = size_repaligns.second.size(); - read_group.rep_aligns = size_repaligns.second; - read_groups.push_back(read_group); - } - - DistributeFlankingReads(parameters, repeat_spec, &read_groups, - flanking_repaligns); - - CoalesceFlankingReads(repeat_spec, read_groups, flanking_repaligns, - repeat_spec.units[0].length(), repeat_spec.units_shifts, - parameters.min_baseq(), parameters.min_wp()); -} - -// Caches alignments from extended target and off-target regions. For BAM files, -// mates of reads in the relevant regions are cached too. -void CacheAligns(BamFile *bam_file, const RepeatSpec &repeat_spec, - AlignPairs &align_pairs, - unordered_set &ontarget_frag_names, - int extension_len) { - align_pairs.clear(); - Region extended_target_region = - repeat_spec.target_region.Extend(extension_len); - - // Cache on-target reads. - CacheReadsFromRegion(extended_target_region, kCacheAll, - repeat_spec.units_shifts, 0.9, &(*bam_file), - &align_pairs); - - // Save names of fragments that overlap target locus before caching reads - // from confusion regions. - ontarget_frag_names.clear(); - for (const auto &kv : align_pairs) { - const AlignPair &frag = kv.second; - const string name = !frag[0].name.empty() ? frag[0].name : frag[1].name; - ontarget_frag_names.insert(name); - } - - cerr << TimeStamp() << ",\t[Found " << ontarget_frag_names.size() - << " reads in target locus]" << endl; - - // Cache aligned off-target reads. - for (const Region &confusion_region : repeat_spec.offtarget_regions) { - Region extended_confusion_region = confusion_region.Extend(extension_len); - CacheReadsFromRegion(extended_confusion_region, kCacheAll, - repeat_spec.units_shifts, 0.9, &(*bam_file), - &align_pairs); - } - - // Filling-in missing mates by jumping around the BAM/CRAM file. - cerr << TimeStamp() << ",\t[Filling in mates]" << endl; - FillinMates(*bam_file, align_pairs, repeat_spec.units_shifts, 0.9, - ontarget_frag_names); - cerr << TimeStamp() << ",\t[Done filling in mates]" << endl; -} - -bool IsFlannkingGroup(const RepeatReadGroup &read_group) { - return read_group.read_type == ReadType::kFlanking; -} - -void FindLongRepeats(const Parameters ¶meters, - const RepeatSpec &repeat_spec, BamFile &bam_file, - unordered_set &ontarget_frag_names, - AlignPairs &align_pairs, RegionFindings ®ion_findings) { - // Count the number of anchored IRRs. - region_findings.num_anchored_irrs = 0; - const int extension_len = parameters.region_extension_len(); - Region target_nhood = repeat_spec.target_region.Extend(extension_len); - - // Anchored IRRs and IRR pairs will be stored here. - vector irr_rep_aligns; - CountAnchoredIrrs(bam_file, parameters, target_nhood, ontarget_frag_names, - align_pairs, region_findings.num_anchored_irrs, - repeat_spec.units_shifts, &irr_rep_aligns); - - cerr << TimeStamp() << ",\t[Found " << region_findings.num_anchored_irrs - << " anchored IRRs]" << endl - << TimeStamp() << ",\t[Cached " << align_pairs.size() << " reads]" - << endl; - - // Stores the total IRR count. - region_findings.num_irrs = region_findings.num_anchored_irrs; - - // Look for IRR pairs only if anchored IRRs are found. - if (region_findings.num_anchored_irrs) { - if (!repeat_spec.is_common_unit()) { - cerr << TimeStamp() << ",\t[Counting aligned IRR pairs]" << endl; - map numIrrConfRegion; - region_findings.num_irrs += - CountAlignedIrr(parameters, align_pairs, numIrrConfRegion, - repeat_spec.units_shifts, &irr_rep_aligns); - - // Record paired IRR counts from each confusion region. - region_findings.offtarget_irr_counts.clear(); - for (const Region &confusionRegion : repeat_spec.offtarget_regions) { - Region confusionNhood = confusionRegion.Extend(extension_len); - region_findings.offtarget_irr_counts.push_back( - numIrrConfRegion[confusionNhood.ToString()]); - } - - region_findings.num_unaligned_irrs = 0; - - if (!parameters.skip_unaligned()) { - cerr << TimeStamp() << ",\t[Counting unaligned IRRs]" << endl; - CountUnalignedIrrs(bam_file, parameters, - region_findings.num_unaligned_irrs, - repeat_spec.units_shifts, &irr_rep_aligns); - region_findings.num_irrs += region_findings.num_unaligned_irrs; - } else { - cerr << TimeStamp() << ",\t[Skipping unaligned IRRs]" << endl; - } - } - - const size_t unit_len = repeat_spec.units[0].length(); - - RepeatReadGroup read_group; - read_group.read_type = ReadType::kInrepeat; - - read_group.size = - (int)(std::ceil(parameters.read_len() / (double)unit_len)); - read_group.num_supporting_reads = region_findings.num_irrs; - read_group.rep_aligns = irr_rep_aligns; - - region_findings.read_groups.push_back(read_group); - - // If there is evidence for a long repeat allele we assume that flanking - // reads came from and so any previously-found alleles whose size was - // estimated from flanking reads are no longer valid. - - vector::const_iterator flanking_allele_it = - std::find_if(region_findings.read_groups.begin(), - region_findings.read_groups.end(), IsFlannkingGroup); - - if (flanking_allele_it != region_findings.read_groups.end()) { - region_findings.flanking_repaligns.insert( - region_findings.flanking_repaligns.end(), - flanking_allele_it->rep_aligns.begin(), - flanking_allele_it->rep_aligns.end()); - } - - region_findings.read_groups.erase( - std::remove_if(region_findings.read_groups.begin(), - region_findings.read_groups.end(), IsFlannkingGroup), - region_findings.read_groups.end()); - } -} - -static bool CompareRegionFindings(const RegionFindings &r1, - const RegionFindings &r2) { - return r1.region < r2.region; -} - -// For each repeat region, calculate sizes of repeats that are (a) shorter and -// (b) longer than the read length; reconsile alleles and output results to -// appropriate files. - -void EstimateRepeatSizes(const Parameters ¶meters, - const map &repeat_specs, - BamFile *bam_file, Outputs *outputs) { - // boost::property_tree::ptree ptree_root; - vector sample_findings; - - // Analyze repeats one by one. - for (auto &kv : repeat_specs) { - const RepeatSpec &repeat_spec = kv.second; - - string repeat_header = repeat_spec.target_region.ToString(); - repeat_header += " " + boost::algorithm::join(repeat_spec.units, "/"); - - GenotypeType genotype_type = GenotypeType::kDiploid; - const string chrom = repeat_spec.target_region.chrom(); - - const bool is_female_chrom_y = - parameters.sex() == Sex::kFemale && (chrom == "chrY" || chrom == "Y"); - - if (is_female_chrom_y) { - cerr << TimeStamp() << ",[Skipping " << repeat_header - << " because the sample is female]" << endl; - continue; - } - - cerr << TimeStamp() << ",[Analyzing " << repeat_header << "]" << endl; - - const bool is_sex_chrom = - chrom == "chrX" || chrom == "X" || chrom == "chrY" || chrom == "Y"; - - if (parameters.sex() == Sex::kMale && is_sex_chrom) { - genotype_type = GenotypeType::kHaploid; - } - - RegionFindings region_findings; - region_findings.region_id = repeat_spec.repeat_id; - region_findings.region = repeat_spec.target_region; - region_findings.offtarget_irr_counts = - vector(repeat_spec.offtarget_regions.size(), 0); - - cerr << TimeStamp() << ",\t[Caching reads]" << endl; - AlignPairs align_pairs; - unordered_set ontarget_frag_names; - CacheAligns(&(*bam_file), repeat_spec, align_pairs, ontarget_frag_names, - parameters.region_extension_len()); - if (align_pairs.empty()) { - cerr << TimeStamp() << ",\t[Found no on-target or off-target reads]" - << endl; - continue; - } - - cerr << TimeStamp() << ",\t[Estimating short repeat sizes]" << endl; - FindShortRepeats(parameters, repeat_spec, align_pairs, - region_findings.read_groups, - ®ion_findings.flanking_repaligns); - - cerr << TimeStamp() << ",\t[Estimating long repeat sizes]" << endl; - region_findings.num_anchored_irrs = 0; - region_findings.num_unaligned_irrs = 0; - region_findings.num_irrs = 0; - - FindLongRepeats(parameters, repeat_spec, *bam_file, ontarget_frag_names, - align_pairs, region_findings); - - map flanking_size_counts; - map spanning_size_counts; - - for (const auto &align : region_findings.flanking_repaligns) { - flanking_size_counts[align.size] += 1; - } - - const int num_units_in_read = (int)(std::ceil( - parameters.read_len() / (double)repeat_spec.units[0].length())); - - vector haplotype_candidates; - // Add count of in-repeat reads to flanking. - for (const auto &read_group : region_findings.read_groups) { - if (read_group.read_type == ReadType::kSpanning) { - spanning_size_counts[read_group.size] += - read_group.num_supporting_reads; - haplotype_candidates.push_back( - RepeatAllele(read_group.size, read_group.num_supporting_reads, - ReadType::kSpanning)); - } else if (read_group.read_type == ReadType::kInrepeat) { - flanking_size_counts[num_units_in_read] += - read_group.num_supporting_reads; - haplotype_candidates.push_back( - RepeatAllele(num_units_in_read, read_group.num_supporting_reads, - ReadType::kInrepeat)); - } else if (read_group.read_type == ReadType::kFlanking) { - haplotype_candidates.push_back( - RepeatAllele(read_group.size, read_group.num_supporting_reads, - ReadType::kFlanking)); - for (const auto &align : read_group.rep_aligns) { - flanking_size_counts[align.size] += 1; - } - } else { - throw std::logic_error("Do not know how to deal with " + - kReadTypeToString.at(read_group.read_type) + - " alleles"); - } - } - - cerr << TimeStamp() << ",\t[Flanking:"; - for (const auto &kv : flanking_size_counts) { - cerr << " (" << kv.first << ", " << kv.second << ")"; - } - cerr << "]" << endl; - - cerr << TimeStamp() << ",\t[Spanning:"; - for (const auto &kv : spanning_size_counts) { - cerr << " (" << kv.first << ", " << kv.second << ")"; - } - cerr << "]" << endl; - - cerr << TimeStamp() << ",\t[Haplotype candidates:"; - for (const auto &candiate : haplotype_candidates) { - cerr << " " << candiate.size_; - } - cerr << "]" << endl; - - if (haplotype_candidates.empty()) { - cerr - << TimeStamp() - << ",\t[Skipping this region because no informative reads were found]" - << endl; - continue; - } - - const int unit_len = repeat_spec.units[0].length(); - double kPropCorrectMolecules = 0.97; - if (unit_len <= 2) { - kPropCorrectMolecules = 0.70; - } - const double hap_depth = parameters.depth() / 2; - const int max_num_units_in_read = - (int)(std::ceil(parameters.read_len() / (double)unit_len)); - - GenotypeRepeat(parameters, repeat_spec, max_num_units_in_read, - kPropCorrectMolecules, hap_depth, parameters.read_len(), - haplotype_candidates, flanking_size_counts, - spanning_size_counts, genotype_type, - region_findings.genotype); - - // End genotyping - sample_findings.push_back(region_findings); - - OutputRepeatAligns(parameters, repeat_spec, region_findings.read_groups, - region_findings.flanking_repaligns, &(*outputs).log()); - } - - std::sort(sample_findings.begin(), sample_findings.end(), - CompareRegionFindings); - WriteJson(parameters, repeat_specs, sample_findings, outputs->json()); - WriteVcf(parameters, repeat_specs, sample_findings, outputs->vcf()); - cerr << TimeStamp() << ",[All done]" << endl; -} -} // namespace ehunter - -#ifdef LIBRARY_TARGET -int expansionHunter(int argc, char *argv[]) { -#else -int main(int argc, char *argv[]) { -#endif - try { - ehunter::Parameters parameters; - cerr << ehunter::kProgramVersion << endl; - - if (!parameters.Load(argc, argv)) { - return 0; - } - - cerr << ehunter::TimeStamp() << ",[Starting Logging for " - << parameters.sample_name() << "]" << endl; - - ehunter::Outputs outputs(parameters.vcf_path(), parameters.json_path(), - parameters.log_path()); - - map repeat_specs; - if (!LoadRepeatSpecs(parameters.repeat_specs_path(), - parameters.genome_path(), parameters.min_wp(), - &repeat_specs)) { - throw std::invalid_argument( - "Failed to load repeat table from disease specs in '" + - parameters.repeat_specs_path() + "'"); - } - - if (!parameters.read_len_is_set()) { - const int read_len = ehunter::CalcReadLen(parameters.bam_path()); - if (read_len < parameters.minReadLength) { - throw std::runtime_error( - "Read length=" + lexical_cast(read_len) + - " determined from first alignment" + " is too small."); - } - parameters.set_read_len(read_len); - } - - ehunter::BamFile bam_file; - bam_file.Init(parameters.bam_path(), parameters.genome_path()); - - if (!parameters.depth_is_set()) { - cerr << ehunter::TimeStamp() << ",[Calculating depth]" << endl; - const double depth = - bam_file.CalcMedianDepth(parameters, parameters.read_len()); - parameters.set_depth(depth); - } - - cerr << ehunter::TimeStamp() << ",[Read length: " << parameters.read_len() - << "]" << endl; - cerr << ehunter::TimeStamp() << ",[Depth: " << parameters.depth() << "]" - << endl; - - if (parameters.depth() < parameters.kSmallestPossibleDepth) { - throw std::runtime_error("Estimated depth of " + - lexical_cast(parameters.depth()) + - " is too low for a meaningful inference of " - "repeat sizes"); - } - - ehunter::EstimateRepeatSizes(parameters, repeat_specs, &bam_file, &outputs); - } catch (const std::exception &e) { - cerr << e.what() << endl; - return 1; - } - - return 0; -} diff --git a/src/irr_counting.cc b/src/irr_counting.cc deleted file mode 100644 index a2b3318..0000000 --- a/src/irr_counting.cc +++ /dev/null @@ -1,406 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include "include/irr_counting.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "common/timestamp.h" -#include "purity/purity.h" -#include "rep_align/rep_align.h" - -using std::string; -using std::endl; -using std::cerr; -using std::unordered_set; -using std::vector; -using std::map; -using std::array; - -namespace ehunter { -// Check if two alignments are same. -static bool SameAlign(Align &al1, Align &al2) { - return (al1.name == al2.name && al1.mate_pos == al2.mate_pos && - al1.flag == al2.flag && al1.bases == al2.bases); -} - -void CacheReadsFromRegion(const Region ®ion, const WhatToCache whatToCache, - const vector> &units_shifts, - double min_wp_score, BamFile *bam_file, - AlignPairs *align_pairs) { - // Jump to the target region or the unaligned reads if the chromosome name - // is "*" then jump to unaligned reads. - if (region.chrom() == "*") { - if (!(*bam_file).JumpToUnaligned()) { - cerr << TimeStamp() - << ",\t[Warning: there appears to be no aligned reads]" << endl; - return; - } - } else { - if (!(*bam_file).SetRegionToRange(region)) { - throw std::runtime_error("Failed to jump to " + region.ToString()); - } - } - - Align align; - - while ((*bam_file).GetRead(align)) { - const AlignPairs::iterator it = (*align_pairs).find(align.name); - - if (it == (*align_pairs).end()) { - Align dummy_align; - - align.region = region.ToString(); - array pair = {{align, dummy_align}}; - if (!align.IsFirstMate()) { - std::reverse(pair.begin(), pair.end()); - } - (*align_pairs)[align.name] = pair; - } else { - // The same alignment might be encountered twice if two confusion - // regions are near each other. Such duplicate alignments are skipped - // with a warning. Two alignments for the same mate, however, are not - // permitted. - - align.region = region.ToString(); - AlignPair &frag = (*align_pairs)[align.name]; - - if (align.IsFirstMate()) { - if (frag[0].name.empty()) { - frag[0] = align; - } else { - if (!SameAlign(frag[0], align)) { - cerr << TimeStamp() - << ",\t[WARNING: There are multiple first mates named \"" - << frag[0].name << "\"]" << endl; - } - } - } else { - if (frag[1].name.empty()) { - frag[1] = align; - } else { - if (!SameAlign(frag[1], align)) { - cerr << TimeStamp() - << ",\t[WARNING: There are multiple second mates named \"" - << frag[1].name << "\"]" << endl; - } - } - } - - if (whatToCache == kCacheIrr) { - // If both mates are cached. Remove both mates from cache - // unless one of them is in-repeat. - if (!frag[0].name.empty() && !frag[1].name.empty()) { - double score1 = - MatchRepeatRc(units_shifts, frag[0].bases, frag[0].quals); - double score2 = - MatchRepeatRc(units_shifts, frag[1].bases, frag[1].quals); - score1 /= frag[0].bases.length(); - score2 /= frag[1].bases.length(); - - if (score1 < min_wp_score && score2 < min_wp_score) { - (*align_pairs).erase(it); - } - } - } - } - } -} - -bool CheckAnchoredIrrs(const BamFile &bam_file, const Parameters ¶meters, - const Region &target_neighborhood, - const Align &read_align, const Align &mate_align, - const vector> &units_shifts) { - const int min_mapq = parameters.min_anchor_mapq(); - // Check if the read has low mapping quality and it is an off-target - // anchor; such reads are reported but not included into the calculation. - if (read_align.mapq < min_mapq) { - if (mate_align.IsMapped() && mate_align.mapq >= min_mapq) { - Region mateRegion; - mate_align.GetReadRegion(mateRegion, bam_file.ref_vec()); - - if (!mateRegion.Overlaps(target_neighborhood)) { - cerr << TimeStamp() << ",\t[Discarding IRR " << read_align.name - << " read" << (read_align.IsFirstMate() ? "1" : "2") - << read_align.pos << " MAPQ " << read_align.mapq - << ") because anchoring mate " << mate_align.name << " read" - << (mate_align.IsFirstMate() ? "1" : "2") << mate_align.pos - << " MAPQ " << mate_align.mapq << ") not on target (" - << target_neighborhood << ")]" << endl; - return false; - } - } - } - - double wp = MatchRepeatRc(units_shifts, read_align.bases, read_align.quals); - if (!read_align.bases.empty()) { - wp /= read_align.bases.length(); - } - const bool is_irr = wp >= parameters.min_wp(); - - // no repeat above the length/score threshold was detected in the read - if (!is_irr) { - return false; - } - - if (mate_align.mapq < min_mapq) { - return false; - } - - return true; -} - -/*****************************************************************************/ - -void FillinMates(BamFile &bam_file, AlignPairs &align_pairs, - const vector> &units_shifts, - double min_wp_score, - const unordered_set &ontarget_frag_names) { - for (AlignPairs::iterator it = align_pairs.begin(); it != align_pairs.end(); - ++it) { - AlignPair &frag = it->second; - - // At least one read must always be filled out. - assert(!frag[0].name.empty() || !frag[1].name.empty()); - // Try to fill in the missing mate. - if (frag[0].name.empty() || frag[1].name.empty()) { - // Get references for exisitng Align and the one that - // needs to be filled in and then process them below. This - // is done to avoid code duplication. - Align &existing_al = frag[0].name.empty() ? frag[1] : frag[0]; - Align &missing_al = frag[0].name.empty() ? frag[0] : frag[1]; - - // Do not recover nearby mates. - if ((existing_al.chrom_id == existing_al.mate_chrom_id) && - (std::abs(existing_al.pos - existing_al.mate_pos) < 1000)) { - continue; - } - - // Do not recover mates of off-target reads that are not IRRs. - if (ontarget_frag_names.find(existing_al.name) == - ontarget_frag_names.end()) { - double score = - MatchRepeatRc(units_shifts, existing_al.bases, existing_al.quals); - score /= existing_al.bases.length(); - - if (score < min_wp_score) { - continue; - } - } - - if (bam_file.GetAlignedMate(existing_al, missing_al)) { - // region typically stores the position of the region from - // which a read was cached. Since this read was not cached - // from anywhere, we store its position in the region field. - const vector &refVec = bam_file.ref_vec(); - assert(missing_al.chrom_id < (int)refVec.size()); - missing_al.region = Region(refVec[missing_al.chrom_id], - missing_al.pos + 1, missing_al.pos + 2) - .ToString(); - } else { - missing_al = Align(); - missing_al.region = Region("chr-1", 0, 0).ToString(); - } - } - } -} - -// TODO(edolzhenko): Move cache creation out of the function. -void CountUnalignedIrrs(BamFile &bam_file, const Parameters ¶meters, - int &numUnalignedIRRs, - const vector> &units_shifts, - vector *irr_rep_aligns) { - numUnalignedIRRs = 0; - - AlignPairs align_pairs; - Region unalignedRegion("*", 0, 0, ""); - - cerr << TimeStamp() << ",\t[Caching unaligned IRRs]" << endl; - CacheReadsFromRegion(unalignedRegion, kCacheIrr, units_shifts, - parameters.min_wp(), &bam_file, &align_pairs); - - cerr << TimeStamp() << ",\t[Done; cached " << align_pairs.size() - << " unaligned fragments containing at least one IRR read]" << endl; - - for (AlignPairs::const_iterator it = align_pairs.begin(); - it != align_pairs.end(); ++it) { - const AlignPair &frag = it->second; - - double topScore1 = - MatchRepeatRc(units_shifts, frag[0].bases, frag[0].quals); - double topScore2 = - MatchRepeatRc(units_shifts, frag[1].bases, frag[1].quals); - if (!frag[0].bases.empty()) { - topScore1 /= frag[0].bases.length(); - } - if (!frag[1].bases.empty()) { - topScore2 /= frag[1].bases.length(); - } - - const bool is_irr1 = topScore1 >= parameters.min_wp(); - const bool is_irr2 = topScore2 >= parameters.min_wp(); - - assert(!(frag[0].name.empty() && is_irr1)); - assert(!(frag[1].name.empty() && is_irr2)); - - if (is_irr1 && is_irr2) { - numUnalignedIRRs += 2; - - RepeatAlign rep_align; - rep_align.read.name = it->first; - rep_align.read.bases = frag[0].bases; - rep_align.read.quals = frag[0].quals; - rep_align.left_flank_len = 0; - rep_align.right_flank_len = 0; - rep_align.type = RepeatAlign::Type::kUnalignedIrrPair; - const int unit_len = units_shifts[0][0].length(); - rep_align.size = rep_align.read.bases.length() / unit_len; - rep_align.mate.bases = frag[1].bases; - rep_align.mate.quals = frag[1].quals; - irr_rep_aligns->push_back(rep_align); - } else if (is_irr1 || is_irr2) { - ++numUnalignedIRRs; - const Align &irr = is_irr1 ? frag[0] : frag[1]; - const Align &mate = is_irr1 ? frag[1] : frag[0]; - - RepeatAlign rep_align; - rep_align.read.name = it->first; - rep_align.read.bases = irr.bases; - rep_align.read.quals = irr.quals; - rep_align.left_flank_len = 0; - rep_align.right_flank_len = 0; - rep_align.type = RepeatAlign::Type::kUnalignedIrrSingleton; - const int unit_len = units_shifts[0][0].length(); - rep_align.size = rep_align.read.bases.length() / unit_len; - rep_align.mate.bases = mate.bases; - rep_align.mate.quals = mate.quals; - irr_rep_aligns->push_back(rep_align); - } - } -} - -int CountAlignedIrr(const Parameters ¶meters, const AlignPairs &align_pairs, - map &numIrrConfRegion, - const vector> &units_shifts, - vector *irr_rep_aligns) { - int irr_count = 0; - for (AlignPairs::const_iterator it = align_pairs.begin(); - it != align_pairs.end(); ++it) { - const AlignPair &frag = it->second; - double first_top_score = - MatchRepeatRc(units_shifts, frag[0].bases, frag[0].quals); - if (!frag[0].bases.empty()) { - first_top_score /= frag[0].bases.length(); - } - const bool is_first_irr = first_top_score >= parameters.min_wp(); - - double second_top_score = - MatchRepeatRc(units_shifts, frag[1].bases, frag[1].quals); - if (!frag[1].bases.empty()) { - second_top_score /= frag[1].bases.length(); - } - const bool is_second_irr = second_top_score >= parameters.min_wp(); - - // Update status and increase count if both mates are IRR. - if (is_first_irr && is_second_irr) { - assert(!frag[0].name.empty() && !frag[1].name.empty()); - irr_count += 2; - // Increase the count for the corresponding confusion region. - numIrrConfRegion[frag[0].region] += 1; - numIrrConfRegion[frag[1].region] += 1; - - RepeatAlign rep_align; - rep_align.read.name = it->first; - rep_align.read.bases = frag[0].bases; - rep_align.read.quals = frag[0].quals; - rep_align.left_flank_len = 0; - rep_align.right_flank_len = 0; - rep_align.type = RepeatAlign::Type::kAlignedIrrPair; - const int unit_len = units_shifts[0][0].length(); - rep_align.size = rep_align.read.bases.length() / unit_len; - rep_align.mate.bases = frag[1].bases; - rep_align.mate.quals = frag[1].quals; - irr_rep_aligns->push_back(rep_align); - } - } - - return irr_count; -} - -void CountAnchoredIrrs(const BamFile &bam_file, const Parameters ¶meters, - const Region &targetNhood, - const unordered_set &ontarget_frag_names, - AlignPairs &align_pairs, int &numAnchoredIrrs, - const vector> &units_shifts, - vector *anchored_irrs) { - numAnchoredIrrs = 0; - - // Check fragments from the target locus. - for (unordered_set::const_iterator it = ontarget_frag_names.begin(); - it != ontarget_frag_names.end(); ++it) { - const AlignPair frag = align_pairs[*it]; - const Align &al1 = frag[0]; - const Align &al2 = frag[1]; - - // Counts fragments for which both mates are present. - if (!al1.name.empty() && !al2.name.empty()) { - assert(al1.name == al2.name); - - const bool is_mate1_anchored_irr = - (al1.status != kFlankingRead) && - CheckAnchoredIrrs(bam_file, parameters, targetNhood, al1, al2, - units_shifts); - - const bool is_mate2_anchored_irr = - (al2.status != kFlankingRead) && - CheckAnchoredIrrs(bam_file, parameters, targetNhood, al2, al1, - units_shifts); - - if (is_mate1_anchored_irr || is_mate2_anchored_irr) { - const Align &irr = is_mate1_anchored_irr ? al1 : al2; - const Align &anchor = is_mate1_anchored_irr ? al2 : al1; - - ++numAnchoredIrrs; - RepeatAlign rep_align; - rep_align.read.name = irr.name; - rep_align.read.bases = irr.bases; - rep_align.read.quals = irr.quals; - rep_align.left_flank_len = 0; - rep_align.right_flank_len = 0; - rep_align.type = RepeatAlign::Type::kAnchored; - const int unit_len = units_shifts[0][0].length(); - rep_align.size = rep_align.read.bases.length() / unit_len; - rep_align.mate.bases = anchor.bases; - rep_align.mate.quals = anchor.quals; - anchored_irrs->push_back(rep_align); - } - } - } -} -} // namespace ehunter diff --git a/src/json_output.cc b/src/json_output.cc deleted file mode 100644 index 95678a6..0000000 --- a/src/json_output.cc +++ /dev/null @@ -1,125 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include "include/json_output.h" - -#include -#include -#include -#include -#include - -#include -#include "third_party/json/json.hpp" - -#include "common/parameters.h" -#include "common/repeat_spec.h" - -using std::vector; -using std::ostream; -using std::string; -using std::map; -using std::cerr; -using std::endl; - -using json = nlohmann::json; - -namespace ehunter { -void WriteJson(const Parameters ¶meters, - const map &repeat_specs, - const vector &sample_findings, ostream &out) { - json results_json = json({}); - - for (const auto ®ion_findings : sample_findings) { - const RepeatSpec &repeat_spec = repeat_specs.at(region_findings.region_id); - const string unit_encoding = boost::algorithm::join(repeat_spec.units, "/"); - - // Encode GenotypeRepeat. - vector genotype_encoding_vec, genotype_ci_encoding_vec; - for (const RepeatAllele allele : region_findings.genotype) { - genotype_encoding_vec.push_back(std::to_string(allele.size_)); - genotype_ci_encoding_vec.push_back(allele.ci_.ToString()); - } - - // Encode GenotypeRepeat CI. - const string genotype_ci_encoding = - boost::algorithm::join(genotype_ci_encoding_vec, "/"); - const string genotype_encoding = - boost::algorithm::join(genotype_encoding_vec, "/"); - - // Encode support vector. - vector genotype_support_encoding_vec; - for (const RepeatAllele &allele : region_findings.genotype) { - genotype_support_encoding_vec.push_back(allele.support_.ToString()); - } - const string genotype_support_encoding = - boost::algorithm::join(genotype_support_encoding_vec, "/"); - - results_json[region_findings.region_id] = { - {"RepeatId", repeat_spec.repeat_id}, - {"RepeatUnit", unit_encoding}, - {"TargetRegion", repeat_spec.target_region.ToString()}, - {"Genotype", genotype_encoding}, - {"GenotypeCi", genotype_ci_encoding}, - {"GenotypeSupport", genotype_support_encoding}, - {"AnchoredIrrCount", region_findings.num_anchored_irrs}, - {"UnalignedIrrCount", region_findings.num_unaligned_irrs}, - {"IrrCount", region_findings.num_irrs}}; - - // Add offtarget read counts if they exist. - if (!repeat_spec.offtarget_regions.empty()) { - assert(repeat_spec.offtarget_regions.size() == - region_findings.offtarget_irr_counts.size()); - auto &offtarget_section = - results_json[region_findings.region_id]["OffTargetRegionIrrCounts"]; - for (int i = 0; i != (int)repeat_spec.offtarget_regions.size(); ++i) { - offtarget_section[repeat_spec.offtarget_regions[i].ToString()] = - region_findings.offtarget_irr_counts[i]; - } - } - - // Add detected repeats. - if (!region_findings.read_groups.empty()) { - auto &repeat_section = - results_json[region_findings.region_id]["RepeatSizes"]; - int num_repeat = 1; - vector read_groups = region_findings.read_groups; - std::sort(read_groups.begin(), read_groups.end(), - CompareReadGroupsBySize); - for (const RepeatReadGroup &read_group : read_groups) { - const string repeat_id = "Repeat" + std::to_string(num_repeat); - repeat_section[repeat_id] = { - {"Size", read_group.size}, - {"Source", kReadTypeToString.at(read_group.read_type)}, - {"NumSupportingReads", read_group.num_supporting_reads}}; - - ++num_repeat; - } - } - } - - results_json["BamStats"]["ReadLength"] = parameters.read_len(); - results_json["BamStats"]["MedianDepth"] = parameters.depth(); - - out << results_json.dump(4); -} -} // namespace ehunter diff --git a/src/read_alignment.cc b/src/read_alignment.cc deleted file mode 100644 index aca9f09..0000000 --- a/src/read_alignment.cc +++ /dev/null @@ -1,86 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include -using std::string; -#include - -#include "htslib/hts.h" -#include "htslib/sam.h" - -#include "include/read_alignment.h" - -namespace ehunter { -bool GetAlignFromHtsAlign(bam1_t* hts_align_ptr, Align& align, - bool assumeUnaligned) { - align.name = bam_get_qname(hts_align_ptr); - align.flag = hts_align_ptr->core.flag; - - align.status = kNoCheck; - if (assumeUnaligned) { - align.chrom_id = -1; // since unaligned - align.pos = -1; // since unaligned - align.mapq = 0; // since unaligned - align.mate_chrom_id = -1; // since unaligned - align.mate_pos = -1; // since unaligned - } else { - align.chrom_id = hts_align_ptr->core.tid; - align.pos = hts_align_ptr->core.pos; - align.mapq = hts_align_ptr->core.qual; - - align.mate_chrom_id = hts_align_ptr->core.mtid; - align.mate_pos = hts_align_ptr->core.mpos; - } - - GetBasesFromHtsAlign(hts_align_ptr, align.bases); - align.len = align.bases.length(); - GetQualsFromHtsAlign(hts_align_ptr, align.quals); - - return true; -} - -bool GetQualsFromHtsAlign(bam1_t* hts_align_ptr, string& quals) { - uint8_t* hts_quals_ptr = bam_get_qual(hts_align_ptr); - const int32_t read_len = hts_align_ptr->core.l_qseq; - quals.resize(read_len); - - uint8_t* test_hts_quals_ptr = hts_quals_ptr; - - for (int32_t i = 0; i < read_len; ++i) { - quals[i] = 33 + test_hts_quals_ptr[i]; - } - - return true; -} - -bool GetBasesFromHtsAlign(bam1_t* hts_align_ptr, string& bases) { - uint8_t* hts_seq_ptr = bam_get_seq(hts_align_ptr); - const int32_t read_len = hts_align_ptr->core.l_qseq; - bases.resize(read_len); - - for (int32_t i = 0; i < read_len; ++i) { - bases[i] = seq_nt16_str[bam_seqi(hts_seq_ptr, i)]; - } - - return true; -} -} // namespace ehunter diff --git a/src/read_group.cc b/src/read_group.cc deleted file mode 100644 index 89e020e..0000000 --- a/src/read_group.cc +++ /dev/null @@ -1,441 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include "include/read_group.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "common/genomic_region.h" -#include "common/parameters.h" -#include "common/repeat_spec.h" -#include "purity/purity.h" - -using std::cerr; -using std::endl; -using std::ostream; -using std::string; -using std::vector; -using std::pair; -using std::map; -using std::array; - -using boost::property_tree::ptree; -// using boost::algorithm::split; -// using boost::algorithm::is_any_of; - -namespace ehunter { -bool CompareReadGroupsBySize(const RepeatReadGroup &a1, - const RepeatReadGroup &a2) { - return a1.size < a2.size; -} - -void CoalesceFlankingReads(const RepeatSpec &repeat_spec, - vector &repeats, - vector *flanking_repaligns, - int motif_len, - const vector> &units_shifts, - int min_baseq, double min_wp_score) { - const string &left_flank = repeat_spec.left_flank; - const string &right_flank = repeat_spec.right_flank; - - int longest_spanning = 0; - for (const RepeatReadGroup &read_group : repeats) { - if (read_group.read_type == ReadType::kSpanning) { - if (read_group.size > longest_spanning) { - longest_spanning = read_group.size; - } - } - } - - // cerr << "\t[Longest spanning allele has size " << longest_spanning << "]" - // << endl; - - bool good_repeat_exists = false; - int num_reads_from_unseen_allele = 0; - int longest_flanking = 0; - - // cerr << "\t[There are " << flanking_repaligns->size() << " flanking reads]" - // << endl; - - vector good_flanking_repaligns; - - for (const auto &rep_align : *flanking_repaligns) { - if ((int)rep_align.size > longest_spanning) { - ++num_reads_from_unseen_allele; - - string piece_bases, piece_quals; - - double piece_wp_score = 0; - double flank_wp = 0; - - if (rep_align.left_flank_len) { - const string bases_prefix = - rep_align.read.bases.substr(0, rep_align.left_flank_len); - const string quals_prefix = - rep_align.read.quals.substr(0, rep_align.left_flank_len); - const string left_flank_pref = - left_flank.substr(left_flank.length() - rep_align.left_flank_len, - rep_align.left_flank_len); - const vector left_flank_pref_units = {left_flank_pref}; - double flank_score = - MatchUnits(left_flank_pref_units, bases_prefix.begin(), - bases_prefix.end(), quals_prefix.begin(), min_baseq); - flank_wp = flank_score / rep_align.left_flank_len; - - const int piece_start = - rep_align.left_flank_len + longest_spanning * motif_len; - assert(piece_start < (int)rep_align.read.bases.length()); - piece_bases = rep_align.read.bases.substr( - piece_start, rep_align.read.bases.length() - piece_start); - piece_quals = rep_align.read.quals.substr( - piece_start, rep_align.read.bases.length() - piece_start); - const vector &units = units_shifts[0]; - piece_wp_score = - MatchRepeat(units, piece_bases, piece_quals, min_baseq); - } else { - assert(rep_align.right_flank_len); - const string bases_suffix = rep_align.read.bases.substr( - rep_align.read.bases.length() - rep_align.right_flank_len, - rep_align.right_flank_len); - const string quals_suffix = rep_align.read.quals.substr( - rep_align.read.quals.length() - rep_align.right_flank_len, - rep_align.right_flank_len); - const string right_flank_pref = - right_flank.substr(0, rep_align.right_flank_len); - const vector right_flank_pref_units = {right_flank_pref}; - double flank_score = - MatchUnits(right_flank_pref_units, bases_suffix.begin(), - bases_suffix.end(), quals_suffix.begin(), min_baseq); - flank_wp = flank_score / rep_align.right_flank_len; - - const int piece_end = - rep_align.right_flank_len + longest_spanning * motif_len; - piece_bases = rep_align.read.bases.substr( - 0, rep_align.read.bases.length() - piece_end); - piece_quals = rep_align.read.quals.substr( - 0, rep_align.read.bases.length() - piece_end); - const int unit_length = units_shifts[0][0].length(); - const int offset = - (unit_length - piece_bases.length() % unit_length) % unit_length; - const vector &units = units_shifts[offset]; - piece_wp_score = - MatchRepeat(units, piece_bases, piece_quals, min_baseq); - } - - // if (0.7 > flank_wp || flank_wp > 1.0) { - // cerr << "[WARNING: flank_wp = " << flank_wp << "]" << endl; - //} - - piece_wp_score /= piece_bases.length(); - - if (piece_wp_score >= min_wp_score && flank_wp >= min_wp_score) { - good_flanking_repaligns.push_back(rep_align); - good_repeat_exists = true; - if ((int)rep_align.size > longest_flanking) { - longest_flanking = rep_align.size; - } - } else { - // cerr << "\t[Discarding flanking read " << rep_align.read.name << " " - // << rep_align.read.bases << "]" << endl; - } - } else { - good_flanking_repaligns.push_back(rep_align); - } - } - - *flanking_repaligns = good_flanking_repaligns; - - if (good_repeat_exists) { - vector short_aligns; - vector supporting_aligns; - for (const RepeatAlign &rep_align : *flanking_repaligns) { - if ((int)rep_align.size > longest_spanning) { - supporting_aligns.push_back(rep_align); - } else { - short_aligns.push_back(rep_align); - } - } - *flanking_repaligns = short_aligns; - - // cerr << "\t[Found " << num_reads_from_unseen_allele - // << " flanking reads longer with long repeat]" << endl; - // cerr << "\t[longest_flanking = " << longest_flanking << "]" << endl; - - RepeatReadGroup read_group; - read_group.read_type = ReadType::kFlanking; - read_group.size = longest_flanking; - read_group.num_supporting_reads = num_reads_from_unseen_allele; - read_group.rep_aligns = supporting_aligns; - - repeats.push_back(read_group); - } -} - -struct PlotColumn { - PlotColumn(char t, char m, char b) { - top = t; - mid = m; - bot = b; - } - char top; - char mid; - char bot; -}; - -typedef vector Plot; - -static void PlotGaplessAlign(Plot &plot, const string &top, const string &bot, - const bool add_bars = true) { - assert(top.length() == bot.length()); - for (int i = 0; i < (int)top.length(); ++i) { - plot.push_back(PlotColumn( - top[i], add_bars && std::toupper(top[i]) == bot[i] ? '|' : ' ', - bot[i])); - } -} - -static void PlotToStream(std::ostream &ostrm, Plot &plot) { - // Write the rows one by one. - for (int i = 0; i < (int)plot.size(); ++i) { - ostrm << plot[i].top; - } - ostrm << std::endl; - for (int i = 0; i < (int)plot.size(); ++i) { - ostrm << plot[i].mid; - } - ostrm << std::endl; - for (int i = 0; i < (int)plot.size(); ++i) { - ostrm << plot[i].bot; - } - ostrm << std::endl; -} - -static void PlotSpanningAlign(Plot &plot, const string &read_seq, - const string &refPrefix, const string &refSuffix, - const int prefLen, const int suffLen) { - const string ref_pref = - refPrefix.substr(refPrefix.length() - prefLen, prefLen); - const string ref_mid = string(read_seq.length() - suffLen - prefLen, 'R'); - const string ref_suff = refSuffix.substr(0, suffLen); - - PlotGaplessAlign(plot, read_seq, ref_pref + ref_mid + ref_suff); -} - -static string LowerLowqualBases(const string &bases, const string &quals, - int lowqual_cutoff) { - assert(bases.length() == quals.length()); - string cased_bases; - for (int i = 0; i != (int)bases.length(); ++i) { - if (quals[i] - 33 < lowqual_cutoff) { - cased_bases += std::tolower(bases[i]); - } else { - cased_bases += bases[i]; - } - } - return cased_bases; -} - -void OutputRepeatAligns(const Parameters ¶meters, - const RepeatSpec &repeat_spec, - const vector &read_groups, - const vector &flanking_repaligns, - ostream *out) { - const string &left_flank = repeat_spec.left_flank; - const string &right_flank = repeat_spec.right_flank; - - *out << repeat_spec.repeat_id << ":" << endl; - - for (const RepeatReadGroup &read_group : read_groups) { - *out << " " << kReadTypeToString.at(read_group.read_type) << "_" - << read_group.size << ":" << endl; - for (const RepeatAlign &rep_align : read_group.rep_aligns) { - *out << " -\n name: \"" << rep_align.read.name << "\"" << endl; - - if (read_group.read_type == ReadType::kSpanning || - read_group.read_type == ReadType::kFlanking) { - *out << " align: |" << endl; - Plot plot; - const string cased_based = LowerLowqualBases( - rep_align.read.bases, rep_align.read.quals, parameters.min_baseq()); - PlotGaplessAlign(plot, " ", " ", false); - PlotSpanningAlign(plot, cased_based, left_flank, right_flank, - rep_align.left_flank_len, rep_align.right_flank_len); - PlotToStream(*out, plot); - } else if (read_group.read_type == ReadType::kInrepeat) { - const string read_bases = LowerLowqualBases( - rep_align.read.bases, rep_align.read.quals, parameters.min_baseq()); - const string mate_bases = LowerLowqualBases( - rep_align.mate.bases, rep_align.mate.quals, parameters.min_baseq()); - - if (rep_align.type == RepeatAlign::Type::kAnchored) { - *out << " irr: " << read_bases << endl; - *out << " anc: " << mate_bases << endl; - } else if (rep_align.type == RepeatAlign::Type::kAlignedIrrPair) { - *out << " al_ir1: " << read_bases << endl; - *out << " al_ir2: " << mate_bases << endl; - } else if (rep_align.type == RepeatAlign::Type::kUnalignedIrrPair) { - *out << " un_ir1: " << read_bases << endl; - *out << " un_ir2: " << mate_bases << endl; - } else if (rep_align.type == - RepeatAlign::Type::kUnalignedIrrSingleton) { - *out << " un_ir: " << read_bases << endl; - *out << " un_ma: " << mate_bases << endl; - } - } else { - throw std::logic_error("Unknown repeat allele type"); - } - } - } - - if (!flanking_repaligns.empty()) { - *out << " FLANKING:" << endl; - for (const RepeatAlign &rep_align : flanking_repaligns) { - *out << " -\n name: \"" << rep_align.read.name << "\"" << endl; - *out << " align: |" << endl; - Plot plot; - const string cased_based = LowerLowqualBases( - rep_align.read.bases, rep_align.read.quals, parameters.min_baseq()); - PlotGaplessAlign(plot, " ", " ", false); - PlotSpanningAlign(plot, cased_based, left_flank, right_flank, - rep_align.left_flank_len, rep_align.right_flank_len); - PlotToStream(*out, plot); - } - } - *out << endl; -} - -// Attempt to reclassify flanking reads as spanning. -void DistributeFlankingReads(const Parameters ¶meters, - const RepeatSpec &repeat_spec, - vector *read_groups, - vector *flanking_repaligns) { - const int unit_len = repeat_spec.units_shifts[0][0].length(); - std::sort(read_groups->rbegin(), read_groups->rend(), - CompareReadGroupsBySize); - const string &left_flank = repeat_spec.left_flank; - const string &right_flank = repeat_spec.right_flank; - const double kWpCutoff = 0.8; - - vector filtered_flanking_repaligns; - - for (RepeatAlign &rep_align : *flanking_repaligns) { - const string &bases = rep_align.read.bases; - const string &quals = rep_align.read.quals; - const int non_rep_len = - rep_align.left_flank_len + rep_align.right_flank_len; - assert((int)bases.length() >= non_rep_len); - const int repeat_len = bases.length() - non_rep_len; - - bool found_align = false; - - for (RepeatReadGroup &read_group : *read_groups) { - const int allele_len = read_group.size * unit_len; - if (repeat_len > allele_len) { - if (rep_align.left_flank_len) { - assert(!rep_align.right_flank_len); - const int prefix_len = rep_align.left_flank_len + allele_len; - const string bases_suffix = - bases.substr(prefix_len, bases.length() - prefix_len); - const string quals_suffix = - quals.substr(prefix_len, quals.length() - prefix_len); - - const string right_flank_ref = - right_flank.substr(0, bases_suffix.length()); - const vector right_flank_ref_units = {right_flank_ref}; - float right_flank_score = MatchUnits( - right_flank_ref_units, bases_suffix.begin(), bases_suffix.end(), - quals_suffix.begin(), parameters.min_baseq()); - if (right_flank_score / bases_suffix.length() >= kWpCutoff) { - // cerr << "[Reasign flanking to spanning]" << endl; - // Plot plot; - // const string cased_bases = - // LowerLowqualBases(bases, quals, parameters.min_baseq()); - // PlotSpanningAlign(plot, cased_bases, left_flank, right_flank, - // rep_align.left_flank_len, - // bases_suffix.length()); - // PlotToStream(cerr, plot); - // cerr << endl; - - found_align = true; - rep_align.right_flank_len = bases_suffix.length(); - } - } else if (rep_align.right_flank_len) { - assert(!rep_align.left_flank_len); - const int suffix_len = rep_align.right_flank_len + allele_len; - const string bases_prefix = - bases.substr(0, bases.length() - suffix_len); - const string quals_prefix = - quals.substr(0, quals.length() - suffix_len); - - const string left_flank_ref = - left_flank.substr(left_flank.length() - bases_prefix.length(), - bases_prefix.length()); - const vector left_flank_ref_units = {left_flank_ref}; - double left_flank_score = MatchUnits( - left_flank_ref_units, bases_prefix.begin(), bases_prefix.end(), - quals_prefix.begin(), parameters.min_baseq()); - - if (left_flank_score / bases_prefix.length() >= kWpCutoff) { - // cerr << "[Reasign flanking to spanning]" << endl; - // Plot plot; - // const string cased_bases = - // LowerLowqualBases(bases, quals, parameters.min_baseq()); - // PlotSpanningAlign(plot, cased_bases, left_flank, right_flank, - // bases_prefix.length(), - // rep_align.right_flank_len); - // PlotToStream(cerr, plot); - // cerr << endl; - - found_align = true; - rep_align.left_flank_len = bases_prefix.length(); - } - } - if (found_align) { - rep_align.type = RepeatAlign::Type::kSpanning; - rep_align.size = read_group.size; - read_group.rep_aligns.push_back(rep_align); - break; - } - } - } - - if (!found_align) { - filtered_flanking_repaligns.push_back(rep_align); - } - } - *flanking_repaligns = filtered_flanking_repaligns; -} -} // namespace ehunter diff --git a/src/vcf_output.cc b/src/vcf_output.cc deleted file mode 100644 index 3d7f067..0000000 --- a/src/vcf_output.cc +++ /dev/null @@ -1,167 +0,0 @@ -// -// Expansion Hunter -// Copyright (c) 2016 Illumina, Inc. -// -// Author: Egor Dolzhenko , -// Mitch Bekritsky , Richard Shaw -// Concept: Michael Eberle -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// - -#include "include/vcf_output.h" - -#include -#include -#include -#include -#include -#include - -#include - -using std::cerr; -using std::endl; -using std::map; -using std::ostream; -using std::string; -using std::vector; - -namespace ehunter { -void WriteVcf(const Parameters ¶meters, - const std::map &repeat_specs, - const std::vector &sample_findings, - std::ostream &out) { - std::stringstream vcf_header, vcf_body; - // clang-format off - vcf_header << - "##fileformat=VCFv4.1\n" - "##INFO=\n" - "##INFO=\n" - "##INFO=\n" - "##INFO=\n" - "##INFO=\n" - "##INFO=\n" - "##FORMAT=\n" - "##FORMAT=\n" - "##FORMAT=\n" - "##FORMAT=\n" - "##FORMAT=\n" - "##FORMAT=\n" - "##FORMAT=\n"; - // clang-format on - - std::set alt_sizes; - - for (const auto ®ion_findings : sample_findings) { - const string ®ion_id = region_findings.region_id; - const RepeatSpec &repeat_spec = repeat_specs.at(region_id); - - const string ref_field(1, repeat_spec.LeftFlankBase()); - const int unit_len = repeat_spec.units[0].length(); - const int reference_size = repeat_spec.ref_seq.length() / unit_len; - - const string unit_encoding = boost::algorithm::join(repeat_spec.units, "/"); - - string alt; - int num_alt_allele = 0; - - string sample_gt, sample_so, sample_cn, sample_ci, sample_ad_sp, - sample_ad_fl, sample_ad_ir; - - for (const RepeatAllele allele : region_findings.genotype) { - const string source = kReadTypeToString.at(allele.type_); - - if (allele.size_ != reference_size) { - alt_sizes.insert(allele.size_); - const bool is_hom_diploid_genotype = - region_findings.genotype.size() == 2 && - region_findings.genotype[0] == region_findings.genotype[1]; - - const string allele_symbol = - ""; - if (alt.empty()) { - alt = allele_symbol; - } else if (!is_hom_diploid_genotype) { - alt += "," + allele_symbol; - } - - if (!sample_gt.empty()) { - sample_gt += "/"; - sample_so += "/"; - sample_cn += "/"; - sample_ci += "/"; - sample_ad_sp += "/"; - sample_ad_fl += "/"; - sample_ad_ir += "/"; - } - if (num_alt_allele != 1 || !is_hom_diploid_genotype) { - ++num_alt_allele; - } - sample_gt += std::to_string(num_alt_allele); - sample_so += source; - sample_cn += std::to_string(allele.size_); - sample_ci += allele.ci_.ToString(); - sample_ad_sp += std::to_string(allele.support_.num_spanning()); - sample_ad_fl += std::to_string(allele.support_.num_flanking()); - sample_ad_ir += std::to_string(allele.support_.num_inrepeat()); - } else { - if (!sample_gt.empty()) { - sample_gt = "/" + sample_gt; - sample_so = "/" + sample_so; - sample_cn = "/" + sample_cn; - sample_ci = "/" + sample_ci; - sample_ad_sp = "/" + sample_ad_sp; - sample_ad_fl = "/" + sample_ad_fl; - sample_ad_ir = "/" + sample_ad_ir; - } - sample_gt = "0" + sample_gt; - sample_so = source + sample_so; - sample_cn = std::to_string(allele.size_) + sample_cn; - sample_ci = allele.ci_.ToString() + sample_ci; - sample_ad_sp = - std::to_string(allele.support_.num_spanning()) + sample_ad_sp; - sample_ad_fl = - std::to_string(allele.support_.num_flanking()) + sample_ad_fl; - sample_ad_ir = - std::to_string(allele.support_.num_inrepeat()) + sample_ad_ir; - } - } - const Region ®ion = repeat_spec.target_region; - const string info = "END=" + std::to_string(region.end()) + - ";REF=" + std::to_string(reference_size) + - ";RL=" + std::to_string(reference_size * unit_len) + - ";RU=" + unit_encoding + ";REPID=" + region_id; - if (alt.empty()) { - alt = "."; - } - - vcf_body << region.chrom() << "\t" << region.start() - 1 << "\t.\t" - << ref_field << "\t" << alt << "\t.\tPASS\t" << info - << "\tGT:SO:REPCN:REPCI:ADSP:ADFL:ADIR\t" << sample_gt << ":" - << sample_so << ":" << sample_cn << ":" << sample_ci << ":" - << sample_ad_sp << ":" << sample_ad_fl << ":" << sample_ad_ir - << endl; - } - - for (int size : alt_sizes) { - vcf_header << "##ALT=\n"; - } - vcf_header << "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO\tFORMAT\t" - << parameters.sample_name() << endl; - out << vcf_header.str() << vcf_body.str(); -} -} // namespace ehunter diff --git a/stats/CMakeLists.txt b/stats/CMakeLists.txt new file mode 100755 index 0000000..ae5f116 --- /dev/null +++ b/stats/CMakeLists.txt @@ -0,0 +1,4 @@ +file(GLOB SOURCES "*.cpp") +add_library(stats ${SOURCES}) +target_link_libraries(stats common) +add_subdirectory(tests) diff --git a/stats/ReadSupportCalculator.cpp b/stats/ReadSupportCalculator.cpp new file mode 100755 index 0000000..21835b8 --- /dev/null +++ b/stats/ReadSupportCalculator.cpp @@ -0,0 +1,57 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "stats/ReadSupportCalculator.hh" + +#include + +namespace ehunter +{ + +int ReadSupportCalculator::getCountOfConsistentSpanningReads(int haplotypeSize) const +{ + return spanningReadCounts_.countOf(haplotypeSize); +} + +int ReadSupportCalculator::getCountOfConsistentFlankingReads(int haplotypeSize) const +{ + const int cappedHaplotypeSize = std::min(haplotypeSize, maxUnitsInRead_ - 1); + + int readCount = 0; + for (int size = cappedHaplotypeSize; size != -1; --size) + { + readCount += flankingReadCounts_.countOf(size); + } + + return readCount; +} + +int ReadSupportCalculator::getCountOfConsistentRepeatReads(int haplotypeSize) const +{ + if (haplotypeSize == maxUnitsInRead_) + { + return flankingReadCounts_.countOf(haplotypeSize); + } + + return 0; +} + +} diff --git a/stats/ReadSupportCalculator.hh b/stats/ReadSupportCalculator.hh new file mode 100755 index 0000000..94adff4 --- /dev/null +++ b/stats/ReadSupportCalculator.hh @@ -0,0 +1,58 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once + +#include + +#include "common/CountTable.hh" + +namespace ehunter +{ + +// Determines counts of informative reads consistent with a given repeat length +class ReadSupportCalculator +{ +public: + ReadSupportCalculator( + int maxUnitsInRead, const CountTable& spanningReadCounts, const CountTable& flankingReadCounts) + : maxUnitsInRead_(maxUnitsInRead) + , spanningReadCounts_(spanningReadCounts) + , flankingReadCounts_(flankingReadCounts) + + { + assert(maxUnitsInRead_ > 0); + } + + // A spanning read is consistent with the given repeat allele if it spans same number of repeat units + int getCountOfConsistentSpanningReads(int haplotypeSize) const; + // A flanking read is consistent with the given repeat allele if it spans the same or fewer number of repeat units + int getCountOfConsistentFlankingReads(int haplotypeSize) const; + // Reports the number of in-repeat reads if the repeat allele is longer than the read length + int getCountOfConsistentRepeatReads(int haplotypeSize) const; + +private: + const int maxUnitsInRead_; + const CountTable& spanningReadCounts_; + const CountTable& flankingReadCounts_; +}; + +} diff --git a/stats/WeightedPurityCalculator.cpp b/stats/WeightedPurityCalculator.cpp new file mode 100755 index 0000000..636a015 --- /dev/null +++ b/stats/WeightedPurityCalculator.cpp @@ -0,0 +1,171 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "stats/WeightedPurityCalculator.hh" + +#include +#include +#include +#include + +#include + +#include "graphutils/SequenceOperations.hh" + +using std::string; +using std::to_string; +using std::vector; + +namespace ehunter +{ +namespace irrdetection +{ + using BaseCode = unsigned char; + // const int kMaxBaseCode = 15; + + // Core base codes + const BaseCode A = 0; + const BaseCode a = 1; + const BaseCode C = 2; + const BaseCode c = 3; + const BaseCode G = 4; + const BaseCode g = 5; + const BaseCode T = 6; + const BaseCode t = 7; + const BaseCode X = 8; + + // Degenerate base codes + const BaseCode B = 9; + const BaseCode D = 10; + const BaseCode H = 11; + const BaseCode K = 12; + const BaseCode M = 13; + const BaseCode N = 14; + const BaseCode R = 15; + const BaseCode S = 16; + const BaseCode V = 17; + const BaseCode W = 18; + const BaseCode Y = 19; + + const int kMaxQueryBaseCode = 8; + const int kMaxReferenceBaseCode = 19; + + const int maxBaseAscii = 255; + + const std::array kReferenceBaseEncodingTable + = { X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, A, B, C, D, X, X, G, H, X, X, K, X, M, N, X, X, X, R, S, T, X, V, W, X, Y, X, X, X, X, X, X, + X, A, X, C, X, X, X, G, X, X, X, X, X, X, X, X, X, X, X, X, T, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X }; + + const std::array kQueryBaseEncodingTable + = { X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, A, X, C, X, X, X, G, X, X, X, X, X, X, X, X, X, X, X, X, T, X, X, X, X, X, X, X, X, X, X, X, + X, a, X, c, X, X, X, g, X, X, X, X, X, X, X, X, X, X, X, X, t, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X }; + + const std::array, kMaxReferenceBaseCode + 1> + kReferenceQueryCodeScoreLookupTable = { + // clang-format off + // A a C c G g T t X + {{ 1.0, 1.0, -1.0, 0.5, -1.0, 0.5, -1.0, 0.5, -1.0}, // A + { 1.0, 1.0, -1.0, 0.5, -1.0, 0.5, -1.0, 0.5, -1.0}, // a + {-1.0, 0.5, 1.0, 1.0, -1.0, 0.5, -1.0, 0.5, -1.0}, // C + {-1.0, 0.5, 1.0, 1.0, -1.0, 0.5, -1.0, 0.5, -1.0}, // c + {-1.0, 0.5, -1.0, 0.5, 1.0, 1.0, -1.0, 0.5, -1.0}, // G + {-1.0, 0.5, -1.0, 0.5, 1.0, 1.0, -1.0, 0.5, -1.0}, // g + {-1.0, 0.5, -1.0, 0.5, -1.0, 0.5, 1.0, 1.0, -1.0}, // T + {-1.0, 0.5, -1.0, 0.5, -1.0, 0.5, 1.0, 1.0, -1.0}, // t + {-1.0, 0.5, -1.0, 0.5, -1.0, 0.5, -1.0, 0.5, -1.0}, // X + {-1.0, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0}, // B + { 1.0, 1.0, -1.0, 0.5, 1.0, 1.0, 1.0, 1.0, -1.0}, // D + { 1.0, 1.0, 1.0, 1.0, -1.0, 0.5, 1.0, 1.0, -1.0}, // H + {-1.0, 0.5, -1.0, 0.5, 1.0, 1.0, 1.0, 1.0, -1.0}, // K + { 1.0, 1.0, 1.0, 1.0, -1.0, 0.5, -1.0, 0.5, -1.0}, // M + { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0}, // N + { 1.0, 1.0, -1.0, 0.5, 1.0, 1.0, -1.0, 0.5, -1.0}, // R + {-1.0, 0.5, 1.0, 1.0, 1.0, 1.0, -1.0, 0.5, -1.0}, // S + { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 0.5, -1.0}, // V + { 1.0, 1.0, -1.0, 0.5, -1.0, 0.5, 1.0, 1.0, -1.0}, // W + {-1.0, 0.5, 1.0, 1.0, -1.0, 0.5, 1.0, 1.0, -1.0}} // Y + // clang-format on + }; + + static inline double scoreBases(char referenceBase, char queryBase) + { + return kReferenceQueryCodeScoreLookupTable[kReferenceBaseEncodingTable[referenceBase]] + [kQueryBaseEncodingTable[queryBase]]; + } +} + +WeightedPurityCalculator::WeightedPurityCalculator(const std::string& repeatUnit) +{ + repeatUnits_ = computeCircularPermutations(repeatUnit); + const string repeatUnitRc = graphtools::reverseComplement(repeatUnit); + auto permutationsRc = computeCircularPermutations(repeatUnitRc); + repeatUnits_.insert(repeatUnits_.end(), permutationsRc.begin(), permutationsRc.end()); +} + +double WeightedPurityCalculator::score(const string& querySequence) const +{ + vector scores; + for (const auto& repeatUnit : repeatUnits_) + { + scores.push_back(score(repeatUnit, querySequence)); + } + + const double weightedPurity + = *std::max_element(scores.begin(), scores.end()) / static_cast(querySequence.length()); + return weightedPurity; +} + +double WeightedPurityCalculator::score(const std::string& repeatUnit, const std::string& querySequence) const +{ + double score = 0; + for (int position = 0; position != static_cast(querySequence.length()); ++position) + { + const int offset = position % repeatUnit.length(); + score += irrdetection::scoreBases(repeatUnit[offset], querySequence[position]); + } + + return score; +} + +vector WeightedPurityCalculator::computeCircularPermutations(string sequence) const +{ + vector permutations = { sequence }; + for (int rotationNum = 0; rotationNum != static_cast(sequence.length()) - 1; ++rotationNum) + { + std::rotate(sequence.begin(), sequence.begin() + 1, sequence.end()); + permutations.push_back(sequence); + } + + return permutations; +} + +} diff --git a/stats/WeightedPurityCalculator.hh b/stats/WeightedPurityCalculator.hh new file mode 100755 index 0000000..4f38389 --- /dev/null +++ b/stats/WeightedPurityCalculator.hh @@ -0,0 +1,40 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#pragma once +#include +#include + +namespace ehunter +{ + +class WeightedPurityCalculator +{ +public: + WeightedPurityCalculator(const std::string& repeatUnit); + double score(const std::string& querySequence) const; + +private: + double score(const std::string& repeatUnit, const std::string& querySequence) const; + std::vector computeCircularPermutations(std::string sequence) const; + std::vector repeatUnits_; +}; + +} diff --git a/stats/tests/CMakeLists.txt b/stats/tests/CMakeLists.txt new file mode 100755 index 0000000..cc3a828 --- /dev/null +++ b/stats/tests/CMakeLists.txt @@ -0,0 +1,7 @@ +add_executable(ReadSupportCalculatorTest ReadSupportCalculatorTest.cpp) +target_link_libraries(ReadSupportCalculatorTest stats gtest_main) +add_test(NAME ReadSupportCalculatorTest COMMAND ReadSupportCalculatorTest) + +add_executable(WeightedPurityCalculatorTest WeightedPurityCalculatorTest.cpp) +target_link_libraries(WeightedPurityCalculatorTest stats gtest gmock_main) +add_test(NAME WeightedPurityCalculatorTest COMMAND WeightedPurityCalculatorTest) diff --git a/stats/tests/ReadSupportCalculatorTest.cpp b/stats/tests/ReadSupportCalculatorTest.cpp new file mode 100755 index 0000000..88bfaf7 --- /dev/null +++ b/stats/tests/ReadSupportCalculatorTest.cpp @@ -0,0 +1,53 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko , +// Concept: Michael Eberle +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "stats/ReadSupportCalculator.hh" + +#include "gtest/gtest.h" + +#include "common/Common.hh" + +using namespace ehunter; + +TEST(CalculatingCountsOfReadsConsistentWithHaplotype, TypicalCountTables_SupportCalculated) +{ + int maxUnitsInRead = 12; + CountTable spanningReadCounts({ { 3, 2 }, { 5, 10 } }); + CountTable flankingReadCounts({ { 2, 5 }, { 7, 3 }, { 12, 15 } }); + + ReadSupportCalculator readSupportCalculator(maxUnitsInRead, spanningReadCounts, flankingReadCounts); + + EXPECT_EQ(0, readSupportCalculator.getCountOfConsistentSpanningReads(2)); + EXPECT_EQ(2, readSupportCalculator.getCountOfConsistentSpanningReads(3)); + EXPECT_EQ(0, readSupportCalculator.getCountOfConsistentSpanningReads(4)); + EXPECT_EQ(10, readSupportCalculator.getCountOfConsistentSpanningReads(5)); + + EXPECT_EQ(0, readSupportCalculator.getCountOfConsistentFlankingReads(1)); + EXPECT_EQ(5, readSupportCalculator.getCountOfConsistentFlankingReads(2)); + EXPECT_EQ(5, readSupportCalculator.getCountOfConsistentFlankingReads(4)); + EXPECT_EQ(8, readSupportCalculator.getCountOfConsistentFlankingReads(7)); + EXPECT_EQ(8, readSupportCalculator.getCountOfConsistentFlankingReads(8)); + EXPECT_EQ(8, readSupportCalculator.getCountOfConsistentFlankingReads(12)); + EXPECT_EQ(8, readSupportCalculator.getCountOfConsistentFlankingReads(13)); + + EXPECT_EQ(15, readSupportCalculator.getCountOfConsistentRepeatReads(12)); + EXPECT_EQ(0, readSupportCalculator.getCountOfConsistentRepeatReads(13)); +} diff --git a/stats/tests/WeightedPurityCalculatorTest.cpp b/stats/tests/WeightedPurityCalculatorTest.cpp new file mode 100755 index 0000000..6df80ed --- /dev/null +++ b/stats/tests/WeightedPurityCalculatorTest.cpp @@ -0,0 +1,45 @@ +// +// Expansion Hunter +// Copyright (c) 2018 Illumina, Inc. +// +// Author: Egor Dolzhenko +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "stats/WeightedPurityCalculator.hh" + +#include + +#include "gmock/gmock.h" + +using std::string; +using std::vector; +using testing::DoubleNear; + +using namespace ehunter; + +TEST(CalculatingWeightedPurityScore, PerfectRepeat_Calculated) +{ + const string sequence = "GGCCCCGGCCCC"; + WeightedPurityCalculator wpCalculator("GGCCGG"); + EXPECT_THAT(wpCalculator.score(sequence), DoubleNear(1.0, 0.005)); +} + +TEST(CalculatingWeightedPurityScore, ImperfectRepeat_Calculated) +{ + WeightedPurityCalculator wpCalculator("AACCCC"); + EXPECT_THAT(wpCalculator.score("ACCCCAACCCCAACCCCAACCCCAACCCCAACCCCA"), DoubleNear(1.0, 0.005)); + EXPECT_THAT(wpCalculator.score("tCCCCttCCCCttCCCCttCCCCtTCCCCttCCCCT"), DoubleNear(0.75, 0.005)); +} diff --git a/thirdparty/graph-tools-master/.clang-format b/thirdparty/graph-tools-master/.clang-format new file mode 100755 index 0000000..6c433b4 --- /dev/null +++ b/thirdparty/graph-tools-master/.clang-format @@ -0,0 +1,6 @@ +BasedOnStyle: WebKit +ColumnLimit: 120 +AlignAfterOpenBracket: AlwaysBreak +BreakBeforeBraces: Allman +BreakStringLiterals: 'true' +ReflowComments: 'true' diff --git a/thirdparty/graph-tools-master/CMakeLists.txt b/thirdparty/graph-tools-master/CMakeLists.txt new file mode 100755 index 0000000..b3a1260 --- /dev/null +++ b/thirdparty/graph-tools-master/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.5.0) + +project(graphtools CXX) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +set(BUILD_TESTS OFF CACHE BOOL "Should unit tests be built") +set(BUILD_GRAPHIO OFF CACHE BOOL "Should graphIO library be built") + +set(USE_ASAN OFF CACHE BOOL "Use clang address sanitizer") +set(USE_MSAN OFF CACHE BOOL "Use clang memory sanitizer") + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG") + +if (CMAKE_SYSTEM_PROCESSOR MATCHES "^x86_64$") + if(NOT GRAPHTOOLS_AVX2) + set(GRAPHTOOLS_VECTORIZATION "-msse4.2") + else(NOT GRAPHTOOLS_AVX2) + set(GRAPHTOOLS_VECTORIZATION "-mavx2") + endif(NOT GRAPHTOOLS_AVX2) +endif (CMAKE_SYSTEM_PROCESSOR MATCHES "^x86_64$") + +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set (GRAPHTOOLS_CXX_OPTIMIZATION_FLAGS "${GRAPHTOOLS_VECTORIZATION} -O2 -ftree-vectorize -finline-functions -fpredictive-commoning -fgcse-after-reload -funswitch-loops -ftree-slp-vectorize -fvect-cost-model -fipa-cp-clone -ftree-phiprop") +endif() + +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GRAPHTOOLS_CXX_OPTIMIZATION_FLAGS} -Wall -Werror -pedantic -Wsign-compare -Wno-missing-braces") + +if (USE_ASAN) + SET(CLANG_ASAN "-O1 -g -fsanitize=address -fno-omit-frame-pointer") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CLANG_ASAN}") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${CLANG_ASAN}") + SET(BUILD_TESTS ON) +endif () + +if (USE_MSAN) + SET(CLANG_MSAN "-O1 -g -fsanitize=memory -fno-omit-frame-pointer") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CLANG_MSAN}") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${CLANG_MSAN}") + SET(BUILD_TESTS ON) +endif () + +#find_package(Threads REQUIRED) +find_package(Boost 1.5 COMPONENTS program_options filesystem system REQUIRED) + +file(GLOB SOURCES "src/graphalign/*.cpp" "src/graphalign/*/*.cpp" "src/graphcore/*.cpp" "src/graphutils/*.cpp") +add_library(graphtools ${SOURCES}) +target_include_directories(graphtools PUBLIC include) +target_link_libraries(graphtools Boost::boost) +target_compile_features(graphtools PRIVATE cxx_range_for) + +if (BUILD_GRAPHIO) + add_subdirectory("src/graphIO") +endif (BUILD_GRAPHIO) + +if (BUILD_TESTS) + enable_testing() + # Download and unpack googletest at configure time + include(GetGoogleTest) + add_subdirectory(tests) +endif (BUILD_TESTS) \ No newline at end of file diff --git a/thirdparty/graph-tools-master/Dockerfile b/thirdparty/graph-tools-master/Dockerfile new file mode 100755 index 0000000..25f52d1 --- /dev/null +++ b/thirdparty/graph-tools-master/Dockerfile @@ -0,0 +1,33 @@ +FROM ubuntu:16.04 + +RUN apt-get -qq update && apt-get install -yq \ + wget curl software-properties-common && \ + wget -O llvm.key https://apt.llvm.org/llvm-snapshot.gpg.key && apt-key add llvm.key && rm -f llvm.key + +RUN apt-add-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-5.0 main" + +RUN apt-get -qq update && apt-get install -yq \ + build-essential \ + cmake \ + zlib1g-dev \ + libbz2-dev \ + valgrind \ + cppcheck \ + clang-5.0 \ + clang-format-5.0 \ + clang-tidy-5.0 \ + libboost-all-dev \ + && \ + apt-get clean -y && \ + rm -rf /var/lib/apt/lists/* + +RUN update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-5.0 1000 && \ + update-alternatives --install /usr/bin/clang clang /usr/bin/clang-5.0 1000 && \ + update-alternatives --config clang && \ + update-alternatives --config clang++ + +RUN apt-get -qq update && apt-get install -yq \ + git \ + && \ + apt-get clean -y && \ + rm -rf /var/lib/apt/lists/* diff --git a/thirdparty/graph-tools-master/README.md b/thirdparty/graph-tools-master/README.md new file mode 100755 index 0000000..15ea265 --- /dev/null +++ b/thirdparty/graph-tools-master/README.md @@ -0,0 +1,29 @@ +# A library for working with sequence graphs + +## Introduction + +Graph-tools library aims to provide data models and methods for working with sequence graphs and alignments to them. + +See docs/development.md for build instructions + +## Contents + +### Graph definition +- A data model for graphs in memory and as JSON: [graphs.md](docs/graphs.md) + - The graphs are DAGs, with the addition of support for self-loops on individual nodes +- Paths through graphs to define sequences, e.g. for alignments: [paths.md](docs/paths.md) +- Path Families, subsets of edges of the graph, to define haplotypes: [path_families.md](docs/path_families.md) +- Graph-to-reference mappings: Translating positions in the graph to positions in a linear reference (e.g. genome) +- Nucleotide sequence definition:[query_and_reference_sequences.md](docs/query_and_reference_sequences.md) + - Extended alphabet for degenerate bases and base quality + +### Alignment +- Methods to perform both gapped and gapless alignment of sequences to graphs, +- A rich set of heuristics for filtering alignments. + +### GraphIO +The separate GraphIO library contains methods to read and write + - Graphs from JSON + - (Linear) Reference sequences from fasta + - Graph-alignments from BAM +It depends on htslib. \ No newline at end of file diff --git a/thirdparty/graph-tools-master/RELEASES.md b/thirdparty/graph-tools-master/RELEASES.md new file mode 100755 index 0000000..bbd71c0 --- /dev/null +++ b/thirdparty/graph-tools-master/RELEASES.md @@ -0,0 +1,42 @@ +# Graph-Tools Release Notes / Change Log + +## Version 0.3.0 +| Date Y-m-d | Ticket | Description | +|------------|---------|----------------------------------------------------------------------| +| 2018-07-13 | GT-536 | Add output of graph alignments as (unmapped) BAM records | +| 2018-06-28 | GT-526 | Simple mapping of graph positions to a linear reference | +| 2018-06-26 | GT-502 | Change path coordinate system from closed to half open | + +## Version 0.2.0 +| Date Y-m-d | Ticket | Description | +|------------|---------|----------------------------------------------------------------------| +| 2018-06-26 | GT-512 | Add support for reading and writing graphs from JSON | +| 2018-06-26 | GT-505 | Add functions to reconstruct path families from paths and graphs | +| 2018-06-21 | GT-524 | Add GraphIO library | +| 2018-05-31 | GT-482 | projectAlignmentOntoGraph fix when alignments don't cover entire path| +| 2018-05-24 | GT-470 | Add function to generate paths from path families + documentation | +| 2018-05-22 | GT-458 | Fix bugs in gapped aliner and alignment comparison | +| 2018-05-20 | GT-442 | Document definitions of query and reference sequences | +| 2018-05-10 | GT-445 | Add functions for exact match extension of paths | +| 2018-05-08 | GT-416 | Add projection of graph to linear reference coordinates | +| 2018-05-04 | GT-457 | Provide a common interface for graph aligners | +| 2018-05-03 | GT-444 | Remove sequences from all alignment objects | + +## Version 0.1.1 + +| Date Y-m-d | Ticket | Description | +|------------|---------|----------------------------------------------------------------------| +| 2018-04-26 | GT-453 | Add Path Families | + +## Version 0.1.0 + +| Date Y-m-d | Ticket | Description | +|------------|---------|----------------------------------------------------------------------| +| 2018-04-16 | GT-438 | Add feature to disable graph sequence expansion | +| 2018-04-09 | GT-431 | Faster reference sequence expansion | +| 2018-04-05 | GT-397 | Add gapped alignment | +| 2018-03-29 | GT-353 | Integration issues / function ports from paragraph-tools | + +## Version 0.0.1 + +Initial release. diff --git a/thirdparty/graph-tools-master/cmake/GetGoogleTest.cmake b/thirdparty/graph-tools-master/cmake/GetGoogleTest.cmake new file mode 100755 index 0000000..2ce8a17 --- /dev/null +++ b/thirdparty/graph-tools-master/cmake/GetGoogleTest.cmake @@ -0,0 +1,44 @@ +######################### Google Test ############################ +# Download and unpack googletest at configure time + +FILE(WRITE "${CMAKE_BINARY_DIR}/external/googletest-build/CMakeLists.txt" "\ +cmake_minimum_required(VERSION 2.8.5) +project(googletest-build NONE) +include(ExternalProject) +ExternalProject_Add(googletest + # GIT_REPOSITORY https://github.com/google/googletest.git + # GIT_TAG release-1.8.0 + URL \"${CMAKE_SOURCE_DIR}/external/googletest-release-1.8.0.tar.gz\" + URL_HASH MD5=16877098823401d1bf2ed7891d7dce36 + SOURCE_DIR \"${CMAKE_BINARY_DIR}/external/googletest-src\" + BINARY_DIR \"${CMAKE_BINARY_DIR}/external/googletest-build\" + CONFIGURE_COMMAND \"\" + BUILD_COMMAND \"\" + INSTALL_COMMAND \"\" + TEST_COMMAND \"\" + )" +) + +execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/external/googletest-build ) +if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") +endif() +execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/external/googletest-build ) +if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") +endif() + +# Add googletest directly to our build. This defines +# the gtest and gtest_main targets. +add_subdirectory(${CMAKE_BINARY_DIR}/external/googletest-src + ${CMAKE_BINARY_DIR}/external/googletest-build) +#add_subdirectory(${CMAKE_BINARY_DIR}/external/googletest-src/googletest +# ${CMAKE_BINARY_DIR}/external/googletest-build-test) +#add_subdirectory(${CMAKE_BINARY_DIR}/external/googletest-src/googlemock +# ${CMAKE_BINARY_DIR}/external/googletest-build-mock) + +################################################################## \ No newline at end of file diff --git a/thirdparty/graph-tools-master/cmake/GetHtslib.cmake b/thirdparty/graph-tools-master/cmake/GetHtslib.cmake new file mode 100755 index 0000000..9c033a1 --- /dev/null +++ b/thirdparty/graph-tools-master/cmake/GetHtslib.cmake @@ -0,0 +1,58 @@ +# Download and build htslib at configure time + +set(HTSLIB_INSTALL_PATH "$ENV{HTSLIB_INSTALL_PATH}" CACHE STRING "Specify a pre-built version of htslib (install prefix).") + +set(HTSLIB_INCLUDED_VERSION "1.3.1") + +if (IS_DIRECTORY ${HTSLIB_INSTALL_PATH}) + message( "Using pre-built htslib from ${HTSLIB_INSTALL_PATH}") + message( WARNING "htslib <= 1.7 is known to leak memory with cram files") + set(HTSLIB_PATH ${HTSLIB_INSTALL_PATH}) +else() + message( "Using included htslib" ) + set(HTSLIB_PATH ${CMAKE_BINARY_DIR}/external/htslib-install) + + FILE(WRITE "${CMAKE_BINARY_DIR}/external/htslib-build/CMakeLists.txt" " + cmake_minimum_required(VERSION 2.8.5)\n + project(htslib-build NONE) + include(ExternalProject) + ExternalProject_Add(htslib + GIT_REPOSITORY \"https://github.com/samtools/htslib.git\" + GIT_CONFIG \"http.sslVerify=false\" + GIT_TAG \"${HTSLIB_INCLUDED_VERSION}\" + SOURCE_DIR \"${CMAKE_BINARY_DIR}/external/htslib-src\" + INSTALL_DIR \"${HTSLIB_PATH}\" + CONFIGURE_COMMAND \"\" + PATCH_COMMAND \"\" + BUILD_COMMAND make -C prefix= + INSTALL_COMMAND make -C install prefix= )") + + execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/external/htslib-build ) + if(result) + message(FATAL_ERROR "CMake step for htslib failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/external/htslib-build ) + if(result) + message(FATAL_ERROR "Build step for htslib failed: ${result}") + endif() +endif() + +## Define HTSlib library for downstream use + +include(FindZLIB) +include(FindBZip2) +include(FindLibLZMA) +add_library(htslib STATIC IMPORTED GLOBAL) +set_property(TARGET htslib PROPERTY INTERFACE_INCLUDE_DIRECTORIES + ${ZLIB_INCLUDE_DIR} ${BZIP2_INCLUDE_DIR} ${LIBLZMA_INCLUDE_DIRS}) +set_property(TARGET htslib APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES + ${HTSLIB_PATH}/include) +set_property(TARGET htslib PROPERTY IMPORTED_LOCATION + "${HTSLIB_PATH}/lib/libhts.a") +set_property(TARGET htslib PROPERTY INTERFACE_LINK_LIBRARIES + ${BZIP2_LIBRARIES} ${ZLIB_LIBRARIES}) + diff --git a/thirdparty/graph-tools-master/docs/alignment.md b/thirdparty/graph-tools-master/docs/alignment.md new file mode 100755 index 0000000..5275500 --- /dev/null +++ b/thirdparty/graph-tools-master/docs/alignment.md @@ -0,0 +1,37 @@ +# Graph alignment + +## Alignment methods + +## Alignment representation + +## BAM Output + +Graph-tools supports outputting alignments in BAM format (through `graphIO/BamWriter`). +Currently this does not produce full BAM alignments, but rather 'unaligned' BAM records +that are placed at the right start position in the (linear) reference genome and include +the actual alignment against the alignment encoded as a string in a custom tag. + +For this to work a projection of the graph onto a linear reference genome is needed (class `GraphReferenceMapping`). +This assigns a reference coordinate interval to graph nodes and hence allows projecting positions (and paths) in the graph to +positions in the linear reference that the BAM is based on. + +### BAM encoding for graph alignments + +The BAM record for read `R` with graph-alignment `g`: + * Qname, Seq, Qual describe the read (matching fastq input) + * Chromosome and Position are set to the first projected reference position of the graph-alignment path (`g.path`) + 1. Find first node `n` in `g.path` that has a reference mapping + 2. Set the BAM read position to the projected position of the first base of `n` + the offset of `g.path` in `n`. + * Mate Chrom and Position undefined + * Read is `unmapped`: No mapQ, CIGAR + * Flags + * read paired, first/second in pair, failedQC: Set according to fastq/input BAM + * read unmapped and mate unmapped is set + * read reverse strand is set depending on mapping to graph in forward or reverse direction + * all other flags are unset + * The actual graph alignment is encoded in custom tags + * XG: string - Graph CIGAR + * Combination of Alignment path and CIGAR against each node. + * E.g. 0[Ref start: 2, 2M]1[Ref start: 0, 1M]3[Ref start: 0, 3M] + * Alignment starts at position 2 on node 0 with 2 matches + * Continues at Node1 with 1 match and node3 with 3 matches diff --git a/thirdparty/graph-tools-master/docs/development.md b/thirdparty/graph-tools-master/docs/development.md new file mode 100755 index 0000000..d3c894e --- /dev/null +++ b/thirdparty/graph-tools-master/docs/development.md @@ -0,0 +1,31 @@ +# Building + +The following is required to build graph-tools library from source: + +- A C++ 11 compliant compiler (tested with GCC 6.3.0), +- CMake 3.5.0 or above. +- BOOST 1.5 or above + +To build the library, perform the standard out-of-source CMake build. The unit +tests are not built by default. To build the unit tests, pass `-DBUILD_TESTS=ON` +to CMake. + +To also build the included graphIO library, pass `-DBUILD_GRAPHIO=ON` to CMake. +In that case htslib is required. By default htslib is downloaded from github and build during the CMake configure step. Alternatively +set $HTSLIB_INSTALL_PATH to the path (install prefix) of an already installed htslib. +GraphIO also includes a (header-only) version of the nlohmann/json library. + +## Incorporating the library into other CMake projects + +Copy graph-tools to the source tree of your project. Assuming that the library +was placed in `thirdparty/graph-tools` directory in the project's root, add +`add_subdirectory(thirdparty/graph-tools)` and +`target_link_libraries( graphtools)` to your CMakeLists.txt file. + +# Code standards +C++ code should follow the [c++ core guidelines](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md). +Code has to pass cppcheck; see [docker-cppcheck.sh](/src/sh/docker-cppcheck.sh) for details. + +## Style guide +Naming of identifiers should match the surrounding code. +Formatting should (exactly) match the output of clang-format 5.0.1; see [format-everything.sh](/src/sh/format-everything.sh) for details. diff --git a/thirdparty/graph-tools-master/docs/graph.md b/thirdparty/graph-tools-master/docs/graph.md new file mode 100755 index 0000000..213af08 --- /dev/null +++ b/thirdparty/graph-tools-master/docs/graph.md @@ -0,0 +1,53 @@ +# Graph model + +## Definitions +The sequence graph is made up of nodes and directed edges. Each node holds a (non-empty) sequence. Edges connect +nodes and do not hold sequences themselves. A path is a sequence of nodes, where each node is connected to its successor with a directed edge. A path defines a sequence in the graph. + +## Graph class +### Files +- `include/graphcore/Graph.hh` +- `tests` + - `GraphOperationsTest.cpp` + - `GraphCoordinatesTest.cpp` + +The graph class holds nodes, edges and edge labels (used for path families). +Nodes are simple structs with a name and sequence. Within a graph each node has a unique NodeID, numbered from 0 to +graph.numNodes. +Edges are defined as pairs of NodeIDs and are stored directly within the graph using a node adjacency list. +Every edge can optionally be labeled with one or more labels. + +Graphs are allowed to contain self-loops (edges from a node to itself). Excluding self-loop edges the graph has to be a DAG. A path +may pass through such an loop-edge multiple times, defining a repeated sequence. + +## Paths +See [path](paths.md) and [path family](path_families.md) documentation. + +## Reference Mapping +A graph reference mapping (`include/Graphcore/GraphReferenceMapping.hh`) is used to define a projection of the graph onto linear reference genome coordinates. A subset of nodes +are assigned one (or possibly multiple) reference intervals. For reference nodes this would be the interval their sequence comes from +for alt. allele nodes the 'replaced' reference sequence (or position of insertion). + +These reference mappings can be stored in JSON, either together with the graph or separately. They are used to generate BAM output of alignments. + +## JSON model +Graphs can be serialized in JSON. The format is +- "nodes": array of node objects containing + - "name": string (required; not-empty) - Unique name for this node + - "sequence": string; not-empty - Nucleotide sequence of this node + - [Extended alphabet](query_and_reference_sequences.md) + - Either "sequence" or "reference" is required + - "reference": string - Region in the reference genome + - Sets the sequence of the node to match the reference region if no explicit "sequence" is given + - Region is given as a 0-based half-open interval (:-) + - Also defines this reference region as the mapping position of the node for GraphReferenceMapping +- "edges": array of edge objects containing + - "from": string - name of the start node + - "to": string - name of the end node + - "labels": array of string - edge labels applied to this edge +- "paths": array of path objects (optional) containing + - path_id: string - name of the path + - nodes: array of string - name of nodes in the path (in order) +- "graph_id": string (optional) - name for the graph +- "reference_genome": string - path to a fasta file with the reference genome sequence + - required if using "reference" sequences in node definitions diff --git a/thirdparty/graph-tools-master/docs/path_families.md b/thirdparty/graph-tools-master/docs/path_families.md new file mode 100755 index 0000000..92b69e1 --- /dev/null +++ b/thirdparty/graph-tools-master/docs/path_families.md @@ -0,0 +1,102 @@ +# Path Families + +## Files + +* [include/graphcore/PathFamily.hh](/include/graphcore/PathFamily.hh) +* [src/graphcore/PathFamily.hh](/src/graphcore/PathFamily.cpp) +* [tests/PathFamilyTest.cpp](/tests/PathFamilyTest.cpp) + +## Definitions + +If enumerating all [paths](paths.md) in a set becomes impractical, +we can define a set of paths implicitly by a set of edges. +We call sets of paths defined in this way *path families*. +We construct a path family from +a set of edges *F = \{ f1, f2, ... fl \}*. +A path *P* is in the path family *F* iff. at least one node in *P* +is connected via an edge in *F* and for any node *pn* in *P* +both of these conditions hold: + +* No incoming edge into *pn* exists in *F* or edge *(pn-1, pn)* is in *F*. +* No outgoing edge from *pn* exists in *F* or edge *(pn, pn+1)* is in *F*. + +Informally, a path family F consists of paths that enter and exit nodes +only through edges in F when such edges exist. +A path does not have to cover the entire path family +(either all nodes or all edges), but may start in the middle or +enter/exit through any edge into a node that has no incoming/outgoing +edges in *F*. + +In graph-tools, path families can be created as follows: + +```c++ +Graph G = makeDeletionGraph("TTT", "AT", "CCCCC"); +PathFamily family(&G); +// family of all possible alleles +family.addEdge(0, 1); +family.addEdge(1, 2); +family.addEdge(0, 2); +``` + +The primary operation provided by path families is a query +operation which tells us whether a given path is contained in +a path family: + +```c++ +Path path(&graph, 0, { 0, 1, 2 }, 0); +assert( family.containsPath(path) ); +``` + +We can also test if a path family includes another family: + +```c++ +PathFamily family2(&G); +family2.addEdge(0, 1); +family2.addEdge(1, 2); + +assert( family.includes(family2) ); +assert( !family2.includes(family) ); +``` +# Path Family Operations + +## Files + +* [include/graphcore/PathFamilyOperations.hh](/include/graphcore/PathFamilyOperations.hh) +* [src/graphcore/PathFamilyOperations.cpp](/src/graphcore/PathFamilyOperations.cpp) +* [tests/PathFamilyOperationsTest.cpp](/tests/PathFamilyOperationsTest.cpp) + +## Operations + +### Generate path segments in a family +```c++ +list getPathSegmentsForFamily(PathFamily const& family); +``` + +This function generates a set of maximum-length path segments for a family, +where each path that is the result of merging two path segments +is also contained in the path family. + +### Enumerate path combinations in a family + +```c++ +bool enumeratePathCombinationsInFamily( + PathFamily const& family, list const& segments, std::list* paths, size_t maxPaths); +``` + +Given a set of path segments from a family, this function +performs restricted enumeration of all paths that can be +obtained by merging path segments. + +### Generate a set of maximal paths + +```c++ +bool getMaximalPathsForFamily( + PathFamily const& family, list* paths, size_t maxPaths); +``` + +This function combines segment generation and path enumeration into a +single function call: + +* It generates all path segments for a path family. +* It then enumerates all (or a maximum number of ) merged paths from + these segments. diff --git a/thirdparty/graph-tools-master/docs/paths.md b/thirdparty/graph-tools-master/docs/paths.md new file mode 100755 index 0000000..3f8068f --- /dev/null +++ b/thirdparty/graph-tools-master/docs/paths.md @@ -0,0 +1,110 @@ +# Paths + +## Files + +* [include/graphcore/Path.hh](/include/graphcore/Path.hh) +* [src/graphcore/Path.cpp](/src/graphcore/Path.cpp) +* [tests/PathTest.cpp](/tests/PathTest.cpp) + +## Definitions + +A path *P* in a graph *G* of length *k* is given by + +* a list of nodes *{ ni for i = 1 ... k }* +* a start position on *n1* +* an end position on *nk* + +In order path *P* to be valid in *G*, we require + +* all nodes in *P* must be present in *G*, +* for all *i* in *1 ... k - 1*, *G* must have an edge + *(ni, ni+1)*, +* the start and end position must be valid given the + node sequence lengths of *n1* and *nk*. + +In graph-tools, paths can be created as follows: + +```c++ +Graph G = makeDeletionGraph("TTT", "AT", "CCCCC"); +Path P1(&G, 1, { 0, 1, 2 }, 1)); +Path P2(&G, 1, { 0, 2 }, 2)); +``` + +Path objects can be used to determine the sequence generated by +a path: + +```c++ +assert( P1.seq() == "TTATCC" ); +assert( P2.seq() == "TTCCC" ); +``` + +We can also generate human-readable encodings for paths: + +```c++ +assert( "(0@1)-(1)-(2@1)" == P1.encode() ); +assert( "(0@1)-(2@2)" == P2.encode() ); +``` + +Here, the start and end positions are encoded as *nodeId@position*. + +Other functions provided by paths include: + +* functions to test / quantify a path's overlap with a single node. +* `getDistanceFromPathStart()`: Given node *nj* and offset *o*, calculate the distance from the start of the path to position + *nj@o* if *nj* is contained in the path. +* `shrink*()` / `extend*()` / `shift*()` / `remove*()`: various modifier functions for shrinking and expanding paths at the ends. + +# Path Operations + +## Files + +* [include/graphcore/PathOperations.hh](/include/graphcore/PathOperations.hh) +* [src/graphcore/PathOperations.cpp](/src/graphcore/PathOperations.cpp) +* [tests/PathOperationsTest.cpp](/tests/PathOperationsTest.cpp) + +## Operations + +### Path Extension (`extendPath*`) + +We provide a set of functions to enumerate the possible paths generated +by extending a given path by a number of bases to the left or right. + +Furthermore, paths can also be extended while they uniquely match a given +query sequence (`extendPath*Matching*` functions). This is useful to +produce sets of maximal unique matches of a sequence on a graph from +a seed. + +### Sequence Decomposition (`splitSequenceByPath`) + +```c++ +vector splitSequenceByPath(const Path& path, const string& sequence); +``` + +This function is useful to map a sequence of the same length of a path onto +that path. It returns a list of sequences that correspond to the substring +of the input sequence mapped onto each node in the path. + +### Path Overlap and Merging + +We provide various operations to check if the union of two +paths will produce valid paths also (`checkIfPathsAdjacent`, +`checkPathPrefixSuffixOverlap`). + +If the union of two paths is a valid path, `mergePaths` can be +used to produce this union path. + +The `greedyMerge` and `exhaustiveMerge` function +aim to reduce redundancy in a set of paths by merging paths in a set +if they are compatible in the way described above. Exhaustively +merging paths can take a long time since this function attempts +to merge all possible path prefixes with all possible suffixes, +greedy merging will pick only one prefix / suffix combination. + +### Path Intersections + +```c++ +list intersectPaths(Path const& p1, Path const& p2); +``` + +This returns a set of all longest paths that are subpaths +of both p1 and p2. \ No newline at end of file diff --git a/thirdparty/graph-tools-master/docs/query_and_reference_sequences.md b/thirdparty/graph-tools-master/docs/query_and_reference_sequences.md new file mode 100755 index 0000000..b9982c8 --- /dev/null +++ b/thirdparty/graph-tools-master/docs/query_and_reference_sequences.md @@ -0,0 +1,53 @@ +# Query and reference sequences + +## Introduction + +A large part of the GraphTools library is concerned with alignment of query sequences (typically short reads) to a +reference represented by a sequence graph. This section explains the distinction between query sequences and reference +sequences that make up graphs. + +A **query sequence** must consist of characters representing core nucleotides A, C, G, T and a missing nucleotide symbol +N. The uppercase and lowercase core nucleotide characters correspond to high and low quality base calls respectively. + +Here is an example of a query sequence with many low quality base calls. + +```C++ +CCGACCACGCCCCGGCCCCcGCCCCGGCCCCcaGCGcgCGaCcCCtGaGgTcccGgGctTGCcaCaGgCcggcGgtGttTCCCcCCttgttttTTtCtg +``` + +A **reference sequence** -- a sequence of a node in a graph -- must consist of characters representing cores nucleotides +(A, C, G, T) or degenerate nucleotides (that correspond to multiple nucleotides). The following table lists the +degenerate nucleotides as defined by the International Union of Pure and Applied Chemistry. + +| Degenerate nucleotide | Corresponding core nucleotides | +|-----------------------|--------------------------------| +| R | A, G | +| Y | C, T | +| K | G, T | +| M | A, C | +| S | C, G | +| W | A, T | +| B | C, G, T | +| D | A, G, T | +| H | A, C, T | +| V | A, C, G | +| N | A, C, G, T | + +Here is an example of a reference sequence representing a trinucleotide repeat consisting of 12 Alanine codons. + +```C++ +GCNGCNGCNGCNGCNGCNGCNGCNGCNGCNGCNGCN +``` + +## Matching query and reference sequences + +The rules for matching the characters of query and reference sequences are given in the table above. Additionally, we +require that the missing nucleotide character N in the query sequences does not match any other characters. (Note the +distinction with reference N that matches A, C, G, and T.) + +To accommodate the needs of the existing clients of the GraphTools library, we allow query Ns within alignments to be a +part of mismatch operations or "missing nucleotide" operations. See the section on +[representation of alignments](representation_of_alignments.md) for more details. + +The basic machinery for matching query and reference sequences is provided in `BaseMatching.*` and +`SequenceOperations.*` files inside `graphutils`. diff --git a/thirdparty/graph-tools-master/external/googletest-release-1.8.0.tar.gz b/thirdparty/graph-tools-master/external/googletest-release-1.8.0.tar.gz new file mode 100755 index 0000000000000000000000000000000000000000..a40df33faad4a1fb308282148296ad7d0df4dd7a GIT binary patch literal 1281617 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⁢NPD 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 ~CadQ`t>M|>jd7A=j#lWID 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

|=X|#c8DH8~GEf??nl2O*Vrm05f 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"O(YGu@VNo$o7*7pGh8OX)x{@c91{Dwi^- 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 z

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

    %|BT1f++8AVV1uDTBeP=8<__to%{5@BZEl@2sHc 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 *edB{VT$bflb$L~ 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�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%0A_2i4Eu7WD#MH3+6@}d}dC@ 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. +Copyright (c) 2013-2018 Niels Lohmann . + +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. +*/ + +#ifndef NLOHMANN_JSON_HPP +#define NLOHMANN_JSON_HPP + +#define NLOHMANN_JSON_VERSION_MAJOR 3 +#define NLOHMANN_JSON_VERSION_MINOR 1 +#define NLOHMANN_JSON_VERSION_PATCH 2 + +#include // all_of, find, for_each +#include // assert +#include // and, not, or +#include // nullptr_t, ptrdiff_t, size_t +#include // hash, less +#include // initializer_list +#include // istream, ostream +#include // iterator_traits, random_access_iterator_tag +#include // accumulate +#include // string, stoi, to_string +#include // declval, forward, move, pair, swap + +// #include +#ifndef NLOHMANN_JSON_FWD_HPP +#define NLOHMANN_JSON_FWD_HPP + +#include // int64_t, uint64_t +#include // map +#include // allocator +#include // string +#include // vector + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](http://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer; + +template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer> +class basic_json; + +/*! +@brief JSON Pointer + +A JSON pointer defines a string syntax for identifying a specific value +within a JSON document. It can be used with functions `at` and +`operator[]`. Furthermore, JSON pointers are the base for JSON patches. + +@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + +@since version 2.0.0 +*/ +template +class json_pointer; + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; +} + +#endif + +// #include + + +// This file contains all internal macro definitions +// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them + +// exclude unsupported compilers +#if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif +#elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40900 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif +#endif + +// disable float-equal warnings on GCC/clang +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdocumentation" +#endif + +// allow for portable deprecation warnings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) + #define JSON_DEPRECATED __declspec(deprecated) +#else + #define JSON_DEPRECATED +#endif + +// allow to disable exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) +#else + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) +#endif + +// override exception macros +#if defined(JSON_THROW_USER) + #undef JSON_THROW + #define JSON_THROW JSON_THROW_USER +#endif +#if defined(JSON_TRY_USER) + #undef JSON_TRY + #define JSON_TRY JSON_TRY_USER +#endif +#if defined(JSON_CATCH_USER) + #undef JSON_CATCH + #define JSON_CATCH JSON_CATCH_USER +#endif + +// manual branch prediction +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_LIKELY(x) __builtin_expect(!!(x), 1) + #define JSON_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else + #define JSON_LIKELY(x) x + #define JSON_UNLIKELY(x) x +#endif + +// C++ language standard detection +#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 +#endif + +// Ugly macros to avoid uglier copy-paste when specializing basic_json. They +// may be removed in the future once the class is split. + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template class ObjectType, \ + template class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template class AllocatorType, \ + template class JSONSerializer> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json + +/*! +@brief Helper to determine whether there's a key_type for T. + +This helper is used to tell associative containers apart from other containers +such as sequence containers. For instance, `std::map` passes the test as it +contains a `mapped_type`, whereas `std::vector` fails the test. + +@sa http://stackoverflow.com/a/7728728/266378 +@since version 1.0.0, overworked in version 2.0.6 +*/ +#define NLOHMANN_JSON_HAS_HELPER(type) \ + template struct has_##type { \ + private: \ + template \ + static int detect(U &&); \ + static void detect(...); \ + public: \ + static constexpr bool value = \ + std::is_integral()))>::value; \ + } + +// #include + + +#include // not +#include // size_t +#include // numeric_limits +#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // declval + +// #include + +// #include + + +namespace nlohmann +{ +/*! +@brief detail namespace with internal helper functions + +This namespace collects functions that should not be exposed, +implementations of some @ref basic_json methods, and meta-programming helpers. + +@since version 2.1.0 +*/ +namespace detail +{ +///////////// +// helpers // +///////////// + +template struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json : std::true_type {}; + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +template +using uncvref_t = typename std::remove_cv::type>::type; + +// implementation of C++14 index_sequence and affiliates +// source: https://stackoverflow.com/a/32223343 +template +struct index_sequence +{ + using type = index_sequence; + using value_type = std::size_t; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +template +struct merge_and_renumber; + +template +struct merge_and_renumber, index_sequence> + : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; + +template +struct make_index_sequence + : merge_and_renumber < typename make_index_sequence < N / 2 >::type, + typename make_index_sequence < N - N / 2 >::type > {}; + +template<> struct make_index_sequence<0> : index_sequence<> {}; +template<> struct make_index_sequence<1> : index_sequence<0> {}; + +template +using index_sequence_for = make_index_sequence; + +/* +Implementation of two C++17 constructs: conjunction, negation. This is needed +to avoid evaluating all the traits in a condition + +For example: not std::is_same::value and has_value_type::value +will not compile when T = void (on MSVC at least). Whereas +conjunction>, has_value_type>::value will +stop evaluating if negation<...>::value == false + +Please note that those constructs must be used with caution, since symbols can +become very long quickly (which can slow down compilation and cause MSVC +internal compiler errors). Only use it when you have to (see example ahead). +*/ +template struct conjunction : std::true_type {}; +template struct conjunction : B1 {}; +template +struct conjunction : std::conditional, B1>::type {}; + +template struct negation : std::integral_constant {}; + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + +//////////////////////// +// has_/is_ functions // +//////////////////////// + +// source: https://stackoverflow.com/a/37193089/4116453 + +template +struct is_complete_type : std::false_type {}; + +template +struct is_complete_type : std::true_type {}; + +NLOHMANN_JSON_HAS_HELPER(mapped_type); +NLOHMANN_JSON_HAS_HELPER(key_type); +NLOHMANN_JSON_HAS_HELPER(value_type); +NLOHMANN_JSON_HAS_HELPER(iterator); + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl +{ + static constexpr auto value = + std::is_constructible::value and + std::is_constructible::value; +}; + +template +struct is_compatible_object_type +{ + static auto constexpr value = is_compatible_object_type_impl < + conjunction>, + has_mapped_type, + has_key_type>::value, + typename BasicJsonType::object_t, CompatibleObjectType >::value; +}; + +template +struct is_basic_json_nested_type +{ + static auto constexpr value = std::is_same::value or + std::is_same::value or + std::is_same::value or + std::is_same::value; +}; + +template +struct is_compatible_array_type +{ + static auto constexpr value = + conjunction>, + negation>, + negation>, + negation>, + has_value_type, + has_iterator>::value; +}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + std::is_constructible::value and + CompatibleLimits::is_integer and + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type +{ + static constexpr auto value = + is_compatible_integer_type_impl < + std::is_integral::value and + not std::is_same::value, + RealIntegerType, CompatibleNumberIntegerType > ::value; +}; + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json +{ + private: + // also check the return type of from_json + template::from_json( + std::declval(), std::declval()))>::value>> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json +{ + private: + template < + typename U, + typename = enable_if_t::from_json(std::declval()))>::value >> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +template +struct has_to_json +{ + private: + template::to_json( + std::declval(), std::declval()))> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +template +struct is_compatible_complete_type +{ + static constexpr bool value = + not std::is_base_of::value and + not is_basic_json::value and + not is_basic_json_nested_type::value and + has_to_json::value; +}; + +template +struct is_compatible_type + : conjunction, + is_compatible_complete_type> +{ +}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; +} +} + +// #include + + +#include // exception +#include // runtime_error +#include // to_string + +namespace nlohmann +{ +namespace detail +{ +//////////////// +// exceptions // +//////////////// + +/*! +@brief general exception of the @ref basic_json class + +This class is an extension of `std::exception` objects with a member @a id for +exception ids. It is used as the base class for all exceptions thrown by the +@ref basic_json class. This class can hence be used as "wildcard" to catch +exceptions. + +Subclasses: +- @ref parse_error for exceptions indicating a parse error +- @ref invalid_iterator for exceptions indicating errors with iterators +- @ref type_error for exceptions indicating executing a member function with + a wrong type +- @ref out_of_range for exceptions indicating access out of the defined range +- @ref other_error for exceptions indicating other library errors + +@internal +@note To have nothrow-copy-constructible exceptions, we internally use + `std::runtime_error` which can cope with arbitrary-length error messages. + Intermediate strings are built with static functions and then passed to + the actual constructor. +@endinternal + +@liveexample{The following code shows how arbitrary library exceptions can be +caught.,exception} + +@since version 3.0.0 +*/ +class exception : public std::exception +{ + public: + /// returns the explanatory string + const char* what() const noexcept override + { + return m.what(); + } + + /// the id of the exception + const int id; + + protected: + exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} + + static std::string name(const std::string& ename, int id_) + { + return "[json.exception." + ename + "." + std::to_string(id_) + "] "; + } + + private: + /// an exception object as storage for error messages + std::runtime_error m; +}; + +/*! +@brief exception indicating a parse error + +This exception is thrown by the library when a parse error occurs. Parse errors +can occur during the deserialization of JSON text, CBOR, MessagePack, as well +as when using JSON Patch. + +Member @a byte holds the byte index of the last read character in the input +file. + +Exceptions have ids 1xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. +json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. +json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. +json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. +json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. +json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`. +json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. +json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. +json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. +json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. +json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. +json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. + +@note For an input with n bytes, 1 is the index of the first character and n+1 + is the index of the terminating null byte or the end of file. This also + holds true when reading a byte vector (CBOR or MessagePack). + +@liveexample{The following code shows how a `parse_error` exception can be +caught.,parse_error} + +@sa @ref exception for the base class of the library exceptions +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class parse_error : public exception +{ + public: + /*! + @brief create a parse error exception + @param[in] id_ the id of the exception + @param[in] byte_ the byte index where the error occurred (or 0 if the + position cannot be determined) + @param[in] what_arg the explanatory string + @return parse_error object + */ + static parse_error create(int id_, std::size_t byte_, const std::string& what_arg) + { + std::string w = exception::name("parse_error", id_) + "parse error" + + (byte_ != 0 ? (" at " + std::to_string(byte_)) : "") + + ": " + what_arg; + return parse_error(id_, byte_, w.c_str()); + } + + /*! + @brief byte index of the parse error + + The byte index of the last read character in the input file. + + @note For an input with n bytes, 1 is the index of the first character and + n+1 is the index of the terminating null byte or the end of file. + This also holds true when reading a byte vector (CBOR or MessagePack). + */ + const std::size_t byte; + + private: + parse_error(int id_, std::size_t byte_, const char* what_arg) + : exception(id_, what_arg), byte(byte_) {} +}; + +/*! +@brief exception indicating errors with iterators + +This exception is thrown if iterators passed to a library function do not match +the expected semantics. + +Exceptions have ids 2xx. + +name / id | example message | description +----------------------------------- | --------------- | ------------------------- +json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. +json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from. +json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid. +json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid. +json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range. +json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key. +json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. +json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. +json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. +json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). + +@liveexample{The following code shows how an `invalid_iterator` exception can be +caught.,invalid_iterator} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class invalid_iterator : public exception +{ + public: + static invalid_iterator create(int id_, const std::string& what_arg) + { + std::string w = exception::name("invalid_iterator", id_) + what_arg; + return invalid_iterator(id_, w.c_str()); + } + + private: + invalid_iterator(int id_, const char* what_arg) + : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating executing a member function with a wrong type + +This exception is thrown in case of a type error; that is, a library function is +executed on a JSON value whose type does not match the expected semantics. + +Exceptions have ids 3xx. + +name / id | example message | description +----------------------------- | --------------- | ------------------------- +json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. +json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. +json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t&. +json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types. +json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types. +json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types. +json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types. +json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types. +json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. +json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. +json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. +json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. +json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. +json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. +json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. +json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. | + +@liveexample{The following code shows how a `type_error` exception can be +caught.,type_error} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class type_error : public exception +{ + public: + static type_error create(int id_, const std::string& what_arg) + { + std::string w = exception::name("type_error", id_) + what_arg; + return type_error(id_, w.c_str()); + } + + private: + type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating access out of the defined range + +This exception is thrown in case a library function is called on an input +parameter that exceeds the expected range, for instance in case of array +indices or nonexisting object keys. + +Exceptions have ids 4xx. + +name / id | example message | description +------------------------------- | --------------- | ------------------------- +json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. +json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. +json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object. +json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. +json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. +json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. +json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON only supports integers numbers up to 9223372036854775807. | +json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. | + +@liveexample{The following code shows how an `out_of_range` exception can be +caught.,out_of_range} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class out_of_range : public exception +{ + public: + static out_of_range create(int id_, const std::string& what_arg) + { + std::string w = exception::name("out_of_range", id_) + what_arg; + return out_of_range(id_, w.c_str()); + } + + private: + out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating other library errors + +This exception is thrown in case of errors that cannot be classified with the +other exception types. + +Exceptions have ids 5xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range + +@liveexample{The following code shows how an `other_error` exception can be +caught.,other_error} + +@since version 3.0.0 +*/ +class other_error : public exception +{ + public: + static other_error create(int id_, const std::string& what_arg) + { + std::string w = exception::name("other_error", id_) + what_arg; + return other_error(id_, w.c_str()); + } + + private: + other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; +} +} + +// #include + + +#include // array +#include // and +#include // size_t +#include // uint8_t + +namespace nlohmann +{ +namespace detail +{ +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + discarded ///< discarded by the the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string +- furthermore, each type is not smaller than itself +- discarded values are not comparable + +@since version 1.0.0 +*/ +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); + return l_index < order.size() and r_index < order.size() and order[l_index] < order[r_index]; +} +} +} + +// #include + + +#include // transform +#include // array +#include // and, not +#include // forward_list +#include // inserter, front_inserter, end +#include // string +#include // tuple, make_tuple +#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible +#include // pair, declval +#include // valarray + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +// overloads for basic_json template parameters +template::value and + not std::is_same::value, + int> = 0> +void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); + } +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +{ + if (JSON_UNLIKELY(not j.is_boolean())) + { + JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()))); + } + b = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +{ + if (JSON_UNLIKELY(not j.is_string())) + { + JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); + } + s = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +{ + get_arithmetic_value(j, val); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, EnumType& e) +{ + typename std::underlying_type::type val; + get_arithmetic_value(j, val); + e = static_cast(val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + arr = *j.template get_ptr(); +} + +// forward_list doesn't have an insert method +template::value, int> = 0> +void from_json(const BasicJsonType& j, std::forward_list& l) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + std::transform(j.rbegin(), j.rend(), + std::front_inserter(l), [](const BasicJsonType & i) + { + return i.template get(); + }); +} + +// valarray doesn't have an insert method +template::value, int> = 0> +void from_json(const BasicJsonType& j, std::valarray& l) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + l.resize(j.size()); + std::copy(j.m_value.array->begin(), j.m_value.array->end(), std::begin(l)); +} + +template +void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<0> /*unused*/) +{ + using std::end; + + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); +} + +template +auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1> /*unused*/) +-> decltype( + arr.reserve(std::declval()), + void()) +{ + using std::end; + + arr.reserve(j.size()); + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); +} + +template +void from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) +{ + for (std::size_t i = 0; i < N; ++i) + { + arr[i] = j.at(i).template get(); + } +} + +template < + typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < + is_compatible_array_type::value and + not std::is_same::value and + std::is_constructible < + BasicJsonType, typename CompatibleArrayType::value_type >::value, + int > = 0 > +void from_json(const BasicJsonType& j, CompatibleArrayType& arr) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + + std::string(j.type_name()))); + } + + from_json_array_impl(j, arr, priority_tag<2> {}); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, CompatibleObjectType& obj) +{ + if (JSON_UNLIKELY(not j.is_object())) + { + JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()))); + } + + auto inner_object = j.template get_ptr(); + using value_type = typename CompatibleObjectType::value_type; + std::transform( + inner_object->begin(), inner_object->end(), + std::inserter(obj, obj.begin()), + [](typename BasicJsonType::object_t::value_type const & p) + { + return value_type(p.first, p.second.template get()); + }); +} + +// overload for arithmetic types, not chosen for basic_json template arguments +// (BooleanType, etc..); note: Is it really necessary to provide explicit +// overloads for boolean_t etc. in case of a custom BooleanType which is not +// an arithmetic type? +template::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value, + int> = 0> +void from_json(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::boolean: + { + val = static_cast(*j.template get_ptr()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); + } +} + +template +void from_json(const BasicJsonType& j, std::pair& p) +{ + p = {j.at(0).template get(), j.at(1).template get()}; +} + +template +void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence) +{ + t = std::make_tuple(j.at(Idx).template get::type>()...); +} + +template +void from_json(const BasicJsonType& j, std::tuple& t) +{ + from_json_tuple_impl(j, t, index_sequence_for {}); +} + +struct from_json_fn +{ + private: + template + auto call(const BasicJsonType& j, T& val, priority_tag<1> /*unused*/) const + noexcept(noexcept(from_json(j, val))) + -> decltype(from_json(j, val), void()) + { + return from_json(j, val); + } + + template + void call(const BasicJsonType& /*unused*/, T& /*unused*/, priority_tag<0> /*unused*/) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, + "could not find from_json() method in T's namespace"); +#ifdef _MSC_VER + // MSVC does not show a stacktrace for the above assert + using decayed = uncvref_t; + static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, + "forcing MSVC stacktrace to show which T we're talking about."); +#endif + } + + public: + template + void operator()(const BasicJsonType& j, T& val) const + noexcept(noexcept(std::declval().call(j, val, priority_tag<1> {}))) + { + return call(j, val, priority_tag<1> {}); + } +}; +} + +/// namespace to hold default `from_json` function +/// to see why this is required: +/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html +namespace +{ +constexpr const auto& from_json = detail::static_const::value; +} +} + +// #include + + +#include // or, and, not +#include // begin, end +#include // tuple, get +#include // is_same, is_constructible, is_floating_point, is_enum, underlying_type +#include // move, forward, declval, pair +#include // valarray +#include // vector + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +////////////////// +// constructors // +////////////////// + +template struct external_constructor; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept + { + j.m_type = value_t::boolean; + j.m_value = b; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) + { + j.m_type = value_t::string; + j.m_value = s; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) + { + j.m_type = value_t::string; + j.m_value = std::move(s); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept + { + j.m_type = value_t::number_float; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept + { + j.m_type = value_t::number_unsigned; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept + { + j.m_type = value_t::number_integer; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) + { + j.m_type = value_t::array; + j.m_value = arr; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) + { + j.m_type = value_t::array; + j.m_value = std::move(arr); + j.assert_invariant(); + } + + template::value, + int> = 0> + static void construct(BasicJsonType& j, const CompatibleArrayType& arr) + { + using std::begin; + using std::end; + j.m_type = value_t::array; + j.m_value.array = j.template create(begin(arr), end(arr)); + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, const std::vector& arr) + { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->reserve(arr.size()); + for (const bool x : arr) + { + j.m_value.array->push_back(x); + } + j.assert_invariant(); + } + + template::value, int> = 0> + static void construct(BasicJsonType& j, const std::valarray& arr) + { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->resize(arr.size()); + std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) + { + j.m_type = value_t::object; + j.m_value = obj; + j.assert_invariant(); + } + + template + static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) + { + j.m_type = value_t::object; + j.m_value = std::move(obj); + j.assert_invariant(); + } + + template::value, int> = 0> + static void construct(BasicJsonType& j, const CompatibleObjectType& obj) + { + using std::begin; + using std::end; + + j.m_type = value_t::object; + j.m_value.object = j.template create(begin(obj), end(obj)); + j.assert_invariant(); + } +}; + +///////////// +// to_json // +///////////// + +template::value, int> = 0> +void to_json(BasicJsonType& j, T b) noexcept +{ + external_constructor::construct(j, b); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, const CompatibleString& s) +{ + external_constructor::construct(j, s); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) +{ + external_constructor::construct(j, std::move(s)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, FloatType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, EnumType e) noexcept +{ + using underlying_type = typename std::underlying_type::type; + external_constructor::construct(j, static_cast(e)); +} + +template +void to_json(BasicJsonType& j, const std::vector& e) +{ + external_constructor::construct(j, e); +} + +template::value or + std::is_same::value, + int> = 0> +void to_json(BasicJsonType& j, const CompatibleArrayType& arr) +{ + external_constructor::construct(j, arr); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, std::valarray arr) +{ + external_constructor::construct(j, std::move(arr)); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) +{ + external_constructor::construct(j, std::move(arr)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, const CompatibleObjectType& obj) +{ + external_constructor::construct(j, obj); +} + +template +void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) +{ + external_constructor::construct(j, std::move(obj)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, T (&arr)[N]) +{ + external_constructor::construct(j, arr); +} + +template +void to_json(BasicJsonType& j, const std::pair& p) +{ + j = {p.first, p.second}; +} + +template +void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence) +{ + j = {std::get(t)...}; +} + +template +void to_json(BasicJsonType& j, const std::tuple& t) +{ + to_json_tuple_impl(j, t, index_sequence_for {}); +} + +struct to_json_fn +{ + private: + template + auto call(BasicJsonType& j, T&& val, priority_tag<1> /*unused*/) const noexcept(noexcept(to_json(j, std::forward(val)))) + -> decltype(to_json(j, std::forward(val)), void()) + { + return to_json(j, std::forward(val)); + } + + template + void call(BasicJsonType& /*unused*/, T&& /*unused*/, priority_tag<0> /*unused*/) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, + "could not find to_json() method in T's namespace"); + +#ifdef _MSC_VER + // MSVC does not show a stacktrace for the above assert + using decayed = uncvref_t; + static_assert(sizeof(typename decayed::force_msvc_stacktrace) == 0, + "forcing MSVC stacktrace to show which T we're talking about."); +#endif + } + + public: + template + void operator()(BasicJsonType& j, T&& val) const + noexcept(noexcept(std::declval().call(j, std::forward(val), priority_tag<1> {}))) + { + return call(j, std::forward(val), priority_tag<1> {}); + } +}; +} + +/// namespace to hold default `to_json` function +namespace +{ +constexpr const auto& to_json = detail::static_const::value; +} +} + +// #include + + +#include // min +#include // array +#include // assert +#include // size_t +#include // strlen +#include // streamsize, streamoff, streampos +#include // istream +#include // begin, end, iterator_traits, random_access_iterator_tag, distance, next +#include // shared_ptr, make_shared, addressof +#include // accumulate +#include // string, char_traits +#include // enable_if, is_base_of, is_pointer, is_integral, remove_pointer +#include // pair, declval + +// #include + + +namespace nlohmann +{ +namespace detail +{ +//////////////////// +// input adapters // +//////////////////// + +/*! +@brief abstract input adapter interface + +Produces a stream of std::char_traits::int_type characters from a +std::istream, a buffer, or some other input type. Accepts the return of exactly +one non-EOF character for future input. The int_type characters returned +consist of all valid char values as positive values (typically unsigned char), +plus an EOF value outside that range, specified by the value of the function +std::char_traits::eof(). This value is typically -1, but could be any +arbitrary value which is not a valid char value. +*/ +struct input_adapter_protocol +{ + /// get a character [0,255] or std::char_traits::eof(). + virtual std::char_traits::int_type get_character() = 0; + /// restore the last non-eof() character to input + virtual void unget_character() = 0; + virtual ~input_adapter_protocol() = default; +}; + +/// a type to simplify interfaces +using input_adapter_t = std::shared_ptr; + +/*! +Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at +beginning of input. Does not support changing the underlying std::streambuf +in mid-input. Maintains underlying std::istream and std::streambuf to support +subsequent use of standard std::istream operations to process any input +characters following those used in parsing the JSON input. Clears the +std::istream flags; any input errors (e.g., EOF) will be detected by the first +subsequent call for input from the std::istream. +*/ +class input_stream_adapter : public input_adapter_protocol +{ + public: + ~input_stream_adapter() override + { + // clear stream flags; we use underlying streambuf I/O, do not + // maintain ifstream flags + is.clear(); + } + + explicit input_stream_adapter(std::istream& i) + : is(i), sb(*i.rdbuf()) + { + // skip byte order mark + std::char_traits::int_type c; + if ((c = get_character()) == 0xEF) + { + if ((c = get_character()) == 0xBB) + { + if ((c = get_character()) == 0xBF) + { + return; // Ignore BOM + } + else if (c != std::char_traits::eof()) + { + is.unget(); + } + is.putback('\xBB'); + } + else if (c != std::char_traits::eof()) + { + is.unget(); + } + is.putback('\xEF'); + } + else if (c != std::char_traits::eof()) + { + is.unget(); // no byte order mark; process as usual + } + } + + // delete because of pointer members + input_stream_adapter(const input_stream_adapter&) = delete; + input_stream_adapter& operator=(input_stream_adapter&) = delete; + + // std::istream/std::streambuf use std::char_traits::to_int_type, to + // ensure that std::char_traits::eof() and the character 0xFF do not + // end up as the same value, eg. 0xFFFFFFFF. + std::char_traits::int_type get_character() override + { + return sb.sbumpc(); + } + + void unget_character() override + { + sb.sungetc(); // is.unget() avoided for performance + } + + private: + /// the associated input stream + std::istream& is; + std::streambuf& sb; +}; + +/// input adapter for buffer input +class input_buffer_adapter : public input_adapter_protocol +{ + public: + input_buffer_adapter(const char* b, const std::size_t l) + : cursor(b), limit(b + l), start(b) + { + // skip byte order mark + if (l >= 3 and b[0] == '\xEF' and b[1] == '\xBB' and b[2] == '\xBF') + { + cursor += 3; + } + } + + // delete because of pointer members + input_buffer_adapter(const input_buffer_adapter&) = delete; + input_buffer_adapter& operator=(input_buffer_adapter&) = delete; + + std::char_traits::int_type get_character() noexcept override + { + if (JSON_LIKELY(cursor < limit)) + { + return std::char_traits::to_int_type(*(cursor++)); + } + + return std::char_traits::eof(); + } + + void unget_character() noexcept override + { + if (JSON_LIKELY(cursor > start)) + { + --cursor; + } + } + + private: + /// pointer to the current character + const char* cursor; + /// pointer past the last character + const char* limit; + /// pointer to the first character + const char* start; +}; + +class input_adapter +{ + public: + // native support + + /// input adapter for input stream + input_adapter(std::istream& i) + : ia(std::make_shared(i)) {} + + /// input adapter for input stream + input_adapter(std::istream&& i) + : ia(std::make_shared(i)) {} + + /// input adapter for buffer + template::value and + std::is_integral::type>::value and + sizeof(typename std::remove_pointer::type) == 1, + int>::type = 0> + input_adapter(CharT b, std::size_t l) + : ia(std::make_shared(reinterpret_cast(b), l)) {} + + // derived support + + /// input adapter for string literal + template::value and + std::is_integral::type>::value and + sizeof(typename std::remove_pointer::type) == 1, + int>::type = 0> + input_adapter(CharT b) + : input_adapter(reinterpret_cast(b), + std::strlen(reinterpret_cast(b))) {} + + /// input adapter for iterator range with contiguous storage + template::iterator_category, std::random_access_iterator_tag>::value, + int>::type = 0> + input_adapter(IteratorType first, IteratorType last) + { + // assertion to check that the iterator range is indeed contiguous, + // see http://stackoverflow.com/a/35008842/266378 for more discussion + assert(std::accumulate( + first, last, std::pair(true, 0), + [&first](std::pair res, decltype(*first) val) + { + res.first &= (val == *(std::next(std::addressof(*first), res.second++))); + return res; + }).first); + + // assertion to check that each element is 1 byte long + static_assert( + sizeof(typename std::iterator_traits::value_type) == 1, + "each element in the iterator range must have the size of 1 byte"); + + const auto len = static_cast(std::distance(first, last)); + if (JSON_LIKELY(len > 0)) + { + // there is at least one element: use the address of first + ia = std::make_shared(reinterpret_cast(&(*first)), len); + } + else + { + // the address of first cannot be used: use nullptr + ia = std::make_shared(nullptr, len); + } + } + + /// input adapter for array + template + input_adapter(T (&array)[N]) + : input_adapter(std::begin(array), std::end(array)) {} + + /// input adapter for contiguous container + template::value and + std::is_base_of()))>::iterator_category>::value, + int>::type = 0> + input_adapter(const ContiguousContainer& c) + : input_adapter(std::begin(c), std::end(c)) {} + + operator input_adapter_t() + { + return ia; + } + + private: + /// the actual adapter + input_adapter_t ia = nullptr; +}; +} +} + +// #include + + +#include // localeconv +#include // size_t +#include // strtof, strtod, strtold, strtoll, strtoull +#include // initializer_list +#include // hex, uppercase +#include // setw, setfill +#include // stringstream +#include // char_traits, string +#include // vector + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////// +// lexer // +/////////// + +/*! +@brief lexical analysis + +This class organizes the lexical analysis during JSON deserialization. +*/ +template +class lexer +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + + public: + /// token types for the parser + enum class token_type + { + uninitialized, ///< indicating the scanner is uninitialized + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal + value_string, ///< a string -- use get_string() for actual value + value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value + value_integer, ///< a signed integer -- use get_number_integer() for actual value + value_float, ///< an floating point number -- use get_number_float() for actual value + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` + name_separator, ///< the name separator `:` + value_separator, ///< the value separator `,` + parse_error, ///< indicating a parse error + end_of_input, ///< indicating the end of the input buffer + literal_or_value ///< a literal or the begin of a value (only for diagnostics) + }; + + /// return name of values of type token_type (only used for errors) + static const char* token_type_name(const token_type t) noexcept + { + switch (t) + { + case token_type::uninitialized: + return ""; + case token_type::literal_true: + return "true literal"; + case token_type::literal_false: + return "false literal"; + case token_type::literal_null: + return "null literal"; + case token_type::value_string: + return "string literal"; + case lexer::token_type::value_unsigned: + case lexer::token_type::value_integer: + case lexer::token_type::value_float: + return "number literal"; + case token_type::begin_array: + return "'['"; + case token_type::begin_object: + return "'{'"; + case token_type::end_array: + return "']'"; + case token_type::end_object: + return "'}'"; + case token_type::name_separator: + return "':'"; + case token_type::value_separator: + return "','"; + case token_type::parse_error: + return ""; + case token_type::end_of_input: + return "end of input"; + case token_type::literal_or_value: + return "'[', '{', or a literal"; + default: // catch non-enum values + return "unknown token"; // LCOV_EXCL_LINE + } + } + + explicit lexer(detail::input_adapter_t adapter) + : ia(std::move(adapter)), decimal_point_char(get_decimal_point()) {} + + // delete because of pointer members + lexer(const lexer&) = delete; + lexer& operator=(lexer&) = delete; + + private: + ///////////////////// + // locales + ///////////////////// + + /// return the locale-dependent decimal point + static char get_decimal_point() noexcept + { + const auto loc = localeconv(); + assert(loc != nullptr); + return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point); + } + + ///////////////////// + // scan functions + ///////////////////// + + /*! + @brief get codepoint from 4 hex characters following `\u` + + For input "\u c1 c2 c3 c4" the codepoint is: + (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 + = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) + + Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' + must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The + conversion is done by subtracting the offset (0x30, 0x37, and 0x57) + between the ASCII value of the character and the desired integer value. + + @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or + non-hex character) + */ + int get_codepoint() + { + // this function only makes sense after reading `\u` + assert(current == 'u'); + int codepoint = 0; + + const auto factors = { 12, 8, 4, 0 }; + for (const auto factor : factors) + { + get(); + + if (current >= '0' and current <= '9') + { + codepoint += ((current - 0x30) << factor); + } + else if (current >= 'A' and current <= 'F') + { + codepoint += ((current - 0x37) << factor); + } + else if (current >= 'a' and current <= 'f') + { + codepoint += ((current - 0x57) << factor); + } + else + { + return -1; + } + } + + assert(0x0000 <= codepoint and codepoint <= 0xFFFF); + return codepoint; + } + + /*! + @brief check if the next byte(s) are inside a given range + + Adds the current byte and, for each passed range, reads a new byte and + checks if it is inside the range. If a violation was detected, set up an + error message and return false. Otherwise, return true. + + @param[in] ranges list of integers; interpreted as list of pairs of + inclusive lower and upper bound, respectively + + @pre The passed list @a ranges must have 2, 4, or 6 elements; that is, + 1, 2, or 3 pairs. This precondition is enforced by an assertion. + + @return true if and only if no range violation was detected + */ + bool next_byte_in_range(std::initializer_list ranges) + { + assert(ranges.size() == 2 or ranges.size() == 4 or ranges.size() == 6); + add(current); + + for (auto range = ranges.begin(); range != ranges.end(); ++range) + { + get(); + if (JSON_LIKELY(*range <= current and current <= *(++range))) + { + add(current); + } + else + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return false; + } + } + + return true; + } + + /*! + @brief scan a string literal + + This function scans a string according to Sect. 7 of RFC 7159. While + scanning, bytes are escaped and copied into buffer token_buffer. Then the + function returns successfully, token_buffer is *not* null-terminated (as it + may contain \0 bytes), and token_buffer.size() is the number of bytes in the + string. + + @return token_type::value_string if string could be successfully scanned, + token_type::parse_error otherwise + + @note In case of errors, variable error_message contains a textual + description. + */ + token_type scan_string() + { + // reset token_buffer (ignore opening quote) + reset(); + + // we entered the function by reading an open quote + assert(current == '\"'); + + while (true) + { + // get next character + switch (get()) + { + // end of file while parsing string + case std::char_traits::eof(): + { + error_message = "invalid string: missing closing quote"; + return token_type::parse_error; + } + + // closing quote + case '\"': + { + return token_type::value_string; + } + + // escapes + case '\\': + { + switch (get()) + { + // quotation mark + case '\"': + add('\"'); + break; + // reverse solidus + case '\\': + add('\\'); + break; + // solidus + case '/': + add('/'); + break; + // backspace + case 'b': + add('\b'); + break; + // form feed + case 'f': + add('\f'); + break; + // line feed + case 'n': + add('\n'); + break; + // carriage return + case 'r': + add('\r'); + break; + // tab + case 't': + add('\t'); + break; + + // unicode escapes + case 'u': + { + const int codepoint1 = get_codepoint(); + int codepoint = codepoint1; // start with codepoint1 + + if (JSON_UNLIKELY(codepoint1 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + // check if code point is a high surrogate + if (0xD800 <= codepoint1 and codepoint1 <= 0xDBFF) + { + // expect next \uxxxx entry + if (JSON_LIKELY(get() == '\\' and get() == 'u')) + { + const int codepoint2 = get_codepoint(); + + if (JSON_UNLIKELY(codepoint2 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + // check if codepoint2 is a low surrogate + if (JSON_LIKELY(0xDC00 <= codepoint2 and codepoint2 <= 0xDFFF)) + { + // overwrite codepoint + codepoint = + // high surrogate occupies the most significant 22 bits + (codepoint1 << 10) + // low surrogate occupies the least significant 15 bits + + codepoint2 + // there is still the 0xD800, 0xDC00 and 0x10000 noise + // in the result so we have to subtract with: + // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 + - 0x35FDC00; + } + else + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + if (JSON_UNLIKELY(0xDC00 <= codepoint1 and codepoint1 <= 0xDFFF)) + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; + return token_type::parse_error; + } + } + + // result of the above calculation yields a proper codepoint + assert(0x00 <= codepoint and codepoint <= 0x10FFFF); + + // translate codepoint into bytes + if (codepoint < 0x80) + { + // 1-byte characters: 0xxxxxxx (ASCII) + add(codepoint); + } + else if (codepoint <= 0x7FF) + { + // 2-byte characters: 110xxxxx 10xxxxxx + add(0xC0 | (codepoint >> 6)); + add(0x80 | (codepoint & 0x3F)); + } + else if (codepoint <= 0xFFFF) + { + // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx + add(0xE0 | (codepoint >> 12)); + add(0x80 | ((codepoint >> 6) & 0x3F)); + add(0x80 | (codepoint & 0x3F)); + } + else + { + // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + add(0xF0 | (codepoint >> 18)); + add(0x80 | ((codepoint >> 12) & 0x3F)); + add(0x80 | ((codepoint >> 6) & 0x3F)); + add(0x80 | (codepoint & 0x3F)); + } + + break; + } + + // other characters after escape + default: + error_message = "invalid string: forbidden character after backslash"; + return token_type::parse_error; + } + + break; + } + + // invalid control characters + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + { + error_message = "invalid string: control character must be escaped"; + return token_type::parse_error; + } + + // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) + case 0x20: + case 0x21: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + { + add(current); + break; + } + + // U+0080..U+07FF: bytes C2..DF 80..BF + case 0xC2: + case 0xC3: + case 0xC4: + case 0xC5: + case 0xC6: + case 0xC7: + case 0xC8: + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCC: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xD0: + case 0xD1: + case 0xD2: + case 0xD3: + case 0xD4: + case 0xD5: + case 0xD6: + case 0xD7: + case 0xD8: + case 0xD9: + case 0xDA: + case 0xDB: + case 0xDC: + case 0xDD: + case 0xDE: + case 0xDF: + { + if (JSON_UNLIKELY(not next_byte_in_range({0x80, 0xBF}))) + { + return token_type::parse_error; + } + break; + } + + // U+0800..U+0FFF: bytes E0 A0..BF 80..BF + case 0xE0: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF + // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xEE: + case 0xEF: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+D000..U+D7FF: bytes ED 80..9F 80..BF + case 0xED: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF + case 0xF0: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF + case 0xF1: + case 0xF2: + case 0xF3: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF + case 0xF4: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // remaining bytes (80..C1 and F5..FF) are ill-formed + default: + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return token_type::parse_error; + } + } + } + } + + static void strtof(float& f, const char* str, char** endptr) noexcept + { + f = std::strtof(str, endptr); + } + + static void strtof(double& f, const char* str, char** endptr) noexcept + { + f = std::strtod(str, endptr); + } + + static void strtof(long double& f, const char* str, char** endptr) noexcept + { + f = std::strtold(str, endptr); + } + + /*! + @brief scan a number literal + + This function scans a string according to Sect. 6 of RFC 7159. + + The function is realized with a deterministic finite state machine derived + from the grammar described in RFC 7159. Starting in state "init", the + input is read and used to determined the next state. Only state "done" + accepts the number. State "error" is a trap state to model errors. In the + table below, "anything" means any character but the ones listed before. + + state | 0 | 1-9 | e E | + | - | . | anything + ---------|----------|----------|----------|---------|---------|----------|----------- + init | zero | any1 | [error] | [error] | minus | [error] | [error] + minus | zero | any1 | [error] | [error] | [error] | [error] | [error] + zero | done | done | exponent | done | done | decimal1 | done + any1 | any1 | any1 | exponent | done | done | decimal1 | done + decimal1 | decimal2 | [error] | [error] | [error] | [error] | [error] | [error] + decimal2 | decimal2 | decimal2 | exponent | done | done | done | done + exponent | any2 | any2 | [error] | sign | sign | [error] | [error] + sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] + any2 | any2 | any2 | done | done | done | done | done + + The state machine is realized with one label per state (prefixed with + "scan_number_") and `goto` statements between them. The state machine + contains cycles, but any cycle can be left when EOF is read. Therefore, + the function is guaranteed to terminate. + + During scanning, the read bytes are stored in token_buffer. This string is + then converted to a signed integer, an unsigned integer, or a + floating-point number. + + @return token_type::value_unsigned, token_type::value_integer, or + token_type::value_float if number could be successfully scanned, + token_type::parse_error otherwise + + @note The scanner is independent of the current locale. Internally, the + locale's decimal point is used instead of `.` to work with the + locale-dependent converters. + */ + token_type scan_number() + { + // reset token_buffer to store the number's bytes + reset(); + + // the type of the parsed number; initially set to unsigned; will be + // changed if minus sign, decimal point or exponent is read + token_type number_type = token_type::value_unsigned; + + // state (init): we just found out we need to scan a number + switch (current) + { + case '-': + { + add(current); + goto scan_number_minus; + } + + case '0': + { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + default: + { + // all other characters are rejected outside scan_number() + assert(false); // LCOV_EXCL_LINE + } + } + +scan_number_minus: + // state: we just parsed a leading minus sign + number_type = token_type::value_integer; + switch (get()) + { + case '0': + { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + default: + { + error_message = "invalid number; expected digit after '-'"; + return token_type::parse_error; + } + } + +scan_number_zero: + // state: we just parse a zero (maybe with a leading minus sign) + switch (get()) + { + case '.': + { + add(decimal_point_char); + goto scan_number_decimal1; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_any1: + // state: we just parsed a number 0-9 (maybe with a leading minus sign) + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + case '.': + { + add(decimal_point_char); + goto scan_number_decimal1; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_decimal1: + // state: we just parsed a decimal point + number_type = token_type::value_float; + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } + + default: + { + error_message = "invalid number; expected digit after '.'"; + return token_type::parse_error; + } + } + +scan_number_decimal2: + // we just parsed at least one number after a decimal point + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_exponent: + // we just parsed an exponent + number_type = token_type::value_float; + switch (get()) + { + case '+': + case '-': + { + add(current); + goto scan_number_sign; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + { + error_message = + "invalid number; expected '+', '-', or digit after exponent"; + return token_type::parse_error; + } + } + +scan_number_sign: + // we just parsed an exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + { + error_message = "invalid number; expected digit after exponent sign"; + return token_type::parse_error; + } + } + +scan_number_any2: + // we just parsed a number after the exponent or exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + goto scan_number_done; + } + +scan_number_done: + // unget the character after the number (we only read it to know that + // we are done scanning a number) + unget(); + + char* endptr = nullptr; + errno = 0; + + // try to parse integers first and fall back to floats + if (number_type == token_type::value_unsigned) + { + const auto x = std::strtoull(token_buffer.data(), &endptr, 10); + + // we checked the number format before + assert(endptr == token_buffer.data() + token_buffer.size()); + + if (errno == 0) + { + value_unsigned = static_cast(x); + if (value_unsigned == x) + { + return token_type::value_unsigned; + } + } + } + else if (number_type == token_type::value_integer) + { + const auto x = std::strtoll(token_buffer.data(), &endptr, 10); + + // we checked the number format before + assert(endptr == token_buffer.data() + token_buffer.size()); + + if (errno == 0) + { + value_integer = static_cast(x); + if (value_integer == x) + { + return token_type::value_integer; + } + } + } + + // this code is reached if we parse a floating-point number or if an + // integer conversion above failed + strtof(value_float, token_buffer.data(), &endptr); + + // we checked the number format before + assert(endptr == token_buffer.data() + token_buffer.size()); + + return token_type::value_float; + } + + /*! + @param[in] literal_text the literal text to expect + @param[in] length the length of the passed literal text + @param[in] return_type the token type to return on success + */ + token_type scan_literal(const char* literal_text, const std::size_t length, + token_type return_type) + { + assert(current == literal_text[0]); + for (std::size_t i = 1; i < length; ++i) + { + if (JSON_UNLIKELY(get() != literal_text[i])) + { + error_message = "invalid literal"; + return token_type::parse_error; + } + } + return return_type; + } + + ///////////////////// + // input management + ///////////////////// + + /// reset token_buffer; current character is beginning of token + void reset() noexcept + { + token_buffer.clear(); + token_string.clear(); + token_string.push_back(std::char_traits::to_char_type(current)); + } + + /* + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a + `std::char_traits::eof()` in that case. Stores the scanned characters + for use in error messages. + + @return character read from the input + */ + std::char_traits::int_type get() + { + ++chars_read; + current = ia->get_character(); + if (JSON_LIKELY(current != std::char_traits::eof())) + { + token_string.push_back(std::char_traits::to_char_type(current)); + } + return current; + } + + /// unget current character (return it again on next get) + void unget() + { + --chars_read; + if (JSON_LIKELY(current != std::char_traits::eof())) + { + ia->unget_character(); + assert(token_string.size() != 0); + token_string.pop_back(); + } + } + + /// add a character to token_buffer + void add(int c) + { + token_buffer.push_back(std::char_traits::to_char_type(c)); + } + + public: + ///////////////////// + // value getters + ///////////////////// + + /// return integer value + constexpr number_integer_t get_number_integer() const noexcept + { + return value_integer; + } + + /// return unsigned integer value + constexpr number_unsigned_t get_number_unsigned() const noexcept + { + return value_unsigned; + } + + /// return floating-point value + constexpr number_float_t get_number_float() const noexcept + { + return value_float; + } + + /// return current string value (implicitly resets the token; useful only once) + string_t&& move_string() + { + return std::move(token_buffer); + } + + ///////////////////// + // diagnostics + ///////////////////// + + /// return position of last read token + constexpr std::size_t get_position() const noexcept + { + return chars_read; + } + + /// return the last read token (for errors only). Will never contain EOF + /// (an arbitrary value that is not a valid char value, often -1), because + /// 255 may legitimately occur. May contain NUL, which should be escaped. + std::string get_token_string() const + { + // escape control characters + std::string result; + for (const auto c : token_string) + { + if ('\x00' <= c and c <= '\x1F') + { + // escape control characters + std::stringstream ss; + ss << "(c) << ">"; + result += ss.str(); + } + else + { + // add character as is + result.push_back(c); + } + } + + return result; + } + + /// return syntax error message + constexpr const char* get_error_message() const noexcept + { + return error_message; + } + + ///////////////////// + // actual scanner + ///////////////////// + + token_type scan() + { + // read next character and ignore whitespace + do + { + get(); + } + while (current == ' ' or current == '\t' or current == '\n' or current == '\r'); + + switch (current) + { + // structural characters + case '[': + return token_type::begin_array; + case ']': + return token_type::end_array; + case '{': + return token_type::begin_object; + case '}': + return token_type::end_object; + case ':': + return token_type::name_separator; + case ',': + return token_type::value_separator; + + // literals + case 't': + return scan_literal("true", 4, token_type::literal_true); + case 'f': + return scan_literal("false", 5, token_type::literal_false); + case 'n': + return scan_literal("null", 4, token_type::literal_null); + + // string + case '\"': + return scan_string(); + + // number + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return scan_number(); + + // end of input (the null byte is needed when parsing from + // string literals) + case '\0': + case std::char_traits::eof(): + return token_type::end_of_input; + + // error + default: + error_message = "invalid literal"; + return token_type::parse_error; + } + } + + private: + /// input adapter + detail::input_adapter_t ia = nullptr; + + /// the current character + std::char_traits::int_type current = std::char_traits::eof(); + + /// the number of characters read + std::size_t chars_read = 0; + + /// raw input token string (for error messages) + std::vector token_string {}; + + /// buffer for variable-length tokens (numbers, strings) + string_t token_buffer {}; + + /// a description of occurred lexer errors + const char* error_message = ""; + + // number values + number_integer_t value_integer = 0; + number_unsigned_t value_unsigned = 0; + number_float_t value_float = 0; + + /// the decimal point + const char decimal_point_char = '.'; +}; +} +} + +// #include + + +#include // assert +#include // isfinite +#include // uint8_t +#include // function +#include // string +#include // move + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +//////////// +// parser // +//////////// + +/*! +@brief syntax analysis + +This class implements a recursive decent parser. +*/ +template +class parser +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using string_t = typename BasicJsonType::string_t; + using lexer_t = lexer; + using token_type = typename lexer_t::token_type; + + public: + enum class parse_event_t : uint8_t + { + /// the parser read `{` and started to process a JSON object + object_start, + /// the parser read `}` and finished processing a JSON object + object_end, + /// the parser read `[` and started to process a JSON array + array_start, + /// the parser read `]` and finished processing a JSON array + array_end, + /// the parser read a key of a value in an object + key, + /// the parser finished reading a JSON value + value + }; + + using parser_callback_t = + std::function; + + /// a parser reading from an input adapter + explicit parser(detail::input_adapter_t adapter, + const parser_callback_t cb = nullptr, + const bool allow_exceptions_ = true) + : callback(cb), m_lexer(adapter), allow_exceptions(allow_exceptions_) + {} + + /*! + @brief public parser interface + + @param[in] strict whether to expect the last token to be EOF + @param[in,out] result parsed JSON value + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + void parse(const bool strict, BasicJsonType& result) + { + // read first token + get_token(); + + parse_internal(true, result); + result.assert_invariant(); + + // in strict mode, input must be completely read + if (strict) + { + get_token(); + expect(token_type::end_of_input); + } + + // in case of an error, return discarded value + if (errored) + { + result = value_t::discarded; + return; + } + + // set top-level value to null if it was discarded by the callback + // function + if (result.is_discarded()) + { + result = nullptr; + } + } + + /*! + @brief public accept interface + + @param[in] strict whether to expect the last token to be EOF + @return whether the input is a proper JSON text + */ + bool accept(const bool strict = true) + { + // read first token + get_token(); + + if (not accept_internal()) + { + return false; + } + + // strict => last token must be EOF + return not strict or (get_token() == token_type::end_of_input); + } + + private: + /*! + @brief the actual parser + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + void parse_internal(bool keep, BasicJsonType& result) + { + // never parse after a parse error was detected + assert(not errored); + + // start with a discarded value + if (not result.is_discarded()) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + + switch (last_token) + { + case token_type::begin_object: + { + if (keep) + { + if (callback) + { + keep = callback(depth++, parse_event_t::object_start, result); + } + + if (not callback or keep) + { + // explicitly set result to object to cope with {} + result.m_type = value_t::object; + result.m_value = value_t::object; + } + } + + // read next token + get_token(); + + // closing } -> we are done + if (last_token == token_type::end_object) + { + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + // parse values + string_t key; + BasicJsonType value; + while (true) + { + // store key + if (not expect(token_type::value_string)) + { + return; + } + key = m_lexer.move_string(); + + bool keep_tag = false; + if (keep) + { + if (callback) + { + BasicJsonType k(key); + keep_tag = callback(depth, parse_event_t::key, k); + } + else + { + keep_tag = true; + } + } + + // parse separator (:) + get_token(); + if (not expect(token_type::name_separator)) + { + return; + } + + // parse and add value + get_token(); + value.m_value.destroy(value.m_type); + value.m_type = value_t::discarded; + parse_internal(keep, value); + + if (JSON_UNLIKELY(errored)) + { + return; + } + + if (keep and keep_tag and not value.is_discarded()) + { + result.m_value.object->emplace(std::move(key), std::move(value)); + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing } + if (not expect(token_type::end_object)) + { + return; + } + break; + } + + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + case token_type::begin_array: + { + if (keep) + { + if (callback) + { + keep = callback(depth++, parse_event_t::array_start, result); + } + + if (not callback or keep) + { + // explicitly set result to array to cope with [] + result.m_type = value_t::array; + result.m_value = value_t::array; + } + } + + // read next token + get_token(); + + // closing ] -> we are done + if (last_token == token_type::end_array) + { + if (callback and not callback(--depth, parse_event_t::array_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + // parse values + BasicJsonType value; + while (true) + { + // parse value + value.m_value.destroy(value.m_type); + value.m_type = value_t::discarded; + parse_internal(keep, value); + + if (JSON_UNLIKELY(errored)) + { + return; + } + + if (keep and not value.is_discarded()) + { + result.m_value.array->push_back(std::move(value)); + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing ] + if (not expect(token_type::end_array)) + { + return; + } + break; + } + + if (keep and callback and not callback(--depth, parse_event_t::array_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + case token_type::literal_null: + { + result.m_type = value_t::null; + break; + } + + case token_type::value_string: + { + result.m_type = value_t::string; + result.m_value = m_lexer.move_string(); + break; + } + + case token_type::literal_true: + { + result.m_type = value_t::boolean; + result.m_value = true; + break; + } + + case token_type::literal_false: + { + result.m_type = value_t::boolean; + result.m_value = false; + break; + } + + case token_type::value_unsigned: + { + result.m_type = value_t::number_unsigned; + result.m_value = m_lexer.get_number_unsigned(); + break; + } + + case token_type::value_integer: + { + result.m_type = value_t::number_integer; + result.m_value = m_lexer.get_number_integer(); + break; + } + + case token_type::value_float: + { + result.m_type = value_t::number_float; + result.m_value = m_lexer.get_number_float(); + + // throw in case of infinity or NAN + if (JSON_UNLIKELY(not std::isfinite(result.m_value.number_float))) + { + if (allow_exceptions) + { + JSON_THROW(out_of_range::create(406, "number overflow parsing '" + + m_lexer.get_token_string() + "'")); + } + expect(token_type::uninitialized); + } + break; + } + + case token_type::parse_error: + { + // using "uninitialized" to avoid "expected" message + if (not expect(token_type::uninitialized)) + { + return; + } + break; // LCOV_EXCL_LINE + } + + default: + { + // the last token was unexpected; we expected a value + if (not expect(token_type::literal_or_value)) + { + return; + } + break; // LCOV_EXCL_LINE + } + } + + if (keep and callback and not callback(depth, parse_event_t::value, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + } + + /*! + @brief the actual acceptor + + @invariant 1. The last token is not yet processed. Therefore, the caller + of this function must make sure a token has been read. + 2. When this function returns, the last token is processed. + That is, the last read character was already considered. + + This invariant makes sure that no token needs to be "unput". + */ + bool accept_internal() + { + switch (last_token) + { + case token_type::begin_object: + { + // read next token + get_token(); + + // closing } -> we are done + if (last_token == token_type::end_object) + { + return true; + } + + // parse values + while (true) + { + // parse key + if (last_token != token_type::value_string) + { + return false; + } + + // parse separator (:) + get_token(); + if (last_token != token_type::name_separator) + { + return false; + } + + // parse value + get_token(); + if (not accept_internal()) + { + return false; + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing } + return (last_token == token_type::end_object); + } + } + + case token_type::begin_array: + { + // read next token + get_token(); + + // closing ] -> we are done + if (last_token == token_type::end_array) + { + return true; + } + + // parse values + while (true) + { + // parse value + if (not accept_internal()) + { + return false; + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing ] + return (last_token == token_type::end_array); + } + } + + case token_type::value_float: + { + // reject infinity or NAN + return std::isfinite(m_lexer.get_number_float()); + } + + case token_type::literal_false: + case token_type::literal_null: + case token_type::literal_true: + case token_type::value_integer: + case token_type::value_string: + case token_type::value_unsigned: + return true; + + default: // the last token was unexpected + return false; + } + } + + /// get next token from lexer + token_type get_token() + { + return (last_token = m_lexer.scan()); + } + + /*! + @throw parse_error.101 if expected token did not occur + */ + bool expect(token_type t) + { + if (JSON_UNLIKELY(t != last_token)) + { + errored = true; + expected = t; + if (allow_exceptions) + { + throw_exception(); + } + else + { + return false; + } + } + + return true; + } + + [[noreturn]] void throw_exception() const + { + std::string error_msg = "syntax error - "; + if (last_token == token_type::parse_error) + { + error_msg += std::string(m_lexer.get_error_message()) + "; last read: '" + + m_lexer.get_token_string() + "'"; + } + else + { + error_msg += "unexpected " + std::string(lexer_t::token_type_name(last_token)); + } + + if (expected != token_type::uninitialized) + { + error_msg += "; expected " + std::string(lexer_t::token_type_name(expected)); + } + + JSON_THROW(parse_error::create(101, m_lexer.get_position(), error_msg)); + } + + private: + /// current level of recursion + int depth = 0; + /// callback function + const parser_callback_t callback = nullptr; + /// the type of the last read token + token_type last_token = token_type::uninitialized; + /// the lexer + lexer_t m_lexer; + /// whether a syntax error occurred + bool errored = false; + /// possible reason for the syntax error + token_type expected = token_type::uninitialized; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; +}; +} +} + +// #include + + +#include // ptrdiff_t +#include // numeric_limits + +namespace nlohmann +{ +namespace detail +{ +/* +@brief an iterator for primitive JSON types + +This class models an iterator for primitive JSON types (boolean, number, +string). It's only purpose is to allow the iterator/const_iterator classes +to "iterate" over primitive values. Internally, the iterator is modeled by +a `difference_type` variable. Value begin_value (`0`) models the begin, +end_value (`1`) models past the end. +*/ +class primitive_iterator_t +{ + private: + using difference_type = std::ptrdiff_t; + static constexpr difference_type begin_value = 0; + static constexpr difference_type end_value = begin_value + 1; + + /// iterator as signed integer type + difference_type m_it = (std::numeric_limits::min)(); + + public: + constexpr difference_type get_value() const noexcept + { + return m_it; + } + + /// set iterator to a defined beginning + void set_begin() noexcept + { + m_it = begin_value; + } + + /// set iterator to a defined past the end + void set_end() noexcept + { + m_it = end_value; + } + + /// return whether the iterator can be dereferenced + constexpr bool is_begin() const noexcept + { + return m_it == begin_value; + } + + /// return whether the iterator is at end + constexpr bool is_end() const noexcept + { + return m_it == end_value; + } + + friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it == rhs.m_it; + } + + friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it < rhs.m_it; + } + + primitive_iterator_t operator+(difference_type n) noexcept + { + auto result = *this; + result += n; + return result; + } + + friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it - rhs.m_it; + } + + primitive_iterator_t& operator++() noexcept + { + ++m_it; + return *this; + } + + primitive_iterator_t const operator++(int) noexcept + { + auto result = *this; + m_it++; + return result; + } + + primitive_iterator_t& operator--() noexcept + { + --m_it; + return *this; + } + + primitive_iterator_t const operator--(int) noexcept + { + auto result = *this; + m_it--; + return result; + } + + primitive_iterator_t& operator+=(difference_type n) noexcept + { + m_it += n; + return *this; + } + + primitive_iterator_t& operator-=(difference_type n) noexcept + { + m_it -= n; + return *this; + } +}; +} +} + +// #include + + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/*! +@brief an iterator value + +@note This structure could easily be a union, but MSVC currently does not allow +unions members with complex constructors, see https://github.com/nlohmann/json/pull/105. +*/ +template struct internal_iterator +{ + /// iterator for JSON objects + typename BasicJsonType::object_t::iterator object_iterator {}; + /// iterator for JSON arrays + typename BasicJsonType::array_t::iterator array_iterator {}; + /// generic iterator for all other types + primitive_iterator_t primitive_iterator {}; +}; +} +} + +// #include + + +#include // not +#include // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next +#include // conditional, is_const, remove_const + +// #include + +// #include + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +// forward declare, to be able to friend it later on +template class iteration_proxy; + +/*! +@brief a template for a bidirectional iterator for the @ref basic_json class + +This class implements a both iterators (iterator and const_iterator) for the +@ref basic_json class. + +@note An iterator is called *initialized* when a pointer to a JSON value has + been set (e.g., by a constructor or a copy assignment). If the iterator is + default-constructed, it is *uninitialized* and most methods are undefined. + **The library uses assertions to detect calls on uninitialized iterators.** + +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). + +@since version 1.0.0, simplified in version 2.0.9, change to bidirectional + iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593) +*/ +template +class iter_impl +{ + /// allow basic_json to access private members + friend iter_impl::value, typename std::remove_const::type, const BasicJsonType>::type>; + friend BasicJsonType; + friend iteration_proxy; + + using object_t = typename BasicJsonType::object_t; + using array_t = typename BasicJsonType::array_t; + // make sure BasicJsonType is basic_json or const basic_json + static_assert(is_basic_json::type>::value, + "iter_impl only accepts (const) basic_json"); + + public: + + /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17. + /// The C++ Standard has never required user-defined iterators to derive from std::iterator. + /// A user-defined iterator should provide publicly accessible typedefs named + /// iterator_category, value_type, difference_type, pointer, and reference. + /// Note that value_type is required to be non-const, even for constant iterators. + using iterator_category = std::bidirectional_iterator_tag; + + /// the type of the values when the iterator is dereferenced + using value_type = typename BasicJsonType::value_type; + /// a type to represent differences between iterators + using difference_type = typename BasicJsonType::difference_type; + /// defines a pointer to the type iterated over (value_type) + using pointer = typename std::conditional::value, + typename BasicJsonType::const_pointer, + typename BasicJsonType::pointer>::type; + /// defines a reference to the type iterated over (value_type) + using reference = + typename std::conditional::value, + typename BasicJsonType::const_reference, + typename BasicJsonType::reference>::type; + + /// default constructor + iter_impl() = default; + + /*! + @brief constructor for a given JSON instance + @param[in] object pointer to a JSON object for this iterator + @pre object != nullptr + @post The iterator is initialized; i.e. `m_object != nullptr`. + */ + explicit iter_impl(pointer object) noexcept : m_object(object) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = typename object_t::iterator(); + break; + } + + case value_t::array: + { + m_it.array_iterator = typename array_t::iterator(); + break; + } + + default: + { + m_it.primitive_iterator = primitive_iterator_t(); + break; + } + } + } + + /*! + @note The conventional copy constructor and copy assignment are implicitly + defined. Combined with the following converting constructor and + assignment, they support: (1) copy from iterator to iterator, (2) + copy from const iterator to const iterator, and (3) conversion from + iterator to const iterator. However conversion from const iterator + to iterator is not defined. + */ + + /*! + @brief converting constructor + @param[in] other non-const iterator to copy from + @note It is not checked whether @a other is initialized. + */ + iter_impl(const iter_impl::type>& other) noexcept + : m_object(other.m_object), m_it(other.m_it) {} + + /*! + @brief converting assignment + @param[in,out] other non-const iterator to copy from + @return const/non-const iterator + @note It is not checked whether @a other is initialized. + */ + iter_impl& operator=(const iter_impl::type>& other) noexcept + { + m_object = other.m_object; + m_it = other.m_it; + return *this; + } + + private: + /*! + @brief set the iterator to the first value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_begin() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_value.object->begin(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_value.array->begin(); + break; + } + + case value_t::null: + { + // set to end so begin()==end() is true: null is empty + m_it.primitive_iterator.set_end(); + break; + } + + default: + { + m_it.primitive_iterator.set_begin(); + break; + } + } + } + + /*! + @brief set the iterator past the last value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_end() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_value.object->end(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_value.array->end(); + break; + } + + default: + { + m_it.primitive_iterator.set_end(); + break; + } + } + } + + public: + /*! + @brief return a reference to the value pointed to by the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator*() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return m_it.object_iterator->second; + } + + case value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return *m_it.array_iterator; + } + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: + { + if (JSON_LIKELY(m_it.primitive_iterator.is_begin())) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief dereference the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + pointer operator->() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return &(m_it.object_iterator->second); + } + + case value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return &*m_it.array_iterator; + } + + default: + { + if (JSON_LIKELY(m_it.primitive_iterator.is_begin())) + { + return m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief post-increment (it++) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl const operator++(int) + { + auto result = *this; + ++(*this); + return result; + } + + /*! + @brief pre-increment (++it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator++() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + std::advance(m_it.object_iterator, 1); + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, 1); + break; + } + + default: + { + ++m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief post-decrement (it--) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl const operator--(int) + { + auto result = *this; + --(*this); + return result; + } + + /*! + @brief pre-decrement (--it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator--() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + std::advance(m_it.object_iterator, -1); + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, -1); + break; + } + + default: + { + --m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief comparison: equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator==(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + return (m_it.object_iterator == other.m_it.object_iterator); + + case value_t::array: + return (m_it.array_iterator == other.m_it.array_iterator); + + default: + return (m_it.primitive_iterator == other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: not equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator!=(const iter_impl& other) const + { + return not operator==(other); + } + + /*! + @brief comparison: smaller + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators")); + + case value_t::array: + return (m_it.array_iterator < other.m_it.array_iterator); + + default: + return (m_it.primitive_iterator < other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: less than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<=(const iter_impl& other) const + { + return not other.operator < (*this); + } + + /*! + @brief comparison: greater than + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>(const iter_impl& other) const + { + return not operator<=(other); + } + + /*! + @brief comparison: greater than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>=(const iter_impl& other) const + { + return not operator<(other); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator+=(difference_type i) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + + case value_t::array: + { + std::advance(m_it.array_iterator, i); + break; + } + + default: + { + m_it.primitive_iterator += i; + break; + } + } + + return *this; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator-=(difference_type i) + { + return operator+=(-i); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /*! + @brief addition of distance and iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + friend iter_impl operator+(difference_type i, const iter_impl& it) + { + auto result = it; + result += i; + return result; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /*! + @brief return difference + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + difference_type operator-(const iter_impl& other) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + + case value_t::array: + return m_it.array_iterator - other.m_it.array_iterator; + + default: + return m_it.primitive_iterator - other.m_it.primitive_iterator; + } + } + + /*! + @brief access to successor + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator[](difference_type n) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators")); + + case value_t::array: + return *std::next(m_it.array_iterator, n); + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: + { + if (JSON_LIKELY(m_it.primitive_iterator.get_value() == -n)) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief return the key of an object iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + typename object_t::key_type key() const + { + assert(m_object != nullptr); + + if (JSON_LIKELY(m_object->is_object())) + { + return m_it.object_iterator->first; + } + + JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators")); + } + + /*! + @brief return the value of an iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference value() const + { + return operator*(); + } + + private: + /// associated JSON instance + pointer m_object = nullptr; + /// the actual iterator of the associated instance + internal_iterator::type> m_it; +}; +} +} + +// #include + + +#include // size_t +#include // string, to_string + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/// proxy class for the items() function +template class iteration_proxy +{ + private: + /// helper class for iteration + class iteration_proxy_internal + { + private: + /// the iterator + IteratorType anchor; + /// an index for arrays (used to create key names) + std::size_t array_index = 0; + + public: + explicit iteration_proxy_internal(IteratorType it) noexcept : anchor(it) {} + + /// dereference operator (needed for range-based for) + iteration_proxy_internal& operator*() + { + return *this; + } + + /// increment operator (needed for range-based for) + iteration_proxy_internal& operator++() + { + ++anchor; + ++array_index; + + return *this; + } + + /// inequality operator (needed for range-based for) + bool operator!=(const iteration_proxy_internal& o) const noexcept + { + return anchor != o.anchor; + } + + /// return key of the iterator + std::string key() const + { + assert(anchor.m_object != nullptr); + + switch (anchor.m_object->type()) + { + // use integer array index as key + case value_t::array: + return std::to_string(array_index); + + // use key from the object + case value_t::object: + return anchor.key(); + + // use an empty key for all primitive types + default: + return ""; + } + } + + /// return value of the iterator + typename IteratorType::reference value() const + { + return anchor.value(); + } + }; + + /// the container to iterate + typename IteratorType::reference container; + + public: + /// construct iteration proxy from a container + explicit iteration_proxy(typename IteratorType::reference cont) noexcept + : container(cont) {} + + /// return iterator begin (needed for range-based for) + iteration_proxy_internal begin() noexcept + { + return iteration_proxy_internal(container.begin()); + } + + /// return iterator end (needed for range-based for) + iteration_proxy_internal end() noexcept + { + return iteration_proxy_internal(container.end()); + } +}; +} +} + +// #include + + +#include // ptrdiff_t +#include // reverse_iterator +#include // declval + +namespace nlohmann +{ +namespace detail +{ +////////////////////// +// reverse_iterator // +////////////////////// + +/*! +@brief a template for a reverse iterator class + +@tparam Base the base iterator type to reverse. Valid types are @ref +iterator (to create @ref reverse_iterator) and @ref const_iterator (to +create @ref const_reverse_iterator). + +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). +- [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): + It is possible to write to the pointed-to element (only if @a Base is + @ref iterator). + +@since version 1.0.0 +*/ +template +class json_reverse_iterator : public std::reverse_iterator +{ + public: + using difference_type = std::ptrdiff_t; + /// shortcut to the reverse iterator adapter + using base_iterator = std::reverse_iterator; + /// the reference type for the pointed-to element + using reference = typename Base::reference; + + /// create reverse iterator from iterator + json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept + : base_iterator(it) {} + + /// create reverse iterator from base class + json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {} + + /// post-increment (it++) + json_reverse_iterator const operator++(int) + { + return static_cast(base_iterator::operator++(1)); + } + + /// pre-increment (++it) + json_reverse_iterator& operator++() + { + return static_cast(base_iterator::operator++()); + } + + /// post-decrement (it--) + json_reverse_iterator const operator--(int) + { + return static_cast(base_iterator::operator--(1)); + } + + /// pre-decrement (--it) + json_reverse_iterator& operator--() + { + return static_cast(base_iterator::operator--()); + } + + /// add to iterator + json_reverse_iterator& operator+=(difference_type i) + { + return static_cast(base_iterator::operator+=(i)); + } + + /// add to iterator + json_reverse_iterator operator+(difference_type i) const + { + return static_cast(base_iterator::operator+(i)); + } + + /// subtract from iterator + json_reverse_iterator operator-(difference_type i) const + { + return static_cast(base_iterator::operator-(i)); + } + + /// return difference + difference_type operator-(const json_reverse_iterator& other) const + { + return base_iterator(*this) - base_iterator(other); + } + + /// access to successor + reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } + + /// return the key of an object iterator + auto key() const -> decltype(std::declval().key()) + { + auto it = --this->base(); + return it.key(); + } + + /// return the value of an iterator + reference value() const + { + auto it = --this->base(); + return it.operator * (); + } +}; +} +} + +// #include + + +#include // copy +#include // size_t +#include // streamsize +#include // back_inserter +#include // shared_ptr, make_shared +#include // basic_ostream +#include // basic_string +#include // vector + +namespace nlohmann +{ +namespace detail +{ +/// abstract output adapter interface +template struct output_adapter_protocol +{ + virtual void write_character(CharType c) = 0; + virtual void write_characters(const CharType* s, std::size_t length) = 0; + virtual ~output_adapter_protocol() = default; +}; + +/// a type to simplify interfaces +template +using output_adapter_t = std::shared_ptr>; + +/// output adapter for byte vectors +template +class output_vector_adapter : public output_adapter_protocol +{ + public: + explicit output_vector_adapter(std::vector& vec) : v(vec) {} + + void write_character(CharType c) override + { + v.push_back(c); + } + + void write_characters(const CharType* s, std::size_t length) override + { + std::copy(s, s + length, std::back_inserter(v)); + } + + private: + std::vector& v; +}; + +/// output adapter for output streams +template +class output_stream_adapter : public output_adapter_protocol +{ + public: + explicit output_stream_adapter(std::basic_ostream& s) : stream(s) {} + + void write_character(CharType c) override + { + stream.put(c); + } + + void write_characters(const CharType* s, std::size_t length) override + { + stream.write(s, static_cast(length)); + } + + private: + std::basic_ostream& stream; +}; + +/// output adapter for basic_string +template> +class output_string_adapter : public output_adapter_protocol +{ + public: + explicit output_string_adapter(StringType& s) : str(s) {} + + void write_character(CharType c) override + { + str.push_back(c); + } + + void write_characters(const CharType* s, std::size_t length) override + { + str.append(s, length); + } + + private: + StringType& str; +}; + +template> +class output_adapter +{ + public: + output_adapter(std::vector& vec) + : oa(std::make_shared>(vec)) {} + + output_adapter(std::basic_ostream& s) + : oa(std::make_shared>(s)) {} + + output_adapter(StringType& s) + : oa(std::make_shared>(s)) {} + + operator output_adapter_t() + { + return oa; + } + + private: + output_adapter_t oa = nullptr; +}; +} +} + +// #include + + +#include // generate_n +#include // array +#include // assert +#include // ldexp +#include // size_t +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // memcpy +#include // setw, setfill +#include // hex +#include // back_inserter +#include // numeric_limits +#include // stringstream +#include // char_traits, string +#include // make_pair, move + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// binary reader // +/////////////////// + +/*! +@brief deserialization of CBOR and MessagePack values +*/ +template +class binary_reader +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using string_t = typename BasicJsonType::string_t; + + public: + /*! + @brief create a binary reader + + @param[in] adapter input adapter to read from + */ + explicit binary_reader(input_adapter_t adapter) : ia(std::move(adapter)) + { + assert(ia); + } + + /*! + @brief create a JSON value from CBOR input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from CBOR input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + BasicJsonType parse_cbor(const bool strict) + { + const auto res = parse_cbor_internal(); + if (strict) + { + get(); + expect_eof(); + } + return res; + } + + /*! + @brief create a JSON value from MessagePack input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from MessagePack input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + BasicJsonType parse_msgpack(const bool strict) + { + const auto res = parse_msgpack_internal(); + if (strict) + { + get(); + expect_eof(); + } + return res; + } + + /*! + @brief create a JSON value from UBJSON input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from UBJSON input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + BasicJsonType parse_ubjson(const bool strict) + { + const auto res = parse_ubjson_internal(); + if (strict) + { + get_ignore_noop(); + expect_eof(); + } + return res; + } + + /*! + @brief determine system byte order + + @return true if and only if system's byte order is little endian + + @note from http://stackoverflow.com/a/1001328/266378 + */ + static constexpr bool little_endianess(int num = 1) noexcept + { + return (*reinterpret_cast(&num) == 1); + } + + private: + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + */ + BasicJsonType parse_cbor_internal(const bool get_char = true) + { + switch (get_char ? get() : current) + { + // EOF + case std::char_traits::eof(): + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + // Integer 0x00..0x17 (0..23) + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + return static_cast(current); + + case 0x18: // Unsigned integer (one-byte uint8_t follows) + return get_number(); + + case 0x19: // Unsigned integer (two-byte uint16_t follows) + return get_number(); + + case 0x1A: // Unsigned integer (four-byte uint32_t follows) + return get_number(); + + case 0x1B: // Unsigned integer (eight-byte uint64_t follows) + return get_number(); + + // Negative integer -1-0x00..-1-0x17 (-1..-24) + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + return static_cast(0x20 - 1 - current); + + case 0x38: // Negative integer (one-byte uint8_t follows) + { + return static_cast(-1) - get_number(); + } + + case 0x39: // Negative integer -1-n (two-byte uint16_t follows) + { + return static_cast(-1) - get_number(); + } + + case 0x3A: // Negative integer -1-n (four-byte uint32_t follows) + { + return static_cast(-1) - get_number(); + } + + case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows) + { + return static_cast(-1) - + static_cast(get_number()); + } + + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + case 0x7F: // UTF-8 string (indefinite length) + { + return get_cbor_string(); + } + + // array (0x00..0x17 data items follow) + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + { + return get_cbor_array(current & 0x1F); + } + + case 0x98: // array (one-byte uint8_t for n follows) + { + return get_cbor_array(get_number()); + } + + case 0x99: // array (two-byte uint16_t for n follow) + { + return get_cbor_array(get_number()); + } + + case 0x9A: // array (four-byte uint32_t for n follow) + { + return get_cbor_array(get_number()); + } + + case 0x9B: // array (eight-byte uint64_t for n follow) + { + return get_cbor_array(get_number()); + } + + case 0x9F: // array (indefinite length) + { + BasicJsonType result = value_t::array; + while (get() != 0xFF) + { + result.push_back(parse_cbor_internal(false)); + } + return result; + } + + // map (0x00..0x17 pairs of data items follow) + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + { + return get_cbor_object(current & 0x1F); + } + + case 0xB8: // map (one-byte uint8_t for n follows) + { + return get_cbor_object(get_number()); + } + + case 0xB9: // map (two-byte uint16_t for n follow) + { + return get_cbor_object(get_number()); + } + + case 0xBA: // map (four-byte uint32_t for n follow) + { + return get_cbor_object(get_number()); + } + + case 0xBB: // map (eight-byte uint64_t for n follow) + { + return get_cbor_object(get_number()); + } + + case 0xBF: // map (indefinite length) + { + BasicJsonType result = value_t::object; + while (get() != 0xFF) + { + auto key = get_cbor_string(); + result[key] = parse_cbor_internal(); + } + return result; + } + + case 0xF4: // false + { + return false; + } + + case 0xF5: // true + { + return true; + } + + case 0xF6: // null + { + return value_t::null; + } + + case 0xF9: // Half-Precision Float (two-byte IEEE 754) + { + const int byte1 = get(); + unexpect_eof(); + const int byte2 = get(); + unexpect_eof(); + + // code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added + // to IEEE 754 in 2008, today's programming platforms often + // still only have limited support for them. It is very + // easy to include at least decoding support for them even + // without such support. An example of a small decoder for + // half-precision floating-point numbers in the C language + // is shown in Fig. 3. + const int half = (byte1 << 8) + byte2; + const int exp = (half >> 10) & 0x1F; + const int mant = half & 0x3FF; + double val; + if (exp == 0) + { + val = std::ldexp(mant, -24); + } + else if (exp != 31) + { + val = std::ldexp(mant + 1024, exp - 25); + } + else + { + val = (mant == 0) ? std::numeric_limits::infinity() + : std::numeric_limits::quiet_NaN(); + } + return (half & 0x8000) != 0 ? -val : val; + } + + case 0xFA: // Single-Precision Float (four-byte IEEE 754) + { + return get_number(); + } + + case 0xFB: // Double-Precision Float (eight-byte IEEE 754) + { + return get_number(); + } + + default: // anything else (0xFF is handled inside the other types) + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, "error reading CBOR; last byte: 0x" + ss.str())); + } + } + } + + BasicJsonType parse_msgpack_internal() + { + switch (get()) + { + // EOF + case std::char_traits::eof(): + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + // positive fixint + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2A: + case 0x2B: + case 0x2C: + case 0x2D: + case 0x2E: + case 0x2F: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4A: + case 0x4B: + case 0x4C: + case 0x4D: + case 0x4E: + case 0x4F: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5A: + case 0x5B: + case 0x5C: + case 0x5D: + case 0x5E: + case 0x5F: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7A: + case 0x7B: + case 0x7C: + case 0x7D: + case 0x7E: + case 0x7F: + return static_cast(current); + + // fixmap + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8A: + case 0x8B: + case 0x8C: + case 0x8D: + case 0x8E: + case 0x8F: + { + return get_msgpack_object(current & 0x0F); + } + + // fixarray + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + case 0x98: + case 0x99: + case 0x9A: + case 0x9B: + case 0x9C: + case 0x9D: + case 0x9E: + case 0x9F: + { + return get_msgpack_array(current & 0x0F); + } + + // fixstr + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + return get_msgpack_string(); + + case 0xC0: // nil + return value_t::null; + + case 0xC2: // false + return false; + + case 0xC3: // true + return true; + + case 0xCA: // float 32 + return get_number(); + + case 0xCB: // float 64 + return get_number(); + + case 0xCC: // uint 8 + return get_number(); + + case 0xCD: // uint 16 + return get_number(); + + case 0xCE: // uint 32 + return get_number(); + + case 0xCF: // uint 64 + return get_number(); + + case 0xD0: // int 8 + return get_number(); + + case 0xD1: // int 16 + return get_number(); + + case 0xD2: // int 32 + return get_number(); + + case 0xD3: // int 64 + return get_number(); + + case 0xD9: // str 8 + case 0xDA: // str 16 + case 0xDB: // str 32 + return get_msgpack_string(); + + case 0xDC: // array 16 + { + return get_msgpack_array(get_number()); + } + + case 0xDD: // array 32 + { + return get_msgpack_array(get_number()); + } + + case 0xDE: // map 16 + { + return get_msgpack_object(get_number()); + } + + case 0xDF: // map 32 + { + return get_msgpack_object(get_number()); + } + + // positive fixint + case 0xE0: + case 0xE1: + case 0xE2: + case 0xE3: + case 0xE4: + case 0xE5: + case 0xE6: + case 0xE7: + case 0xE8: + case 0xE9: + case 0xEA: + case 0xEB: + case 0xEC: + case 0xED: + case 0xEE: + case 0xEF: + case 0xF0: + case 0xF1: + case 0xF2: + case 0xF3: + case 0xF4: + case 0xF5: + case 0xF6: + case 0xF7: + case 0xF8: + case 0xF9: + case 0xFA: + case 0xFB: + case 0xFC: + case 0xFD: + case 0xFE: + case 0xFF: + return static_cast(current); + + default: // anything else + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "error reading MessagePack; last byte: 0x" + ss.str())); + } + } + } + + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + */ + BasicJsonType parse_ubjson_internal(const bool get_char = true) + { + return get_ubjson_value(get_char ? get_ignore_noop() : current); + } + + /*! + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a -'ve valued + `std::char_traits::eof()` in that case. + + @return character read from the input + */ + int get() + { + ++chars_read; + return (current = ia->get_character()); + } + + /*! + @return character read from the input after ignoring all 'N' entries + */ + int get_ignore_noop() + { + do + { + get(); + } + while (current == 'N'); + + return current; + } + + /* + @brief read a number from the input + + @tparam NumberType the type of the number + + @return number of type @a NumberType + + @note This function needs to respect the system's endianess, because + bytes in CBOR and MessagePack are stored in network order (big + endian) and therefore need reordering on little endian systems. + + @throw parse_error.110 if input has less than `sizeof(NumberType)` bytes + */ + template NumberType get_number() + { + // step 1: read input into array with system's byte order + std::array vec; + for (std::size_t i = 0; i < sizeof(NumberType); ++i) + { + get(); + unexpect_eof(); + + // reverse byte order prior to conversion if necessary + if (is_little_endian) + { + vec[sizeof(NumberType) - i - 1] = static_cast(current); + } + else + { + vec[i] = static_cast(current); // LCOV_EXCL_LINE + } + } + + // step 2: convert array into number of type T and return + NumberType result; + std::memcpy(&result, vec.data(), sizeof(NumberType)); + return result; + } + + /*! + @brief create a string by reading characters from the input + + @param[in] len number of bytes to read + + @note We can not reserve @a len bytes for the result, because @a len + may be too large. Usually, @ref unexpect_eof() detects the end of + the input before we run out of string memory. + + @return string created by reading @a len bytes + + @throw parse_error.110 if input has less than @a len bytes + */ + template + string_t get_string(const NumberType len) + { + string_t result; + std::generate_n(std::back_inserter(result), len, [this]() + { + get(); + unexpect_eof(); + return static_cast(current); + }); + return result; + } + + /*! + @brief reads a CBOR string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + Additionally, CBOR's strings with indefinite lengths are supported. + + @return string + + @throw parse_error.110 if input ended + @throw parse_error.113 if an unexpected byte is read + */ + string_t get_cbor_string() + { + unexpect_eof(); + + switch (current) + { + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6A: + case 0x6B: + case 0x6C: + case 0x6D: + case 0x6E: + case 0x6F: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + { + return get_string(current & 0x1F); + } + + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + { + return get_string(get_number()); + } + + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + { + return get_string(get_number()); + } + + case 0x7A: // UTF-8 string (four-byte uint32_t for n follow) + { + return get_string(get_number()); + } + + case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow) + { + return get_string(get_number()); + } + + case 0x7F: // UTF-8 string (indefinite length) + { + string_t result; + while (get() != 0xFF) + { + result.append(get_cbor_string()); + } + return result; + } + + default: + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, "expected a CBOR string; last byte: 0x" + ss.str())); + } + } + } + + template + BasicJsonType get_cbor_array(const NumberType len) + { + BasicJsonType result = value_t::array; + std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() + { + return parse_cbor_internal(); + }); + return result; + } + + template + BasicJsonType get_cbor_object(const NumberType len) + { + BasicJsonType result = value_t::object; + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + len, [this]() + { + get(); + auto key = get_cbor_string(); + auto val = parse_cbor_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + return result; + } + + /*! + @brief reads a MessagePack string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + + @return string + + @throw parse_error.110 if input ended + @throw parse_error.113 if an unexpected byte is read + */ + string_t get_msgpack_string() + { + unexpect_eof(); + + switch (current) + { + // fixstr + case 0xA0: + case 0xA1: + case 0xA2: + case 0xA3: + case 0xA4: + case 0xA5: + case 0xA6: + case 0xA7: + case 0xA8: + case 0xA9: + case 0xAA: + case 0xAB: + case 0xAC: + case 0xAD: + case 0xAE: + case 0xAF: + case 0xB0: + case 0xB1: + case 0xB2: + case 0xB3: + case 0xB4: + case 0xB5: + case 0xB6: + case 0xB7: + case 0xB8: + case 0xB9: + case 0xBA: + case 0xBB: + case 0xBC: + case 0xBD: + case 0xBE: + case 0xBF: + { + return get_string(current & 0x1F); + } + + case 0xD9: // str 8 + { + return get_string(get_number()); + } + + case 0xDA: // str 16 + { + return get_string(get_number()); + } + + case 0xDB: // str 32 + { + return get_string(get_number()); + } + + default: + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "expected a MessagePack string; last byte: 0x" + ss.str())); + } + } + } + + template + BasicJsonType get_msgpack_array(const NumberType len) + { + BasicJsonType result = value_t::array; + std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() + { + return parse_msgpack_internal(); + }); + return result; + } + + template + BasicJsonType get_msgpack_object(const NumberType len) + { + BasicJsonType result = value_t::object; + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + len, [this]() + { + get(); + auto key = get_msgpack_string(); + auto val = parse_msgpack_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + return result; + } + + /*! + @brief reads a UBJSON string + + This function is either called after reading the 'S' byte explicitly + indicating a string, or in case of an object key where the 'S' byte can be + left out. + + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + + @return string + + @throw parse_error.110 if input ended + @throw parse_error.113 if an unexpected byte is read + */ + string_t get_ubjson_string(const bool get_char = true) + { + if (get_char) + { + get(); // TODO: may we ignore N here? + } + + unexpect_eof(); + + switch (current) + { + case 'U': + return get_string(get_number()); + case 'i': + return get_string(get_number()); + case 'I': + return get_string(get_number()); + case 'l': + return get_string(get_number()); + case 'L': + return get_string(get_number()); + default: + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "expected a UBJSON string; last byte: 0x" + ss.str())); + } + } + + /*! + @brief determine the type and size for a container + + In the optimized UBJSON format, a type and a size can be provided to allow + for a more compact representation. + + @return pair of the size and the type + */ + std::pair get_ubjson_size_type() + { + std::size_t sz = string_t::npos; + int tc = 0; + + get_ignore_noop(); + + if (current == '$') + { + tc = get(); // must not ignore 'N', because 'N' maybe the type + unexpect_eof(); + + get_ignore_noop(); + if (current != '#') + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "expected '#' after UBJSON type information; last byte: 0x" + ss.str())); + } + sz = parse_ubjson_internal(); + } + else if (current == '#') + { + sz = parse_ubjson_internal(); + } + + return std::make_pair(sz, tc); + } + + BasicJsonType get_ubjson_value(const int prefix) + { + switch (prefix) + { + case std::char_traits::eof(): // EOF + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + case 'T': // true + return true; + case 'F': // false + return false; + + case 'Z': // null + return nullptr; + + case 'U': + return get_number(); + case 'i': + return get_number(); + case 'I': + return get_number(); + case 'l': + return get_number(); + case 'L': + return get_number(); + case 'd': + return get_number(); + case 'D': + return get_number(); + + case 'C': // char + { + get(); + unexpect_eof(); + if (JSON_UNLIKELY(current > 127)) + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + ss.str())); + } + return string_t(1, static_cast(current)); + } + + case 'S': // string + return get_ubjson_string(); + + case '[': // array + return get_ubjson_array(); + + case '{': // object + return get_ubjson_object(); + + default: // anything else + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "error reading UBJSON; last byte: 0x" + ss.str())); + } + } + + BasicJsonType get_ubjson_array() + { + BasicJsonType result = value_t::array; + const auto size_and_type = get_ubjson_size_type(); + + if (size_and_type.first != string_t::npos) + { + if (JSON_UNLIKELY(size_and_type.first > result.max_size())) + { + JSON_THROW(out_of_range::create(408, + "excessive array size: " + std::to_string(size_and_type.first))); + } + + if (size_and_type.second != 0) + { + if (size_and_type.second != 'N') + { + std::generate_n(std::back_inserter(*result.m_value.array), + size_and_type.first, [this, size_and_type]() + { + return get_ubjson_value(size_and_type.second); + }); + } + } + else + { + std::generate_n(std::back_inserter(*result.m_value.array), + size_and_type.first, [this]() + { + return parse_ubjson_internal(); + }); + } + } + else + { + while (current != ']') + { + result.push_back(parse_ubjson_internal(false)); + get_ignore_noop(); + } + } + + return result; + } + + BasicJsonType get_ubjson_object() + { + BasicJsonType result = value_t::object; + const auto size_and_type = get_ubjson_size_type(); + + if (size_and_type.first != string_t::npos) + { + if (JSON_UNLIKELY(size_and_type.first > result.max_size())) + { + JSON_THROW(out_of_range::create(408, + "excessive object size: " + std::to_string(size_and_type.first))); + } + + if (size_and_type.second != 0) + { + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + size_and_type.first, [this, size_and_type]() + { + auto key = get_ubjson_string(); + auto val = get_ubjson_value(size_and_type.second); + return std::make_pair(std::move(key), std::move(val)); + }); + } + else + { + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + size_and_type.first, [this]() + { + auto key = get_ubjson_string(); + auto val = parse_ubjson_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + } + } + else + { + while (current != '}') + { + auto key = get_ubjson_string(false); + result[std::move(key)] = parse_ubjson_internal(); + get_ignore_noop(); + } + } + + return result; + } + + /*! + @brief throw if end of input is not reached + @throw parse_error.110 if input not ended + */ + void expect_eof() const + { + if (JSON_UNLIKELY(current != std::char_traits::eof())) + { + JSON_THROW(parse_error::create(110, chars_read, "expected end of input")); + } + } + + /*! + @briefthrow if end of input is reached + @throw parse_error.110 if input ended + */ + void unexpect_eof() const + { + if (JSON_UNLIKELY(current == std::char_traits::eof())) + { + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + } + } + + private: + /// input adapter + input_adapter_t ia = nullptr; + + /// the current character + int current = std::char_traits::eof(); + + /// the number of characters read + std::size_t chars_read = 0; + + /// whether we can assume little endianess + const bool is_little_endian = little_endianess(); +}; +} +} + +// #include + + +#include // reverse +#include // array +#include // uint8_t, uint16_t, uint32_t, uint64_t +#include // memcpy +#include // numeric_limits + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// binary writer // +/////////////////// + +/*! +@brief serialization to CBOR and MessagePack values +*/ +template +class binary_writer +{ + public: + /*! + @brief create a binary writer + + @param[in] adapter output adapter to write to + */ + explicit binary_writer(output_adapter_t adapter) : oa(adapter) + { + assert(oa); + } + + /*! + @brief[in] j JSON value to serialize + */ + void write_cbor(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: + { + oa->write_character(static_cast(0xF6)); + break; + } + + case value_t::boolean: + { + oa->write_character(j.m_value.boolean + ? static_cast(0xF5) + : static_cast(0xF4)); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // CBOR does not differentiate between positive signed + // integers and unsigned integers. Therefore, we used the + // code from the value_t::number_unsigned case here. + if (j.m_value.number_integer <= 0x17) + { + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x18)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x19)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x1A)); + write_number(static_cast(j.m_value.number_integer)); + } + else + { + oa->write_character(static_cast(0x1B)); + write_number(static_cast(j.m_value.number_integer)); + } + } + else + { + // The conversions below encode the sign in the first + // byte, and the value is converted to a positive number. + const auto positive_number = -1 - j.m_value.number_integer; + if (j.m_value.number_integer >= -24) + { + write_number(static_cast(0x20 + positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x38)); + write_number(static_cast(positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x39)); + write_number(static_cast(positive_number)); + } + else if (positive_number <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x3A)); + write_number(static_cast(positive_number)); + } + else + { + oa->write_character(static_cast(0x3B)); + write_number(static_cast(positive_number)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= 0x17) + { + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x18)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x19)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x1A)); + write_number(static_cast(j.m_value.number_unsigned)); + } + else + { + oa->write_character(static_cast(0x1B)); + write_number(static_cast(j.m_value.number_unsigned)); + } + break; + } + + case value_t::number_float: // Double-Precision Float + { + oa->write_character(static_cast(0xFB)); + write_number(j.m_value.number_float); + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_value.string->size(); + if (N <= 0x17) + { + write_number(static_cast(0x60 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x78)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x79)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x7A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x7B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write the string + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_value.array->size(); + if (N <= 0x17) + { + write_number(static_cast(0x80 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x98)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x99)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x9A)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0x9B)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_value.array) + { + write_cbor(el); + } + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_value.object->size(); + if (N <= 0x17) + { + write_number(static_cast(0xA0 + N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0xB8)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0xB9)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0xBA)); + write_number(static_cast(N)); + } + // LCOV_EXCL_START + else if (N <= (std::numeric_limits::max)()) + { + oa->write_character(static_cast(0xBB)); + write_number(static_cast(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_value.object) + { + write_cbor(el.first); + write_cbor(el.second); + } + break; + } + + default: + break; + } + } + + /*! + @brief[in] j JSON value to serialize + */ + void write_msgpack(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: // nil + { + oa->write_character(static_cast(0xC0)); + break; + } + + case value_t::boolean: // true and false + { + oa->write_character(j.m_value.boolean + ? static_cast(0xC3) + : static_cast(0xC2)); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // MessagePack does not differentiate between positive + // signed integers and unsigned integers. Therefore, we used + // the code from the value_t::number_unsigned case here. + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 8 + oa->write_character(static_cast(0xCC)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 16 + oa->write_character(static_cast(0xCD)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 32 + oa->write_character(static_cast(0xCE)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 64 + oa->write_character(static_cast(0xCF)); + write_number(static_cast(j.m_value.number_integer)); + } + } + else + { + if (j.m_value.number_integer >= -32) + { + // negative fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 8 + oa->write_character(static_cast(0xD0)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 16 + oa->write_character(static_cast(0xD1)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 32 + oa->write_character(static_cast(0xD2)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits::min)() and + j.m_value.number_integer <= (std::numeric_limits::max)()) + { + // int 64 + oa->write_character(static_cast(0xD3)); + write_number(static_cast(j.m_value.number_integer)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 8 + oa->write_character(static_cast(0xCC)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 16 + oa->write_character(static_cast(0xCD)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 32 + oa->write_character(static_cast(0xCE)); + write_number(static_cast(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + // uint 64 + oa->write_character(static_cast(0xCF)); + write_number(static_cast(j.m_value.number_integer)); + } + break; + } + + case value_t::number_float: // float 64 + { + oa->write_character(static_cast(0xCB)); + write_number(j.m_value.number_float); + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_value.string->size(); + if (N <= 31) + { + // fixstr + write_number(static_cast(0xA0 | N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 8 + oa->write_character(static_cast(0xD9)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 16 + oa->write_character(static_cast(0xDA)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // str 32 + oa->write_character(static_cast(0xDB)); + write_number(static_cast(N)); + } + + // step 2: write the string + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_value.array->size(); + if (N <= 15) + { + // fixarray + write_number(static_cast(0x90 | N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // array 16 + oa->write_character(static_cast(0xDC)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // array 32 + oa->write_character(static_cast(0xDD)); + write_number(static_cast(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_value.array) + { + write_msgpack(el); + } + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_value.object->size(); + if (N <= 15) + { + // fixmap + write_number(static_cast(0x80 | (N & 0xF))); + } + else if (N <= (std::numeric_limits::max)()) + { + // map 16 + oa->write_character(static_cast(0xDE)); + write_number(static_cast(N)); + } + else if (N <= (std::numeric_limits::max)()) + { + // map 32 + oa->write_character(static_cast(0xDF)); + write_number(static_cast(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_value.object) + { + write_msgpack(el.first); + write_msgpack(el.second); + } + break; + } + + default: + break; + } + } + + /*! + @param[in] j JSON value to serialize + @param[in] use_count whether to use '#' prefixes (optimized format) + @param[in] use_type whether to use '$' prefixes (optimized format) + @param[in] add_prefix whether prefixes need to be used for this value + */ + void write_ubjson(const BasicJsonType& j, const bool use_count, + const bool use_type, const bool add_prefix = true) + { + switch (j.type()) + { + case value_t::null: + { + if (add_prefix) + { + oa->write_character(static_cast('Z')); + } + break; + } + + case value_t::boolean: + { + if (add_prefix) + oa->write_character(j.m_value.boolean + ? static_cast('T') + : static_cast('F')); + break; + } + + case value_t::number_integer: + { + write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix); + break; + } + + case value_t::number_unsigned: + { + write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix); + break; + } + + case value_t::number_float: + { + write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix); + break; + } + + case value_t::string: + { + if (add_prefix) + { + oa->write_character(static_cast('S')); + } + write_number_with_ubjson_prefix(j.m_value.string->size(), true); + oa->write_characters( + reinterpret_cast(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + if (add_prefix) + { + oa->write_character(static_cast('[')); + } + + bool prefix_required = true; + if (use_type and not j.m_value.array->empty()) + { + assert(use_count); + const char first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin() + 1, j.end(), + [this, first_prefix](const BasicJsonType & v) + { + return ubjson_prefix(v) == first_prefix; + }); + + if (same_prefix) + { + prefix_required = false; + oa->write_character(static_cast('$')); + oa->write_character(static_cast(first_prefix)); + } + } + + if (use_count) + { + oa->write_character(static_cast('#')); + write_number_with_ubjson_prefix(j.m_value.array->size(), true); + } + + for (const auto& el : *j.m_value.array) + { + write_ubjson(el, use_count, use_type, prefix_required); + } + + if (not use_count) + { + oa->write_character(static_cast(']')); + } + + break; + } + + case value_t::object: + { + if (add_prefix) + { + oa->write_character(static_cast('{')); + } + + bool prefix_required = true; + if (use_type and not j.m_value.object->empty()) + { + assert(use_count); + const char first_prefix = ubjson_prefix(j.front()); + const bool same_prefix = std::all_of(j.begin(), j.end(), + [this, first_prefix](const BasicJsonType & v) + { + return ubjson_prefix(v) == first_prefix; + }); + + if (same_prefix) + { + prefix_required = false; + oa->write_character(static_cast('$')); + oa->write_character(static_cast(first_prefix)); + } + } + + if (use_count) + { + oa->write_character(static_cast('#')); + write_number_with_ubjson_prefix(j.m_value.object->size(), true); + } + + for (const auto& el : *j.m_value.object) + { + write_number_with_ubjson_prefix(el.first.size(), true); + oa->write_characters( + reinterpret_cast(el.first.c_str()), + el.first.size()); + write_ubjson(el.second, use_count, use_type, prefix_required); + } + + if (not use_count) + { + oa->write_character(static_cast('}')); + } + + break; + } + + default: + break; + } + } + + private: + /* + @brief write a number to output input + + @param[in] n number of type @a NumberType + @tparam NumberType the type of the number + + @note This function needs to respect the system's endianess, because bytes + in CBOR, MessagePack, and UBJSON are stored in network order (big + endian) and therefore need reordering on little endian systems. + */ + template + void write_number(const NumberType n) + { + // step 1: write number to array of length NumberType + std::array vec; + std::memcpy(vec.data(), &n, sizeof(NumberType)); + + // step 2: write array to output (with possible reordering) + if (is_little_endian) + { + // reverse byte order prior to conversion if necessary + std::reverse(vec.begin(), vec.end()); + } + + oa->write_characters(vec.data(), sizeof(NumberType)); + } + + // UBJSON: write number (floating point) + template::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if (add_prefix) + { + oa->write_character(static_cast('D')); // float64 + } + write_number(n); + } + + // UBJSON: write number (unsigned integer) + template::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(static_cast('i')); // int8 + } + write_number(static_cast(n)); + } + else if (n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('U')); // uint8 + } + write_number(static_cast(n)); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(static_cast('I')); // int16 + } + write_number(static_cast(n)); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(static_cast('l')); // int32 + } + write_number(static_cast(n)); + } + else if (n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(static_cast('L')); // int64 + } + write_number(static_cast(n)); + } + else + { + JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(n))); + } + } + + // UBJSON: write number (signed integer) + template::value and + not std::is_floating_point::value, int>::type = 0> + void write_number_with_ubjson_prefix(const NumberType n, + const bool add_prefix) + { + if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('i')); // int8 + } + write_number(static_cast(n)); + } + else if (static_cast((std::numeric_limits::min)()) <= n and n <= static_cast((std::numeric_limits::max)())) + { + if (add_prefix) + { + oa->write_character(static_cast('U')); // uint8 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('I')); // int16 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('l')); // int32 + } + write_number(static_cast(n)); + } + else if ((std::numeric_limits::min)() <= n and n <= (std::numeric_limits::max)()) + { + if (add_prefix) + { + oa->write_character(static_cast('L')); // int64 + } + write_number(static_cast(n)); + } + // LCOV_EXCL_START + else + { + JSON_THROW(out_of_range::create(407, "number overflow serializing " + std::to_string(n))); + } + // LCOV_EXCL_STOP + } + + /*! + @brief determine the type prefix of container values + + @note This function does not need to be 100% accurate when it comes to + integer limits. In case a number exceeds the limits of int64_t, + this will be detected by a later call to function + write_number_with_ubjson_prefix. Therefore, we return 'L' for any + value that does not fit the previous limits. + */ + char ubjson_prefix(const BasicJsonType& j) const noexcept + { + switch (j.type()) + { + case value_t::null: + return 'Z'; + + case value_t::boolean: + return j.m_value.boolean ? 'T' : 'F'; + + case value_t::number_integer: + { + if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'i'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'U'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'I'; + } + else if ((std::numeric_limits::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits::max)()) + { + return 'l'; + } + else // no check and assume int64_t (see note above) + { + return 'L'; + } + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'i'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'U'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'I'; + } + else if (j.m_value.number_unsigned <= (std::numeric_limits::max)()) + { + return 'l'; + } + else // no check and assume int64_t (see note above) + { + return 'L'; + } + } + + case value_t::number_float: + return 'D'; + + case value_t::string: + return 'S'; + + case value_t::array: + return '['; + + case value_t::object: + return '{'; + + default: // discarded values + return 'N'; + } + } + + private: + /// whether we can assume little endianess + const bool is_little_endian = binary_reader::little_endianess(); + + /// the output + output_adapter_t oa = nullptr; +}; +} +} + +// #include + + +#include // reverse, remove, fill, find, none_of +#include // array +#include // assert +#include // and, or +#include // localeconv, lconv +#include // labs, isfinite, isnan, signbit +#include // size_t, ptrdiff_t +#include // uint8_t +#include // snprintf +#include // setfill +#include // next +#include // numeric_limits +#include // string +#include // stringstream +#include // is_same + +// #include + +// #include + + +#include // assert +#include // or, and, not +#include // signbit, isfinite +#include // intN_t, uintN_t +#include // memcpy, memmove + +namespace nlohmann +{ +namespace detail +{ + +/*! +@brief implements the Grisu2 algorithm for binary to decimal floating-point +conversion. + +This implementation is a slightly modified version of the reference +implementation which may be obtained from +http://florian.loitsch.com/publications (bench.tar.gz). + +The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch. + +For a detailed description of the algorithm see: + +[1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with + Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming + Language Design and Implementation, PLDI 2010 +[2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", + Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language + Design and Implementation, PLDI 1996 +*/ +namespace dtoa_impl +{ + +template +Target reinterpret_bits(const Source source) +{ + static_assert(sizeof(Target) == sizeof(Source), "size mismatch"); + + Target target; + std::memcpy(&target, &source, sizeof(Source)); + return target; +} + +struct diyfp // f * 2^e +{ + static constexpr int kPrecision = 64; // = q + + uint64_t f; + int e; + + constexpr diyfp() noexcept : f(0), e(0) {} + constexpr diyfp(uint64_t f_, int e_) noexcept : f(f_), e(e_) {} + + /*! + @brief returns x - y + @pre x.e == y.e and x.f >= y.f + */ + static diyfp sub(const diyfp& x, const diyfp& y) noexcept + { + assert(x.e == y.e); + assert(x.f >= y.f); + + return diyfp(x.f - y.f, x.e); + } + + /*! + @brief returns x * y + @note The result is rounded. (Only the upper q bits are returned.) + */ + static diyfp mul(const diyfp& x, const diyfp& y) noexcept + { + static_assert(kPrecision == 64, "internal error"); + + // Computes: + // f = round((x.f * y.f) / 2^q) + // e = x.e + y.e + q + + // Emulate the 64-bit * 64-bit multiplication: + // + // p = u * v + // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi) + // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi ) + // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 ) + // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 ) + // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3) + // = (p0_lo ) + 2^32 (Q ) + 2^64 (H ) + // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H ) + // + // (Since Q might be larger than 2^32 - 1) + // + // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H) + // + // (Q_hi + H does not overflow a 64-bit int) + // + // = p_lo + 2^64 p_hi + + const uint64_t u_lo = x.f & 0xFFFFFFFF; + const uint64_t u_hi = x.f >> 32; + const uint64_t v_lo = y.f & 0xFFFFFFFF; + const uint64_t v_hi = y.f >> 32; + + const uint64_t p0 = u_lo * v_lo; + const uint64_t p1 = u_lo * v_hi; + const uint64_t p2 = u_hi * v_lo; + const uint64_t p3 = u_hi * v_hi; + + const uint64_t p0_hi = p0 >> 32; + const uint64_t p1_lo = p1 & 0xFFFFFFFF; + const uint64_t p1_hi = p1 >> 32; + const uint64_t p2_lo = p2 & 0xFFFFFFFF; + const uint64_t p2_hi = p2 >> 32; + + uint64_t Q = p0_hi + p1_lo + p2_lo; + + // The full product might now be computed as + // + // p_hi = p3 + p2_hi + p1_hi + (Q >> 32) + // p_lo = p0_lo + (Q << 32) + // + // But in this particular case here, the full p_lo is not required. + // Effectively we only need to add the highest bit in p_lo to p_hi (and + // Q_hi + 1 does not overflow). + + Q += uint64_t{1} << (64 - 32 - 1); // round, ties up + + const uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32); + + return diyfp(h, x.e + y.e + 64); + } + + /*! + @brief normalize x such that the significand is >= 2^(q-1) + @pre x.f != 0 + */ + static diyfp normalize(diyfp x) noexcept + { + assert(x.f != 0); + + while ((x.f >> 63) == 0) + { + x.f <<= 1; + x.e--; + } + + return x; + } + + /*! + @brief normalize x such that the result has the exponent E + @pre e >= x.e and the upper e - x.e bits of x.f must be zero. + */ + static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept + { + const int delta = x.e - target_exponent; + + assert(delta >= 0); + assert(((x.f << delta) >> delta) == x.f); + + return diyfp(x.f << delta, target_exponent); + } +}; + +struct boundaries +{ + diyfp w; + diyfp minus; + diyfp plus; +}; + +/*! +Compute the (normalized) diyfp representing the input number 'value' and its +boundaries. + +@pre value must be finite and positive +*/ +template +boundaries compute_boundaries(FloatType value) +{ + assert(std::isfinite(value)); + assert(value > 0); + + // Convert the IEEE representation into a diyfp. + // + // If v is denormal: + // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1)) + // If v is normalized: + // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1)) + + static_assert(std::numeric_limits::is_iec559, + "internal error: dtoa_short requires an IEEE-754 floating-point implementation"); + + constexpr int kPrecision = std::numeric_limits::digits; // = p (includes the hidden bit) + constexpr int kBias = std::numeric_limits::max_exponent - 1 + (kPrecision - 1); + constexpr int kMinExp = 1 - kBias; + constexpr uint64_t kHiddenBit = uint64_t{1} << (kPrecision - 1); // = 2^(p-1) + + using bits_type = typename std::conditional< kPrecision == 24, uint32_t, uint64_t >::type; + + const uint64_t bits = reinterpret_bits(value); + const uint64_t E = bits >> (kPrecision - 1); + const uint64_t F = bits & (kHiddenBit - 1); + + const bool is_denormal = (E == 0); + const diyfp v = is_denormal + ? diyfp(F, kMinExp) + : diyfp(F + kHiddenBit, static_cast(E) - kBias); + + // Compute the boundaries m- and m+ of the floating-point value + // v = f * 2^e. + // + // Determine v- and v+, the floating-point predecessor and successor if v, + // respectively. + // + // v- = v - 2^e if f != 2^(p-1) or e == e_min (A) + // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B) + // + // v+ = v + 2^e + // + // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_ + // between m- and m+ round to v, regardless of how the input rounding + // algorithm breaks ties. + // + // ---+-------------+-------------+-------------+-------------+--- (A) + // v- m- v m+ v+ + // + // -----------------+------+------+-------------+-------------+--- (B) + // v- m- v m+ v+ + + const bool lower_boundary_is_closer = (F == 0 and E > 1); + const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1); + const diyfp m_minus = lower_boundary_is_closer + ? diyfp(4 * v.f - 1, v.e - 2) // (B) + : diyfp(2 * v.f - 1, v.e - 1); // (A) + + // Determine the normalized w+ = m+. + const diyfp w_plus = diyfp::normalize(m_plus); + + // Determine w- = m- such that e_(w-) = e_(w+). + const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e); + + return {diyfp::normalize(v), w_minus, w_plus}; +} + +// Given normalized diyfp w, Grisu needs to find a (normalized) cached +// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies +// within a certain range [alpha, gamma] (Definition 3.2 from [1]) +// +// alpha <= e = e_c + e_w + q <= gamma +// +// or +// +// f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q +// <= f_c * f_w * 2^gamma +// +// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies +// +// 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma +// +// or +// +// 2^(q - 2 + alpha) <= c * w < 2^(q + gamma) +// +// The choice of (alpha,gamma) determines the size of the table and the form of +// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well +// in practice: +// +// The idea is to cut the number c * w = f * 2^e into two parts, which can be +// processed independently: An integral part p1, and a fractional part p2: +// +// f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e +// = (f div 2^-e) + (f mod 2^-e) * 2^e +// = p1 + p2 * 2^e +// +// The conversion of p1 into decimal form requires a series of divisions and +// modulos by (a power of) 10. These operations are faster for 32-bit than for +// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be +// achieved by choosing +// +// -e >= 32 or e <= -32 := gamma +// +// In order to convert the fractional part +// +// p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ... +// +// into decimal form, the fraction is repeatedly multiplied by 10 and the digits +// d[-i] are extracted in order: +// +// (10 * p2) div 2^-e = d[-1] +// (10 * p2) mod 2^-e = d[-2] / 10^1 + ... +// +// The multiplication by 10 must not overflow. It is sufficient to choose +// +// 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64. +// +// Since p2 = f mod 2^-e < 2^-e, +// +// -e <= 60 or e >= -60 := alpha + +constexpr int kAlpha = -60; +constexpr int kGamma = -32; + +struct cached_power // c = f * 2^e ~= 10^k +{ + uint64_t f; + int e; + int k; +}; + +/*! +For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached +power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c +satisfies (Definition 3.2 from [1]) + + alpha <= e_c + e + q <= gamma. +*/ +inline cached_power get_cached_power_for_binary_exponent(int e) +{ + // Now + // + // alpha <= e_c + e + q <= gamma (1) + // ==> f_c * 2^alpha <= c * 2^e * 2^q + // + // and since the c's are normalized, 2^(q-1) <= f_c, + // + // ==> 2^(q - 1 + alpha) <= c * 2^(e + q) + // ==> 2^(alpha - e - 1) <= c + // + // If c were an exakt power of ten, i.e. c = 10^k, one may determine k as + // + // k = ceil( log_10( 2^(alpha - e - 1) ) ) + // = ceil( (alpha - e - 1) * log_10(2) ) + // + // From the paper: + // "In theory the result of the procedure could be wrong since c is rounded, + // and the computation itself is approximated [...]. In practice, however, + // this simple function is sufficient." + // + // For IEEE double precision floating-point numbers converted into + // normalized diyfp's w = f * 2^e, with q = 64, + // + // e >= -1022 (min IEEE exponent) + // -52 (p - 1) + // -52 (p - 1, possibly normalize denormal IEEE numbers) + // -11 (normalize the diyfp) + // = -1137 + // + // and + // + // e <= +1023 (max IEEE exponent) + // -52 (p - 1) + // -11 (normalize the diyfp) + // = 960 + // + // This binary exponent range [-1137,960] results in a decimal exponent + // range [-307,324]. One does not need to store a cached power for each + // k in this range. For each such k it suffices to find a cached power + // such that the exponent of the product lies in [alpha,gamma]. + // This implies that the difference of the decimal exponents of adjacent + // table entries must be less than or equal to + // + // floor( (gamma - alpha) * log_10(2) ) = 8. + // + // (A smaller distance gamma-alpha would require a larger table.) + + // NB: + // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34. + + constexpr int kCachedPowersSize = 79; + constexpr int kCachedPowersMinDecExp = -300; + constexpr int kCachedPowersDecStep = 8; + + static constexpr cached_power kCachedPowers[] = + { + { 0xAB70FE17C79AC6CA, -1060, -300 }, + { 0xFF77B1FCBEBCDC4F, -1034, -292 }, + { 0xBE5691EF416BD60C, -1007, -284 }, + { 0x8DD01FAD907FFC3C, -980, -276 }, + { 0xD3515C2831559A83, -954, -268 }, + { 0x9D71AC8FADA6C9B5, -927, -260 }, + { 0xEA9C227723EE8BCB, -901, -252 }, + { 0xAECC49914078536D, -874, -244 }, + { 0x823C12795DB6CE57, -847, -236 }, + { 0xC21094364DFB5637, -821, -228 }, + { 0x9096EA6F3848984F, -794, -220 }, + { 0xD77485CB25823AC7, -768, -212 }, + { 0xA086CFCD97BF97F4, -741, -204 }, + { 0xEF340A98172AACE5, -715, -196 }, + { 0xB23867FB2A35B28E, -688, -188 }, + { 0x84C8D4DFD2C63F3B, -661, -180 }, + { 0xC5DD44271AD3CDBA, -635, -172 }, + { 0x936B9FCEBB25C996, -608, -164 }, + { 0xDBAC6C247D62A584, -582, -156 }, + { 0xA3AB66580D5FDAF6, -555, -148 }, + { 0xF3E2F893DEC3F126, -529, -140 }, + { 0xB5B5ADA8AAFF80B8, -502, -132 }, + { 0x87625F056C7C4A8B, -475, -124 }, + { 0xC9BCFF6034C13053, -449, -116 }, + { 0x964E858C91BA2655, -422, -108 }, + { 0xDFF9772470297EBD, -396, -100 }, + { 0xA6DFBD9FB8E5B88F, -369, -92 }, + { 0xF8A95FCF88747D94, -343, -84 }, + { 0xB94470938FA89BCF, -316, -76 }, + { 0x8A08F0F8BF0F156B, -289, -68 }, + { 0xCDB02555653131B6, -263, -60 }, + { 0x993FE2C6D07B7FAC, -236, -52 }, + { 0xE45C10C42A2B3B06, -210, -44 }, + { 0xAA242499697392D3, -183, -36 }, + { 0xFD87B5F28300CA0E, -157, -28 }, + { 0xBCE5086492111AEB, -130, -20 }, + { 0x8CBCCC096F5088CC, -103, -12 }, + { 0xD1B71758E219652C, -77, -4 }, + { 0x9C40000000000000, -50, 4 }, + { 0xE8D4A51000000000, -24, 12 }, + { 0xAD78EBC5AC620000, 3, 20 }, + { 0x813F3978F8940984, 30, 28 }, + { 0xC097CE7BC90715B3, 56, 36 }, + { 0x8F7E32CE7BEA5C70, 83, 44 }, + { 0xD5D238A4ABE98068, 109, 52 }, + { 0x9F4F2726179A2245, 136, 60 }, + { 0xED63A231D4C4FB27, 162, 68 }, + { 0xB0DE65388CC8ADA8, 189, 76 }, + { 0x83C7088E1AAB65DB, 216, 84 }, + { 0xC45D1DF942711D9A, 242, 92 }, + { 0x924D692CA61BE758, 269, 100 }, + { 0xDA01EE641A708DEA, 295, 108 }, + { 0xA26DA3999AEF774A, 322, 116 }, + { 0xF209787BB47D6B85, 348, 124 }, + { 0xB454E4A179DD1877, 375, 132 }, + { 0x865B86925B9BC5C2, 402, 140 }, + { 0xC83553C5C8965D3D, 428, 148 }, + { 0x952AB45CFA97A0B3, 455, 156 }, + { 0xDE469FBD99A05FE3, 481, 164 }, + { 0xA59BC234DB398C25, 508, 172 }, + { 0xF6C69A72A3989F5C, 534, 180 }, + { 0xB7DCBF5354E9BECE, 561, 188 }, + { 0x88FCF317F22241E2, 588, 196 }, + { 0xCC20CE9BD35C78A5, 614, 204 }, + { 0x98165AF37B2153DF, 641, 212 }, + { 0xE2A0B5DC971F303A, 667, 220 }, + { 0xA8D9D1535CE3B396, 694, 228 }, + { 0xFB9B7CD9A4A7443C, 720, 236 }, + { 0xBB764C4CA7A44410, 747, 244 }, + { 0x8BAB8EEFB6409C1A, 774, 252 }, + { 0xD01FEF10A657842C, 800, 260 }, + { 0x9B10A4E5E9913129, 827, 268 }, + { 0xE7109BFBA19C0C9D, 853, 276 }, + { 0xAC2820D9623BF429, 880, 284 }, + { 0x80444B5E7AA7CF85, 907, 292 }, + { 0xBF21E44003ACDD2D, 933, 300 }, + { 0x8E679C2F5E44FF8F, 960, 308 }, + { 0xD433179D9C8CB841, 986, 316 }, + { 0x9E19DB92B4E31BA9, 1013, 324 }, + }; + + // This computation gives exactly the same results for k as + // k = ceil((kAlpha - e - 1) * 0.30102999566398114) + // for |e| <= 1500, but doesn't require floating-point operations. + // NB: log_10(2) ~= 78913 / 2^18 + assert(e >= -1500); + assert(e <= 1500); + const int f = kAlpha - e - 1; + const int k = (f * 78913) / (1 << 18) + (f > 0); + + const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep; + assert(index >= 0); + assert(index < kCachedPowersSize); + static_cast(kCachedPowersSize); // Fix warning. + + const cached_power cached = kCachedPowers[index]; + assert(kAlpha <= cached.e + e + 64); + assert(kGamma >= cached.e + e + 64); + + return cached; +} + +/*! +For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k. +For n == 0, returns 1 and sets pow10 := 1. +*/ +inline int find_largest_pow10(const uint32_t n, uint32_t& pow10) +{ + // LCOV_EXCL_START + if (n >= 1000000000) + { + pow10 = 1000000000; + return 10; + } + // LCOV_EXCL_STOP + else if (n >= 100000000) + { + pow10 = 100000000; + return 9; + } + else if (n >= 10000000) + { + pow10 = 10000000; + return 8; + } + else if (n >= 1000000) + { + pow10 = 1000000; + return 7; + } + else if (n >= 100000) + { + pow10 = 100000; + return 6; + } + else if (n >= 10000) + { + pow10 = 10000; + return 5; + } + else if (n >= 1000) + { + pow10 = 1000; + return 4; + } + else if (n >= 100) + { + pow10 = 100; + return 3; + } + else if (n >= 10) + { + pow10 = 10; + return 2; + } + else + { + pow10 = 1; + return 1; + } +} + +inline void grisu2_round(char* buf, int len, uint64_t dist, uint64_t delta, + uint64_t rest, uint64_t ten_k) +{ + assert(len >= 1); + assert(dist <= delta); + assert(rest <= delta); + assert(ten_k > 0); + + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // ten_k + // <------> + // <---- rest ----> + // --------------[------------------+----+--------------]-------------- + // w V + // = buf * 10^k + // + // ten_k represents a unit-in-the-last-place in the decimal representation + // stored in buf. + // Decrement buf by ten_k while this takes buf closer to w. + + // The tests are written in this order to avoid overflow in unsigned + // integer arithmetic. + + while (rest < dist + and delta - rest >= ten_k + and (rest + ten_k < dist or dist - rest > rest + ten_k - dist)) + { + assert(buf[len - 1] != '0'); + buf[len - 1]--; + rest += ten_k; + } +} + +/*! +Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+. +M- and M+ must be normalized and share the same exponent -60 <= e <= -32. +*/ +inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent, + diyfp M_minus, diyfp w, diyfp M_plus) +{ + static_assert(kAlpha >= -60, "internal error"); + static_assert(kGamma <= -32, "internal error"); + + // Generates the digits (and the exponent) of a decimal floating-point + // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's + // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma. + // + // <--------------------------- delta ----> + // <---- dist ---------> + // --------------[------------------+-------------------]-------------- + // M- w M+ + // + // Grisu2 generates the digits of M+ from left to right and stops as soon as + // V is in [M-,M+]. + + assert(M_plus.e >= kAlpha); + assert(M_plus.e <= kGamma); + + uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e) + uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e) + + // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0): + // + // M+ = f * 2^e + // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e + // = ((p1 ) * 2^-e + (p2 )) * 2^e + // = p1 + p2 * 2^e + + const diyfp one(uint64_t{1} << -M_plus.e, M_plus.e); + + uint32_t p1 = static_cast(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.) + uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e + + // 1) + // + // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0] + + assert(p1 > 0); + + uint32_t pow10; + const int k = find_largest_pow10(p1, pow10); + + // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1) + // + // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1)) + // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1)) + // + // M+ = p1 + p2 * 2^e + // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e + // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e + // = d[k-1] * 10^(k-1) + ( rest) * 2^e + // + // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0) + // + // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0] + // + // but stop as soon as + // + // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e + + int n = k; + while (n > 0) + { + // Invariants: + // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k) + // pow10 = 10^(n-1) <= p1 < 10^n + // + const uint32_t d = p1 / pow10; // d = p1 div 10^(n-1) + const uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1) + // + // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e + // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e) + // + assert(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(n-1) + (r + p2 * 2^e) + // + p1 = r; + n--; + // + // M+ = buffer * 10^n + (p1 + p2 * 2^e) + // pow10 = 10^n + // + + // Now check if enough digits have been generated. + // Compute + // + // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e + // + // Note: + // Since rest and delta share the same exponent e, it suffices to + // compare the significands. + const uint64_t rest = (uint64_t{p1} << -one.e) + p2; + if (rest <= delta) + { + // V = buffer * 10^n, with M- <= V <= M+. + + decimal_exponent += n; + + // We may now just stop. But instead look if the buffer could be + // decremented to bring V closer to w. + // + // pow10 = 10^n is now 1 ulp in the decimal representation V. + // The rounding procedure works with diyfp's with an implicit + // exponent of e. + // + // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e + // + const uint64_t ten_n = uint64_t{pow10} << -one.e; + grisu2_round(buffer, length, dist, delta, rest, ten_n); + + return; + } + + pow10 /= 10; + // + // pow10 = 10^(n-1) <= p1 < 10^n + // Invariants restored. + } + + // 2) + // + // The digits of the integral part have been generated: + // + // M+ = d[k-1]...d[1]d[0] + p2 * 2^e + // = buffer + p2 * 2^e + // + // Now generate the digits of the fractional part p2 * 2^e. + // + // Note: + // No decimal point is generated: the exponent is adjusted instead. + // + // p2 actually represents the fraction + // + // p2 * 2^e + // = p2 / 2^-e + // = d[-1] / 10^1 + d[-2] / 10^2 + ... + // + // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...) + // + // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m + // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...) + // + // using + // + // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e) + // = ( d) * 2^-e + ( r) + // + // or + // 10^m * p2 * 2^e = d + r * 2^e + // + // i.e. + // + // M+ = buffer + p2 * 2^e + // = buffer + 10^-m * (d + r * 2^e) + // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e + // + // and stop as soon as 10^-m * r * 2^e <= delta * 2^e + + assert(p2 > delta); + + int m = 0; + for (;;) + { + // Invariant: + // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e + // = buffer * 10^-m + 10^-m * (p2 ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e + // + assert(p2 <= UINT64_MAX / 10); + p2 *= 10; + const uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e + const uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e + // + // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e + // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e)) + // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + assert(d <= 9); + buffer[length++] = static_cast('0' + d); // buffer := buffer * 10 + d + // + // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e + // + p2 = r; + m++; + // + // M+ = buffer * 10^-m + 10^-m * p2 * 2^e + // Invariant restored. + + // Check if enough digits have been generated. + // + // 10^-m * p2 * 2^e <= delta * 2^e + // p2 * 2^e <= 10^m * delta * 2^e + // p2 <= 10^m * delta + delta *= 10; + dist *= 10; + if (p2 <= delta) + { + break; + } + } + + // V = buffer * 10^-m, with M- <= V <= M+. + + decimal_exponent -= m; + + // 1 ulp in the decimal representation is now 10^-m. + // Since delta and dist are now scaled by 10^m, we need to do the + // same with ulp in order to keep the units in sync. + // + // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e + // + const uint64_t ten_m = one.f; + grisu2_round(buffer, length, dist, delta, p2, ten_m); + + // By construction this algorithm generates the shortest possible decimal + // number (Loitsch, Theorem 6.2) which rounds back to w. + // For an input number of precision p, at least + // + // N = 1 + ceil(p * log_10(2)) + // + // decimal digits are sufficient to identify all binary floating-point + // numbers (Matula, "In-and-Out conversions"). + // This implies that the algorithm does not produce more than N decimal + // digits. + // + // N = 17 for p = 53 (IEEE double precision) + // N = 9 for p = 24 (IEEE single precision) +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +inline void grisu2(char* buf, int& len, int& decimal_exponent, + diyfp m_minus, diyfp v, diyfp m_plus) +{ + assert(m_plus.e == m_minus.e); + assert(m_plus.e == v.e); + + // --------(-----------------------+-----------------------)-------- (A) + // m- v m+ + // + // --------------------(-----------+-----------------------)-------- (B) + // m- v m+ + // + // First scale v (and m- and m+) such that the exponent is in the range + // [alpha, gamma]. + + const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e); + + const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k + + // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma] + const diyfp w = diyfp::mul(v, c_minus_k); + const diyfp w_minus = diyfp::mul(m_minus, c_minus_k); + const diyfp w_plus = diyfp::mul(m_plus, c_minus_k); + + // ----(---+---)---------------(---+---)---------------(---+---)---- + // w- w w+ + // = c*m- = c*v = c*m+ + // + // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and + // w+ are now off by a small amount. + // In fact: + // + // w - v * 10^k < 1 ulp + // + // To account for this inaccuracy, add resp. subtract 1 ulp. + // + // --------+---[---------------(---+---)---------------]---+-------- + // w- M- w M+ w+ + // + // Now any number in [M-, M+] (bounds included) will round to w when input, + // regardless of how the input rounding algorithm breaks ties. + // + // And digit_gen generates the shortest possible such number in [M-, M+]. + // Note that this does not mean that Grisu2 always generates the shortest + // possible number in the interval (m-, m+). + const diyfp M_minus(w_minus.f + 1, w_minus.e); + const diyfp M_plus (w_plus.f - 1, w_plus.e ); + + decimal_exponent = -cached.k; // = -(-k) = k + + grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus); +} + +/*! +v = buf * 10^decimal_exponent +len is the length of the buffer (number of decimal digits) +The buffer must be large enough, i.e. >= max_digits10. +*/ +template +void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value) +{ + static_assert(diyfp::kPrecision >= std::numeric_limits::digits + 3, + "internal error: not enough precision"); + + assert(std::isfinite(value)); + assert(value > 0); + + // If the neighbors (and boundaries) of 'value' are always computed for double-precision + // numbers, all float's can be recovered using strtod (and strtof). However, the resulting + // decimal representations are not exactly "short". + // + // The documentation for 'std::to_chars' (http://en.cppreference.com/w/cpp/utility/to_chars) + // says "value is converted to a string as if by std::sprintf in the default ("C") locale" + // and since sprintf promotes float's to double's, I think this is exactly what 'std::to_chars' + // does. + // On the other hand, the documentation for 'std::to_chars' requires that "parsing the + // representation using the corresponding std::from_chars function recovers value exactly". That + // indicates that single precision floating-point numbers should be recovered using + // 'std::strtof'. + // + // NB: If the neighbors are computed for single-precision numbers, there is a single float + // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision + // value is off by 1 ulp. +#if 0 + const boundaries w = compute_boundaries(static_cast(value)); +#else + const boundaries w = compute_boundaries(value); +#endif + + grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus); +} + +/*! +@brief appends a decimal representation of e to buf +@return a pointer to the element following the exponent. +@pre -1000 < e < 1000 +*/ +inline char* append_exponent(char* buf, int e) +{ + assert(e > -1000); + assert(e < 1000); + + if (e < 0) + { + e = -e; + *buf++ = '-'; + } + else + { + *buf++ = '+'; + } + + uint32_t k = static_cast(e); + if (k < 10) + { + // Always print at least two digits in the exponent. + // This is for compatibility with printf("%g"). + *buf++ = '0'; + *buf++ = static_cast('0' + k); + } + else if (k < 100) + { + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + else + { + *buf++ = static_cast('0' + k / 100); + k %= 100; + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } + + return buf; +} + +/*! +@brief prettify v = buf * 10^decimal_exponent + +If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point +notation. Otherwise it will be printed in exponential notation. + +@pre min_exp < 0 +@pre max_exp > 0 +*/ +inline char* format_buffer(char* buf, int len, int decimal_exponent, + int min_exp, int max_exp) +{ + assert(min_exp < 0); + assert(max_exp > 0); + + const int k = len; + const int n = len + decimal_exponent; + + // v = buf * 10^(n-k) + // k is the length of the buffer (number of decimal digits) + // n is the position of the decimal point relative to the start of the buffer. + + if (k <= n and n <= max_exp) + { + // digits[000] + // len <= max_exp + 2 + + std::memset(buf + k, '0', static_cast(n - k)); + // Make it look like a floating-point number (#362, #378) + buf[n + 0] = '.'; + buf[n + 1] = '0'; + return buf + (n + 2); + } + + if (0 < n and n <= max_exp) + { + // dig.its + // len <= max_digits10 + 1 + + assert(k > n); + + std::memmove(buf + (n + 1), buf + n, static_cast(k - n)); + buf[n] = '.'; + return buf + (k + 1); + } + + if (min_exp < n and n <= 0) + { + // 0.[000]digits + // len <= 2 + (-min_exp - 1) + max_digits10 + + std::memmove(buf + (2 + -n), buf, static_cast(k)); + buf[0] = '0'; + buf[1] = '.'; + std::memset(buf + 2, '0', static_cast(-n)); + return buf + (2 + (-n) + k); + } + + if (k == 1) + { + // dE+123 + // len <= 1 + 5 + + buf += 1; + } + else + { + // d.igitsE+123 + // len <= max_digits10 + 1 + 5 + + std::memmove(buf + 2, buf + 1, static_cast(k - 1)); + buf[1] = '.'; + buf += 1 + k; + } + + *buf++ = 'e'; + return append_exponent(buf, n - 1); +} + +} // namespace dtoa_impl + +/*! +@brief generates a decimal representation of the floating-point number value in [first, last). + +The format of the resulting decimal representation is similar to printf's %g +format. Returns an iterator pointing past-the-end of the decimal representation. + +@note The input number must be finite, i.e. NaN's and Inf's are not supported. +@note The buffer must be large enough. +@note The result is NOT null-terminated. +*/ +template +char* to_chars(char* first, char* last, FloatType value) +{ + static_cast(last); // maybe unused - fix warning + assert(std::isfinite(value)); + + // Use signbit(value) instead of (value < 0) since signbit works for -0. + if (std::signbit(value)) + { + value = -value; + *first++ = '-'; + } + + if (value == 0) // +-0 + { + *first++ = '0'; + // Make it look like a floating-point number (#362, #378) + *first++ = '.'; + *first++ = '0'; + return first; + } + + assert(last - first >= std::numeric_limits::max_digits10); + + // Compute v = buffer * 10^decimal_exponent. + // The decimal digits are stored in the buffer, which needs to be interpreted + // as an unsigned decimal integer. + // len is the length of the buffer, i.e. the number of decimal digits. + int len = 0; + int decimal_exponent = 0; + dtoa_impl::grisu2(first, len, decimal_exponent, value); + + assert(len <= std::numeric_limits::max_digits10); + + // Format the buffer like printf("%.*g", prec, value) + constexpr int kMinExp = -4; + // Use digits10 here to increase compatibility with version 2. + constexpr int kMaxExp = std::numeric_limits::digits10; + + assert(last - first >= kMaxExp + 2); + assert(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits::max_digits10); + assert(last - first >= std::numeric_limits::max_digits10 + 6); + + return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); +} + +} // namespace detail +} // namespace nlohmann + +// #include + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +namespace detail +{ +/////////////////// +// serialization // +/////////////////// + +template +class serializer +{ + using string_t = typename BasicJsonType::string_t; + using number_float_t = typename BasicJsonType::number_float_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + static constexpr uint8_t UTF8_ACCEPT = 0; + static constexpr uint8_t UTF8_REJECT = 1; + + public: + /*! + @param[in] s output stream to serialize to + @param[in] ichar indentation character to use + */ + serializer(output_adapter_t s, const char ichar) + : o(std::move(s)), loc(std::localeconv()), + thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep)), + decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point)), + indent_char(ichar), indent_string(512, indent_char) + {} + + // delete because of pointer members + serializer(const serializer&) = delete; + serializer& operator=(const serializer&) = delete; + + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and organizes + the serialization internally. The indentation level is propagated as + additional parameter. In case of arrays and objects, the function is + called recursively. + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + + @param[in] val value to serialize + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] indent_step the indent level + @param[in] current_indent the current indent level (only used internally) + */ + void dump(const BasicJsonType& val, const bool pretty_print, + const bool ensure_ascii, + const unsigned int indent_step, + const unsigned int current_indent = 0) + { + switch (val.m_type) + { + case value_t::object: + { + if (val.m_value.object->empty()) + { + o->write_characters("{}", 2); + return; + } + + if (pretty_print) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } + else + { + o->write_character('{'); + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + + o->write_character('}'); + } + + return; + } + + case value_t::array: + { + if (val.m_value.array->empty()) + { + o->write_characters("[]", 2); + return; + } + + if (pretty_print) + { + o->write_characters("[\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + dump(*i, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + assert(not val.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character(']'); + } + else + { + o->write_character('['); + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + dump(*i, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + assert(not val.m_value.array->empty()); + dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); + + o->write_character(']'); + } + + return; + } + + case value_t::string: + { + o->write_character('\"'); + dump_escaped(*val.m_value.string, ensure_ascii); + o->write_character('\"'); + return; + } + + case value_t::boolean: + { + if (val.m_value.boolean) + { + o->write_characters("true", 4); + } + else + { + o->write_characters("false", 5); + } + return; + } + + case value_t::number_integer: + { + dump_integer(val.m_value.number_integer); + return; + } + + case value_t::number_unsigned: + { + dump_integer(val.m_value.number_unsigned); + return; + } + + case value_t::number_float: + { + dump_float(val.m_value.number_float); + return; + } + + case value_t::discarded: + { + o->write_characters("", 11); + return; + } + + case value_t::null: + { + o->write_characters("null", 4); + return; + } + } + } + + private: + /*! + @brief dump escaped string + + Escape a string by replacing certain special characters by a sequence of an + escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. The escaped string is written to output stream @a o. + + @param[in] s the string to escape + @param[in] ensure_ascii whether to escape non-ASCII characters with + \uXXXX sequences + + @complexity Linear in the length of string @a s. + */ + void dump_escaped(const string_t& s, const bool ensure_ascii) + { + uint32_t codepoint; + uint8_t state = UTF8_ACCEPT; + std::size_t bytes = 0; // number of bytes written to string_buffer + + for (std::size_t i = 0; i < s.size(); ++i) + { + const auto byte = static_cast(s[i]); + + switch (decode(state, codepoint, byte)) + { + case UTF8_ACCEPT: // decode found a new code point + { + switch (codepoint) + { + case 0x08: // backspace + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'b'; + break; + } + + case 0x09: // horizontal tab + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 't'; + break; + } + + case 0x0A: // newline + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'n'; + break; + } + + case 0x0C: // formfeed + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'f'; + break; + } + + case 0x0D: // carriage return + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = 'r'; + break; + } + + case 0x22: // quotation mark + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\"'; + break; + } + + case 0x5C: // reverse solidus + { + string_buffer[bytes++] = '\\'; + string_buffer[bytes++] = '\\'; + break; + } + + default: + { + // escape control characters (0x00..0x1F) or, if + // ensure_ascii parameter is used, non-ASCII characters + if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F))) + { + if (codepoint <= 0xFFFF) + { + std::snprintf(string_buffer.data() + bytes, 7, "\\u%04x", + static_cast(codepoint)); + bytes += 6; + } + else + { + std::snprintf(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x", + static_cast(0xD7C0 + (codepoint >> 10)), + static_cast(0xDC00 + (codepoint & 0x3FF))); + bytes += 12; + } + } + else + { + // copy byte to buffer (all previous bytes + // been copied have in default case above) + string_buffer[bytes++] = s[i]; + } + break; + } + } + + // write buffer and reset index; there must be 13 bytes + // left, as this is the maximal number of bytes to be + // written ("\uxxxx\uxxxx\0") for one code point + if (string_buffer.size() - bytes < 13) + { + o->write_characters(string_buffer.data(), bytes); + bytes = 0; + } + break; + } + + case UTF8_REJECT: // decode found invalid UTF-8 byte + { + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast(byte); + JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + ss.str())); + } + + default: // decode found yet incomplete multi-byte code point + { + if (not ensure_ascii) + { + // code point will not be escaped - copy byte to buffer + string_buffer[bytes++] = s[i]; + } + break; + } + } + } + + if (JSON_LIKELY(state == UTF8_ACCEPT)) + { + // write buffer + if (bytes > 0) + { + o->write_characters(string_buffer.data(), bytes); + } + } + else + { + // we finish reading, but do not accept: string was incomplete + std::stringstream ss; + ss << std::setw(2) << std::uppercase << std::setfill('0') << std::hex << static_cast(static_cast(s.back())); + JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + ss.str())); + } + } + + /*! + @brief dump an integer + + Dump a given integer to output stream @a o. Works internally with + @a number_buffer. + + @param[in] x integer number (signed or unsigned) to dump + @tparam NumberType either @a number_integer_t or @a number_unsigned_t + */ + template::value or + std::is_same::value, + int> = 0> + void dump_integer(NumberType x) + { + // special case for "0" + if (x == 0) + { + o->write_character('0'); + return; + } + + const bool is_negative = (x <= 0) and (x != 0); // see issue #755 + std::size_t i = 0; + + while (x != 0) + { + // spare 1 byte for '\0' + assert(i < number_buffer.size() - 1); + + const auto digit = std::labs(static_cast(x % 10)); + number_buffer[i++] = static_cast('0' + digit); + x /= 10; + } + + if (is_negative) + { + // make sure there is capacity for the '-' + assert(i < number_buffer.size() - 2); + number_buffer[i++] = '-'; + } + + std::reverse(number_buffer.begin(), number_buffer.begin() + i); + o->write_characters(number_buffer.data(), i); + } + + /*! + @brief dump a floating-point number + + Dump a given floating-point number to output stream @a o. Works internally + with @a number_buffer. + + @param[in] x floating-point number to dump + */ + void dump_float(number_float_t x) + { + // NaN / inf + if (not std::isfinite(x)) + { + o->write_characters("null", 4); + return; + } + + // If number_float_t is an IEEE-754 single or double precision number, + // use the Grisu2 algorithm to produce short numbers which are + // guaranteed to round-trip, using strtof and strtod, resp. + // + // NB: The test below works if == . + static constexpr bool is_ieee_single_or_double + = (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 24 and std::numeric_limits::max_exponent == 128) or + (std::numeric_limits::is_iec559 and std::numeric_limits::digits == 53 and std::numeric_limits::max_exponent == 1024); + + dump_float(x, std::integral_constant()); + } + + void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) + { + char* begin = number_buffer.data(); + char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x); + + o->write_characters(begin, static_cast(end - begin)); + } + + void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/) + { + // get number of digits for a float -> text -> float round-trip + static constexpr auto d = std::numeric_limits::max_digits10; + + // the actual conversion + std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x); + + // negative value indicates an error + assert(len > 0); + // check if buffer was large enough + assert(static_cast(len) < number_buffer.size()); + + // erase thousands separator + if (thousands_sep != '\0') + { + const auto end = std::remove(number_buffer.begin(), + number_buffer.begin() + len, thousands_sep); + std::fill(end, number_buffer.end(), '\0'); + assert((end - number_buffer.begin()) <= len); + len = (end - number_buffer.begin()); + } + + // convert decimal point to '.' + if (decimal_point != '\0' and decimal_point != '.') + { + const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); + if (dec_pos != number_buffer.end()) + { + *dec_pos = '.'; + } + } + + o->write_characters(number_buffer.data(), static_cast(len)); + + // determine if need to append ".0" + const bool value_is_int_like = + std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, + [](char c) + { + return (c == '.' or c == 'e'); + }); + + if (value_is_int_like) + { + o->write_characters(".0", 2); + } + } + + /*! + @brief check whether a string is UTF-8 encoded + + The function checks each byte of a string whether it is UTF-8 encoded. The + result of the check is stored in the @a state parameter. The function must + be called initially with state 0 (accept). State 1 means the string must + be rejected, because the current byte is not allowed. If the string is + completely processed, but the state is non-zero, the string ended + prematurely; that is, the last byte indicated more bytes should have + followed. + + @param[in,out] state the state of the decoding + @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT) + @param[in] byte next byte to decode + @return new state + + @note The function has been edited: a std::array is used. + + @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann + @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + */ + static uint8_t decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept + { + static const std::array utf8d = + { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF + 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF + 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8 + } + }; + + const uint8_t type = utf8d[byte]; + + codep = (state != UTF8_ACCEPT) + ? (byte & 0x3fu) | (codep << 6) + : static_cast(0xff >> type) & (byte); + + state = utf8d[256u + state * 16u + type]; + return state; + } + + private: + /// the output of the serializer + output_adapter_t o = nullptr; + + /// a (hopefully) large enough character buffer + std::array number_buffer{{}}; + + /// the locale + const std::lconv* loc = nullptr; + /// the locale's thousand separator character + const char thousands_sep = '\0'; + /// the locale's decimal point character + const char decimal_point = '\0'; + + /// string buffer + std::array string_buffer{{}}; + + /// the indentation character + const char indent_char; + /// the indentation string + string_t indent_string; +}; +} +} + +// #include + + +#include +#include + +namespace nlohmann +{ +namespace detail +{ +template +class json_ref +{ + public: + using value_type = BasicJsonType; + + json_ref(value_type&& value) + : owned_value(std::move(value)), value_ref(&owned_value), is_rvalue(true) + {} + + json_ref(const value_type& value) + : value_ref(const_cast(&value)), is_rvalue(false) + {} + + json_ref(std::initializer_list init) + : owned_value(init), value_ref(&owned_value), is_rvalue(true) + {} + + template + json_ref(Args&& ... args) + : owned_value(std::forward(args)...), value_ref(&owned_value), is_rvalue(true) + {} + + // class should be movable only + json_ref(json_ref&&) = default; + json_ref(const json_ref&) = delete; + json_ref& operator=(const json_ref&) = delete; + + value_type moved_or_copied() const + { + if (is_rvalue) + { + return std::move(*value_ref); + } + return *value_ref; + } + + value_type const& operator*() const + { + return *static_cast(value_ref); + } + + value_type const* operator->() const + { + return static_cast(value_ref); + } + + private: + mutable value_type owned_value = nullptr; + value_type* value_ref = nullptr; + const bool is_rvalue; +}; +} +} + +// #include + + +#include // assert +#include // accumulate +#include // string +#include // vector + +// #include + +// #include + +// #include + + +namespace nlohmann +{ +template +class json_pointer +{ + // allow basic_json to access private members + NLOHMANN_BASIC_JSON_TPL_DECLARATION + friend class basic_json; + + public: + /*! + @brief create JSON pointer + + Create a JSON pointer according to the syntax described in + [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). + + @param[in] s string representing the JSON pointer; if omitted, the empty + string is assumed which references the whole JSON value + + @throw parse_error.107 if the given JSON pointer @a s is nonempty and does + not begin with a slash (`/`); see example below + + @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is + not followed by `0` (representing `~`) or `1` (representing `/`); see + example below + + @liveexample{The example shows the construction several valid JSON pointers + as well as the exceptional behavior.,json_pointer} + + @since version 2.0.0 + */ + explicit json_pointer(const std::string& s = "") + : reference_tokens(split(s)) + {} + + /*! + @brief return a string representation of the JSON pointer + + @invariant For each JSON pointer `ptr`, it holds: + @code {.cpp} + ptr == json_pointer(ptr.to_string()); + @endcode + + @return a string representation of the JSON pointer + + @liveexample{The example shows the result of `to_string`., + json_pointer__to_string} + + @since version 2.0.0 + */ + std::string to_string() const noexcept + { + return std::accumulate(reference_tokens.begin(), reference_tokens.end(), + std::string{}, + [](const std::string & a, const std::string & b) + { + return a + "/" + escape(b); + }); + } + + /// @copydoc to_string() + operator std::string() const + { + return to_string(); + } + + /*! + @param[in] s reference token to be converted into an array index + + @return integer representation of @a s + + @throw out_of_range.404 if string @a s could not be converted to an integer + */ + static int array_index(const std::string& s) + { + std::size_t processed_chars = 0; + const int res = std::stoi(s, &processed_chars); + + // check if the string was completely read + if (JSON_UNLIKELY(processed_chars != s.size())) + { + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'")); + } + + return res; + } + + private: + /*! + @brief remove and return last reference pointer + @throw out_of_range.405 if JSON pointer has no parent + */ + std::string pop_back() + { + if (JSON_UNLIKELY(is_root())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + auto last = reference_tokens.back(); + reference_tokens.pop_back(); + return last; + } + + /// return whether pointer points to the root document + bool is_root() const + { + return reference_tokens.empty(); + } + + json_pointer top() const + { + if (JSON_UNLIKELY(is_root())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; + } + + /*! + @brief create and return a reference to the pointed to value + + @complexity Linear in the number of reference tokens. + + @throw parse_error.109 if array index is not a number + @throw type_error.313 if value cannot be unflattened + */ + BasicJsonType& get_and_create(BasicJsonType& j) const + { + using size_type = typename BasicJsonType::size_type; + auto result = &j; + + // in case no reference tokens exist, return a reference to the JSON value + // j which will be overwritten by a primitive value + for (const auto& reference_token : reference_tokens) + { + switch (result->m_type) + { + case detail::value_t::null: + { + if (reference_token == "0") + { + // start a new array if reference token is 0 + result = &result->operator[](0); + } + else + { + // start a new object otherwise + result = &result->operator[](reference_token); + } + break; + } + + case detail::value_t::object: + { + // create an entry in the object + result = &result->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // create an entry in the array + JSON_TRY + { + result = &result->operator[](static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + /* + The following code is only reached if there exists a reference + token _and_ the current value is primitive. In this case, we have + an error situation, because primitive values may only occur as + single value; that is, with an empty list of reference tokens. + */ + default: + JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); + } + } + + return *result; + } + + /*! + @brief return a reference to the pointed to value + + @note This version does not throw if a value is not present, but tries to + create nested values instead. For instance, calling this function + with pointer `"/this/that"` on a null value is equivalent to calling + `operator[]("this").operator[]("that")` on that value, effectively + changing the null value to an object. + + @param[in] ptr a JSON value + + @return reference to the JSON value pointed to by the JSON pointer + + @complexity Linear in the length of the JSON pointer. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + BasicJsonType& get_unchecked(BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + // convert null values to arrays or objects before continuing + if (ptr->m_type == detail::value_t::null) + { + // check if reference token is a number + const bool nums = + std::all_of(reference_token.begin(), reference_token.end(), + [](const char x) + { + return (x >= '0' and x <= '9'); + }); + + // change value to array for numbers or "-" or to object otherwise + *ptr = (nums or reference_token == "-") + ? detail::value_t::array + : detail::value_t::object; + } + + switch (ptr->m_type) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + if (reference_token == "-") + { + // explicitly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_value.array->size()); + } + else + { + // convert array index to number; unchecked access + JSON_TRY + { + ptr = &ptr->operator[]( + static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + BasicJsonType& get_checked(BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // note: at performs range check + JSON_TRY + { + ptr = &ptr->at(static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @brief return a const reference to the pointed to value + + @param[in] ptr a JSON value + + @return const reference to the JSON value pointed to by the JSON + pointer + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" cannot be used for const access + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // use unchecked array access + JSON_TRY + { + ptr = &ptr->operator[]( + static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + const BasicJsonType& get_checked(const BasicJsonType* ptr) const + { + using size_type = typename BasicJsonType::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // note: at performs range check + JSON_TRY + { + ptr = &ptr->at(static_cast(array_index(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; + } + + /*! + @brief split the string input to reference tokens + + @note This function is only called by the json_pointer constructor. + All exceptions below are documented there. + + @throw parse_error.107 if the pointer is not empty or begins with '/' + @throw parse_error.108 if character '~' is not followed by '0' or '1' + */ + static std::vector split(const std::string& reference_string) + { + std::vector result; + + // special case: empty reference string -> no reference tokens + if (reference_string.empty()) + { + return result; + } + + // check if nonempty reference string begins with slash + if (JSON_UNLIKELY(reference_string[0] != '/')) + { + JSON_THROW(detail::parse_error::create(107, 1, + "JSON pointer must be empty or begin with '/' - was: '" + + reference_string + "'")); + } + + // extract the reference tokens: + // - slash: position of the last read slash (or end of string) + // - start: position after the previous slash + for ( + // search for the first slash after the first character + std::size_t slash = reference_string.find_first_of('/', 1), + // set the beginning of the first reference token + start = 1; + // we can stop if start == string::npos+1 = 0 + start != 0; + // set the beginning of the next reference token + // (will eventually be 0 if slash == std::string::npos) + start = slash + 1, + // find next slash + slash = reference_string.find_first_of('/', start)) + { + // use the text between the beginning of the reference token + // (start) and the last slash (slash). + auto reference_token = reference_string.substr(start, slash - start); + + // check reference tokens are properly escaped + for (std::size_t pos = reference_token.find_first_of('~'); + pos != std::string::npos; + pos = reference_token.find_first_of('~', pos + 1)) + { + assert(reference_token[pos] == '~'); + + // ~ must be followed by 0 or 1 + if (JSON_UNLIKELY(pos == reference_token.size() - 1 or + (reference_token[pos + 1] != '0' and + reference_token[pos + 1] != '1'))) + { + JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); + } + } + + // finally, store the reference token + unescape(reference_token); + result.push_back(reference_token); + } + + return result; + } + + /*! + @brief replace all occurrences of a substring by another string + + @param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t + @param[in] f the substring to replace with @a t + @param[in] t the string to replace @a f + + @pre The search string @a f must not be empty. **This precondition is + enforced with an assertion.** + + @since version 2.0.0 + */ + static void replace_substring(std::string& s, const std::string& f, + const std::string& t) + { + assert(not f.empty()); + for (auto pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} + } + + /// escape "~"" to "~0" and "/" to "~1" + static std::string escape(std::string s) + { + replace_substring(s, "~", "~0"); + replace_substring(s, "/", "~1"); + return s; + } + + /// unescape "~1" to tilde and "~0" to slash (order is important!) + static void unescape(std::string& s) + { + replace_substring(s, "~1", "/"); + replace_substring(s, "~0", "~"); + } + + /*! + @param[in] reference_string the reference string to the current value + @param[in] value the value to consider + @param[in,out] result the result object to insert values to + + @note Empty objects or arrays are flattened to `null`. + */ + static void flatten(const std::string& reference_string, + const BasicJsonType& value, + BasicJsonType& result) + { + switch (value.m_type) + { + case detail::value_t::array: + { + if (value.m_value.array->empty()) + { + // flatten empty array as null + result[reference_string] = nullptr; + } + else + { + // iterate array and use index as reference string + for (std::size_t i = 0; i < value.m_value.array->size(); ++i) + { + flatten(reference_string + "/" + std::to_string(i), + value.m_value.array->operator[](i), result); + } + } + break; + } + + case detail::value_t::object: + { + if (value.m_value.object->empty()) + { + // flatten empty object as null + result[reference_string] = nullptr; + } + else + { + // iterate object and use keys as reference string + for (const auto& element : *value.m_value.object) + { + flatten(reference_string + "/" + escape(element.first), element.second, result); + } + } + break; + } + + default: + { + // add primitive value with its reference string + result[reference_string] = value; + break; + } + } + } + + /*! + @param[in] value flattened JSON + + @return unflattened JSON + + @throw parse_error.109 if array index is not a number + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + @throw type_error.313 if value cannot be unflattened + */ + static BasicJsonType + unflatten(const BasicJsonType& value) + { + if (JSON_UNLIKELY(not value.is_object())) + { + JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); + } + + BasicJsonType result; + + // iterate the JSON object values + for (const auto& element : *value.m_value.object) + { + if (JSON_UNLIKELY(not element.second.is_primitive())) + { + JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); + } + + // assign value to reference pointed to by JSON pointer; Note that if + // the JSON pointer is "" (i.e., points to the whole value), function + // get_and_create returns a reference to result itself. An assignment + // will then create a primitive value. + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; + } + + friend bool operator==(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return (lhs.reference_tokens == rhs.reference_tokens); + } + + friend bool operator!=(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return not (lhs == rhs); + } + + /// the reference tokens + std::vector reference_tokens; +}; +} + +// #include + + +#include + +// #include + +// #include + + +namespace nlohmann +{ +template +struct adl_serializer +{ + /*! + @brief convert a JSON value to any value type + + This function is usually called by the `get()` function of the + @ref basic_json class (either explicit or via conversion operators). + + @param[in] j JSON value to read from + @param[in,out] val value to write to + */ + template + static void from_json(BasicJsonType&& j, ValueType& val) noexcept( + noexcept(::nlohmann::from_json(std::forward(j), val))) + { + ::nlohmann::from_json(std::forward(j), val); + } + + /*! + @brief convert any value type to a JSON value + + This function is usually called by the constructors of the @ref basic_json + class. + + @param[in,out] j JSON value to write to + @param[in] val value to read from + */ + template + static void to_json(BasicJsonType& j, ValueType&& val) noexcept( + noexcept(::nlohmann::to_json(j, std::forward(val)))) + { + ::nlohmann::to_json(j, std::forward(val)); + } +}; +} + + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ + +/*! +@brief a class to store JSON values + +@tparam ObjectType type for JSON objects (`std::map` by default; will be used +in @ref object_t) +@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used +in @ref array_t) +@tparam StringType type for JSON strings and object keys (`std::string` by +default; will be used in @ref string_t) +@tparam BooleanType type for JSON booleans (`bool` by default; will be used +in @ref boolean_t) +@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by +default; will be used in @ref number_integer_t) +@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c +`uint64_t` by default; will be used in @ref number_unsigned_t) +@tparam NumberFloatType type for JSON floating-point numbers (`double` by +default; will be used in @ref number_float_t) +@tparam AllocatorType type of the allocator to use (`std::allocator` by +default) +@tparam JSONSerializer the serializer to resolve internal calls to `to_json()` +and `from_json()` (@ref adl_serializer by default) + +@requirement The class satisfies the following concept requirements: +- Basic + - [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible): + JSON values can be default constructed. The result will be a JSON null + value. + - [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible): + A JSON value can be constructed from an rvalue argument. + - [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible): + A JSON value can be copy-constructed from an lvalue expression. + - [MoveAssignable](http://en.cppreference.com/w/cpp/concept/MoveAssignable): + A JSON value van be assigned from an rvalue argument. + - [CopyAssignable](http://en.cppreference.com/w/cpp/concept/CopyAssignable): + A JSON value can be copy-assigned from an lvalue expression. + - [Destructible](http://en.cppreference.com/w/cpp/concept/Destructible): + JSON values can be destructed. +- Layout + - [StandardLayoutType](http://en.cppreference.com/w/cpp/concept/StandardLayoutType): + JSON values have + [standard layout](http://en.cppreference.com/w/cpp/language/data_members#Standard_layout): + All non-static data members are private and standard layout types, the + class has no virtual functions or (virtual) base classes. +- Library-wide + - [EqualityComparable](http://en.cppreference.com/w/cpp/concept/EqualityComparable): + JSON values can be compared with `==`, see @ref + operator==(const_reference,const_reference). + - [LessThanComparable](http://en.cppreference.com/w/cpp/concept/LessThanComparable): + JSON values can be compared with `<`, see @ref + operator<(const_reference,const_reference). + - [Swappable](http://en.cppreference.com/w/cpp/concept/Swappable): + Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of + other compatible types, using unqualified function call @ref swap(). + - [NullablePointer](http://en.cppreference.com/w/cpp/concept/NullablePointer): + JSON values can be compared against `std::nullptr_t` objects which are used + to model the `null` value. +- Container + - [Container](http://en.cppreference.com/w/cpp/concept/Container): + JSON values can be used like STL containers and provide iterator access. + - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer); + JSON values can be used like STL containers and provide reverse iterator + access. + +@invariant The member variables @a m_value and @a m_type have the following +relationship: +- If `m_type == value_t::object`, then `m_value.object != nullptr`. +- If `m_type == value_t::array`, then `m_value.array != nullptr`. +- If `m_type == value_t::string`, then `m_value.string != nullptr`. +The invariants are checked by member function assert_invariant(). + +@internal +@note ObjectType trick from http://stackoverflow.com/a/9860911 +@endinternal + +@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange +Format](http://rfc7159.net/rfc7159) + +@since version 1.0.0 + +@nosubgrouping +*/ +NLOHMANN_BASIC_JSON_TPL_DECLARATION +class basic_json +{ + private: + template friend struct detail::external_constructor; + friend ::nlohmann::json_pointer; + friend ::nlohmann::detail::parser; + friend ::nlohmann::detail::serializer; + template + friend class ::nlohmann::detail::iter_impl; + template + friend class ::nlohmann::detail::binary_writer; + template + friend class ::nlohmann::detail::binary_reader; + + /// workaround type for MSVC + using basic_json_t = NLOHMANN_BASIC_JSON_TPL; + + // convenience aliases for types residing in namespace detail; + using lexer = ::nlohmann::detail::lexer; + using parser = ::nlohmann::detail::parser; + + using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; + template + using internal_iterator = ::nlohmann::detail::internal_iterator; + template + using iter_impl = ::nlohmann::detail::iter_impl; + template + using iteration_proxy = ::nlohmann::detail::iteration_proxy; + template using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator; + + template + using output_adapter_t = ::nlohmann::detail::output_adapter_t; + + using binary_reader = ::nlohmann::detail::binary_reader; + template using binary_writer = ::nlohmann::detail::binary_writer; + + using serializer = ::nlohmann::detail::serializer; + + public: + using value_t = detail::value_t; + /// @copydoc nlohmann::json_pointer + using json_pointer = ::nlohmann::json_pointer; + template + using json_serializer = JSONSerializer; + /// helper type for initializer lists of basic_json values + using initializer_list_t = std::initializer_list>; + + //////////////// + // exceptions // + //////////////// + + /// @name exceptions + /// Classes to implement user-defined exceptions. + /// @{ + + /// @copydoc detail::exception + using exception = detail::exception; + /// @copydoc detail::parse_error + using parse_error = detail::parse_error; + /// @copydoc detail::invalid_iterator + using invalid_iterator = detail::invalid_iterator; + /// @copydoc detail::type_error + using type_error = detail::type_error; + /// @copydoc detail::out_of_range + using out_of_range = detail::out_of_range; + /// @copydoc detail::other_error + using other_error = detail::other_error; + + /// @} + + + ///////////////////// + // container types // + ///////////////////// + + /// @name container types + /// The canonic container types to use @ref basic_json like any other STL + /// container. + /// @{ + + /// the type of elements in a basic_json container + using value_type = basic_json; + + /// the type of an element reference + using reference = value_type&; + /// the type of an element const reference + using const_reference = const value_type&; + + /// a type to represent differences between iterators + using difference_type = std::ptrdiff_t; + /// a type to represent container sizes + using size_type = std::size_t; + + /// the allocator type + using allocator_type = AllocatorType; + + /// the type of an element pointer + using pointer = typename std::allocator_traits::pointer; + /// the type of an element const pointer + using const_pointer = typename std::allocator_traits::const_pointer; + + /// an iterator for a basic_json container + using iterator = iter_impl; + /// a const iterator for a basic_json container + using const_iterator = iter_impl; + /// a reverse iterator for a basic_json container + using reverse_iterator = json_reverse_iterator; + /// a const reverse iterator for a basic_json container + using const_reverse_iterator = json_reverse_iterator; + + /// @} + + + /*! + @brief returns the allocator associated with the container + */ + static allocator_type get_allocator() + { + return allocator_type(); + } + + /*! + @brief returns version information on the library + + This function returns a JSON object with information about the library, + including the version number and information on the platform and compiler. + + @return JSON object holding version information + key | description + ----------- | --------------- + `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version). + `copyright` | The copyright line for the library as string. + `name` | The name of the library as string. + `platform` | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`. + `url` | The URL of the project as string. + `version` | The version of the library. It is an object with the following keys: `major`, `minor`, and `patch` as defined by [Semantic Versioning](http://semver.org), and `string` (the version string). + + @liveexample{The following code shows an example output of the `meta()` + function.,meta} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @complexity Constant. + + @since 2.1.0 + */ + static basic_json meta() + { + basic_json result; + + result["copyright"] = "(C) 2013-2017 Niels Lohmann"; + result["name"] = "JSON for Modern C++"; + result["url"] = "https://github.com/nlohmann/json"; + result["version"]["string"] = + std::to_string(NLOHMANN_JSON_VERSION_MAJOR) + "." + + std::to_string(NLOHMANN_JSON_VERSION_MINOR) + "." + + std::to_string(NLOHMANN_JSON_VERSION_PATCH); + result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR; + result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR; + result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH; + +#ifdef _WIN32 + result["platform"] = "win32"; +#elif defined __linux__ + result["platform"] = "linux"; +#elif defined __APPLE__ + result["platform"] = "apple"; +#elif defined __unix__ + result["platform"] = "unix"; +#else + result["platform"] = "unknown"; +#endif + +#if defined(__ICC) || defined(__INTEL_COMPILER) + result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; +#elif defined(__clang__) + result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; +#elif defined(__GNUC__) || defined(__GNUG__) + result["compiler"] = {{"family", "gcc"}, {"version", std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__)}}; +#elif defined(__HP_cc) || defined(__HP_aCC) + result["compiler"] = "hp" +#elif defined(__IBMCPP__) + result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; +#elif defined(_MSC_VER) + result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; +#elif defined(__PGI) + result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; +#elif defined(__SUNPRO_CC) + result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; +#else + result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; +#endif + +#ifdef __cplusplus + result["compiler"]["c++"] = std::to_string(__cplusplus); +#else + result["compiler"]["c++"] = "unknown"; +#endif + return result; + } + + + /////////////////////////// + // JSON value data types // + /////////////////////////// + + /// @name JSON value data types + /// The data types to store a JSON value. These types are derived from + /// the template arguments passed to class @ref basic_json. + /// @{ + +#if defined(JSON_HAS_CPP_14) + // Use transparent comparator if possible, combined with perfect forwarding + // on find() and count() calls prevents unnecessary string construction. + using object_comparator_t = std::less<>; +#else + using object_comparator_t = std::less; +#endif + + /*! + @brief a type for an object + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows: + > An object is an unordered collection of zero or more name/value pairs, + > where a name is a string and a value is a string, number, boolean, null, + > object, or array. + + To store objects in C++, a type is defined by the template parameters + described below. + + @tparam ObjectType the container to store objects (e.g., `std::map` or + `std::unordered_map`) + @tparam StringType the type of the keys or names (e.g., `std::string`). + The comparison function `std::less` is used to order elements + inside the container. + @tparam AllocatorType the allocator to use for objects (e.g., + `std::allocator`) + + #### Default type + + With the default values for @a ObjectType (`std::map`), @a StringType + (`std::string`), and @a AllocatorType (`std::allocator`), the default + value for @a object_t is: + + @code {.cpp} + std::map< + std::string, // key_type + basic_json, // value_type + std::less, // key_compare + std::allocator> // allocator_type + > + @endcode + + #### Behavior + + The choice of @a object_t influences the behavior of the JSON class. With + the default type, objects have the following behavior: + + - When all names are unique, objects will be interoperable in the sense + that all software implementations receiving that object will agree on + the name-value mappings. + - When the names within an object are not unique, it is unspecified which + one of the values for a given key will be chosen. For instance, + `{"key": 2, "key": 1}` could be equal to either `{"key": 1}` or + `{"key": 2}`. + - Internally, name/value pairs are stored in lexicographical order of the + names. Objects will also be serialized (see @ref dump) in this order. + For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored + and serialized as `{"a": 2, "b": 1}`. + - When comparing objects, the order of the name/value pairs is irrelevant. + This makes objects interoperable in the sense that they will not be + affected by these differences. For instance, `{"b": 1, "a": 2}` and + `{"a": 2, "b": 1}` will be treated as equal. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the object's limit of nesting is not explicitly constrained. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON object. + + #### Storage + + Objects are stored as pointers in a @ref basic_json type. That is, for any + access to object values, a pointer of type `object_t*` must be + dereferenced. + + @sa @ref array_t -- type for an array value + + @since version 1.0.0 + + @note The order name/value pairs are added to the object is *not* + preserved by the library. Therefore, iterating an object may return + name/value pairs in a different order than they were originally stored. In + fact, keys will be traversed in alphabetical order as `std::map` with + `std::less` is used by default. Please note this behavior conforms to [RFC + 7159](http://rfc7159.net/rfc7159), because any order implements the + specified "unordered" nature of JSON objects. + */ + using object_t = ObjectType>>; + + /*! + @brief a type for an array + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: + > An array is an ordered sequence of zero or more values. + + To store objects in C++, a type is defined by the template parameters + explained below. + + @tparam ArrayType container type to store arrays (e.g., `std::vector` or + `std::list`) + @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) + + #### Default type + + With the default values for @a ArrayType (`std::vector`) and @a + AllocatorType (`std::allocator`), the default value for @a array_t is: + + @code {.cpp} + std::vector< + basic_json, // value_type + std::allocator // allocator_type + > + @endcode + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the array's limit of nesting is not explicitly constrained. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON array. + + #### Storage + + Arrays are stored as pointers in a @ref basic_json type. That is, for any + access to array values, a pointer of type `array_t*` must be dereferenced. + + @sa @ref object_t -- type for an object value + + @since version 1.0.0 + */ + using array_t = ArrayType>; + + /*! + @brief a type for a string + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: + > A string is a sequence of zero or more Unicode characters. + + To store objects in C++, a type is defined by the template parameter + described below. Unicode values are split by the JSON class into + byte-sized characters during deserialization. + + @tparam StringType the container to store strings (e.g., `std::string`). + Note this container is used for keys/names in objects, see @ref object_t. + + #### Default type + + With the default values for @a StringType (`std::string`), the default + value for @a string_t is: + + @code {.cpp} + std::string + @endcode + + #### Encoding + + Strings are stored in UTF-8 encoding. Therefore, functions like + `std::string::size()` or `std::string::length()` return the number of + bytes in the string rather than the number of characters or glyphs. + + #### String comparison + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > Software implementations are typically required to test names of object + > members for equality. Implementations that transform the textual + > representation into sequences of Unicode code units and then perform the + > comparison numerically, code unit by code unit, are interoperable in the + > sense that implementations will agree in all cases on equality or + > inequality of two strings. For example, implementations that compare + > strings with escaped characters unconverted may incorrectly find that + > `"a\\b"` and `"a\u005Cb"` are not equal. + + This implementation is interoperable as it does compare strings code unit + by code unit. + + #### Storage + + String values are stored as pointers in a @ref basic_json type. That is, + for any access to string values, a pointer of type `string_t*` must be + dereferenced. + + @since version 1.0.0 + */ + using string_t = StringType; + + /*! + @brief a type for a boolean + + [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a + type which differentiates the two literals `true` and `false`. + + To store objects in C++, a type is defined by the template parameter @a + BooleanType which chooses the type to use. + + #### Default type + + With the default values for @a BooleanType (`bool`), the default value for + @a boolean_t is: + + @code {.cpp} + bool + @endcode + + #### Storage + + Boolean values are stored directly inside a @ref basic_json type. + + @since version 1.0.0 + */ + using boolean_t = BooleanType; + + /*! + @brief a type for a number (integer) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store integer numbers in C++, a type is defined by the template + parameter @a NumberIntegerType which chooses the type to use. + + #### Default type + + With the default values for @a NumberIntegerType (`int64_t`), the default + value for @a number_integer_t is: + + @code {.cpp} + int64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `9223372036854775807` (INT64_MAX) and the minimal integer number + that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers + that are out of range will yield over/underflow when used in a + constructor. During deserialization, too large or small integer numbers + will be automatically be stored as @ref number_unsigned_t or @ref + number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange of the exactly supported range [INT64_MIN, + INT64_MAX], this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_integer_t = NumberIntegerType; + + /*! + @brief a type for a number (unsigned) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store unsigned integer numbers in C++, a type is defined by the + template parameter @a NumberUnsignedType which chooses the type to use. + + #### Default type + + With the default values for @a NumberUnsignedType (`uint64_t`), the + default value for @a number_unsigned_t is: + + @code {.cpp} + uint64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `18446744073709551615` (UINT64_MAX) and the minimal integer + number that can be stored is `0`. Integer numbers that are out of range + will yield over/underflow when used in a constructor. During + deserialization, too large or small integer numbers will be automatically + be stored as @ref number_integer_t or @ref number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange (when considered in conjunction with the + number_integer_t type) of the exactly supported range [0, UINT64_MAX], + this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + @sa @ref number_integer_t -- type for number values (integer) + + @since version 2.0.0 + */ + using number_unsigned_t = NumberUnsignedType; + + /*! + @brief a type for a number (floating-point) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store floating-point numbers in C++, a type is defined by the template + parameter @a NumberFloatType which chooses the type to use. + + #### Default type + + With the default values for @a NumberFloatType (`double`), the default + value for @a number_float_t is: + + @code {.cpp} + double + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in floating-point literals will be ignored. Internally, + the value will be stored as decimal number. For instance, the C++ + floating-point literal `01.2` will be serialized to `1.2`. During + deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > This specification allows implementations to set limits on the range and + > precision of numbers accepted. Since software that implements IEEE + > 754-2008 binary64 (double precision) numbers is generally available and + > widely used, good interoperability can be achieved by implementations + > that expect no more precision or range than these provide, in the sense + > that implementations will approximate JSON numbers within the expected + > precision. + + This implementation does exactly follow this approach, as it uses double + precision floating-point numbers. Note values smaller than + `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` + will be stored as NaN internally and be serialized to `null`. + + #### Storage + + Floating-point number values are stored directly inside a @ref basic_json + type. + + @sa @ref number_integer_t -- type for number values (integer) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_float_t = NumberFloatType; + + /// @} + + private: + + /// helper for exception-safe object creation + template + static T* create(Args&& ... args) + { + AllocatorType alloc; + using AllocatorTraits = std::allocator_traits>; + + auto deleter = [&](T * object) + { + AllocatorTraits::deallocate(alloc, object, 1); + }; + std::unique_ptr object(AllocatorTraits::allocate(alloc, 1), deleter); + AllocatorTraits::construct(alloc, object.get(), std::forward(args)...); + assert(object != nullptr); + return object.release(); + } + + //////////////////////// + // JSON value storage // + //////////////////////// + + /*! + @brief a JSON value + + The actual storage for a JSON value of the @ref basic_json class. This + union combines the different storage types for the JSON value types + defined in @ref value_t. + + JSON type | value_t type | used type + --------- | --------------- | ------------------------ + object | object | pointer to @ref object_t + array | array | pointer to @ref array_t + string | string | pointer to @ref string_t + boolean | boolean | @ref boolean_t + number | number_integer | @ref number_integer_t + number | number_unsigned | @ref number_unsigned_t + number | number_float | @ref number_float_t + null | null | *no value is stored* + + @note Variable-length types (objects, arrays, and strings) are stored as + pointers. The size of the union should not exceed 64 bits if the default + value types are used. + + @since version 1.0.0 + */ + union json_value + { + /// object (stored with pointer to save storage) + object_t* object; + /// array (stored with pointer to save storage) + array_t* array; + /// string (stored with pointer to save storage) + string_t* string; + /// boolean + boolean_t boolean; + /// number (integer) + number_integer_t number_integer; + /// number (unsigned integer) + number_unsigned_t number_unsigned; + /// number (floating-point) + number_float_t number_float; + + /// default constructor (for null values) + json_value() = default; + /// constructor for booleans + json_value(boolean_t v) noexcept : boolean(v) {} + /// constructor for numbers (integer) + json_value(number_integer_t v) noexcept : number_integer(v) {} + /// constructor for numbers (unsigned) + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} + /// constructor for numbers (floating-point) + json_value(number_float_t v) noexcept : number_float(v) {} + /// constructor for empty values of a given type + json_value(value_t t) + { + switch (t) + { + case value_t::object: + { + object = create(); + break; + } + + case value_t::array: + { + array = create(); + break; + } + + case value_t::string: + { + string = create(""); + break; + } + + case value_t::boolean: + { + boolean = boolean_t(false); + break; + } + + case value_t::number_integer: + { + number_integer = number_integer_t(0); + break; + } + + case value_t::number_unsigned: + { + number_unsigned = number_unsigned_t(0); + break; + } + + case value_t::number_float: + { + number_float = number_float_t(0.0); + break; + } + + case value_t::null: + { + object = nullptr; // silence warning, see #821 + break; + } + + default: + { + object = nullptr; // silence warning, see #821 + if (JSON_UNLIKELY(t == value_t::null)) + { + JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.1.2")); // LCOV_EXCL_LINE + } + break; + } + } + } + + /// constructor for strings + json_value(const string_t& value) + { + string = create(value); + } + + /// constructor for rvalue strings + json_value(string_t&& value) + { + string = create(std::move(value)); + } + + /// constructor for objects + json_value(const object_t& value) + { + object = create(value); + } + + /// constructor for rvalue objects + json_value(object_t&& value) + { + object = create(std::move(value)); + } + + /// constructor for arrays + json_value(const array_t& value) + { + array = create(value); + } + + /// constructor for rvalue arrays + json_value(array_t&& value) + { + array = create(std::move(value)); + } + + void destroy(value_t t) noexcept + { + switch (t) + { + case value_t::object: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, object); + std::allocator_traits::deallocate(alloc, object, 1); + break; + } + + case value_t::array: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, array); + std::allocator_traits::deallocate(alloc, array, 1); + break; + } + + case value_t::string: + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, string); + std::allocator_traits::deallocate(alloc, string, 1); + break; + } + + default: + { + break; + } + } + } + }; + + /*! + @brief checks the class invariants + + This function asserts the class invariants. It needs to be called at the + end of every constructor to make sure that created objects respect the + invariant. Furthermore, it has to be called each time the type of a JSON + value is changed, because the invariant expresses a relationship between + @a m_type and @a m_value. + */ + void assert_invariant() const noexcept + { + assert(m_type != value_t::object or m_value.object != nullptr); + assert(m_type != value_t::array or m_value.array != nullptr); + assert(m_type != value_t::string or m_value.string != nullptr); + } + + public: + ////////////////////////// + // JSON parser callback // + ////////////////////////// + + /*! + @brief parser event types + + The parser callback distinguishes the following events: + - `object_start`: the parser read `{` and started to process a JSON object + - `key`: the parser read a key of a value in an object + - `object_end`: the parser read `}` and finished processing a JSON object + - `array_start`: the parser read `[` and started to process a JSON array + - `array_end`: the parser read `]` and finished processing a JSON array + - `value`: the parser finished reading a JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + @sa @ref parser_callback_t for more information and examples + */ + using parse_event_t = typename parser::parse_event_t; + + /*! + @brief per-element parser callback type + + With a parser callback function, the result of parsing a JSON text can be + influenced. When passed to @ref parse, it is called on certain events + (passed as @ref parse_event_t via parameter @a event) with a set recursion + depth @a depth and context JSON value @a parsed. The return value of the + callback function is a boolean indicating whether the element that emitted + the callback shall be kept or not. + + We distinguish six scenarios (determined by the event type) in which the + callback function can be called. The following table describes the values + of the parameters @a depth, @a event, and @a parsed. + + parameter @a event | description | parameter @a depth | parameter @a parsed + ------------------ | ----------- | ------------------ | ------------------- + parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded + parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key + parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object + parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded + parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array + parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + Discarding a value (i.e., returning `false`) has different effects + depending on the context in which function was called: + + - Discarded values in structured types are skipped. That is, the parser + will behave as if the discarded value was never read. + - In case a value outside a structured type is skipped, it is replaced + with `null`. This case happens if the top-level element is skipped. + + @param[in] depth the depth of the recursion during parsing + + @param[in] event an event of type parse_event_t indicating the context in + the callback function has been called + + @param[in,out] parsed the current intermediate parse result; note that + writing to this value has no effect for parse_event_t::key events + + @return Whether the JSON value which called the function during parsing + should be kept (`true`) or not (`false`). In the latter case, it is either + skipped completely or replaced by an empty discarded object. + + @sa @ref parse for examples + + @since version 1.0.0 + */ + using parser_callback_t = typename parser::parser_callback_t; + + + ////////////////// + // constructors // + ////////////////// + + /// @name constructors and destructors + /// Constructors of class @ref basic_json, copy/move constructor, copy + /// assignment, static functions creating objects, and the destructor. + /// @{ + + /*! + @brief create an empty value with a given type + + Create an empty JSON value with a given type. The value will be default + initialized with an empty value which depends on the type: + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @param[in] v the type of the value to create + + @complexity Constant. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows the constructor for different @ref + value_t values,basic_json__value_t} + + @sa @ref clear() -- restores the postcondition of this constructor + + @since version 1.0.0 + */ + basic_json(const value_t v) + : m_type(v), m_value(v) + { + assert_invariant(); + } + + /*! + @brief create a null object + + Create a `null` JSON value. It either takes a null pointer as parameter + (explicitly creating `null`) or no parameter (implicitly creating `null`). + The passed null pointer itself is not read -- it is only used to choose + the right constructor. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @liveexample{The following code shows the constructor with and without a + null pointer parameter.,basic_json__nullptr_t} + + @since version 1.0.0 + */ + basic_json(std::nullptr_t = nullptr) noexcept + : basic_json(value_t::null) + { + assert_invariant(); + } + + /*! + @brief create a JSON value + + This is a "catch all" constructor for all compatible JSON types; that is, + types for which a `to_json()` method exists. The constructor forwards the + parameter @a val to that method (to `json_serializer::to_json` method + with `U = uncvref_t`, to be exact). + + Template type @a CompatibleType includes, but is not limited to, the + following types: + - **arrays**: @ref array_t and all kinds of compatible containers such as + `std::vector`, `std::deque`, `std::list`, `std::forward_list`, + `std::array`, `std::valarray`, `std::set`, `std::unordered_set`, + `std::multiset`, and `std::unordered_multiset` with a `value_type` from + which a @ref basic_json value can be constructed. + - **objects**: @ref object_t and all kinds of compatible associative + containers such as `std::map`, `std::unordered_map`, `std::multimap`, + and `std::unordered_multimap` with a `key_type` compatible to + @ref string_t and a `value_type` from which a @ref basic_json value can + be constructed. + - **strings**: @ref string_t, string literals, and all compatible string + containers can be used. + - **numbers**: @ref number_integer_t, @ref number_unsigned_t, + @ref number_float_t, and all convertible number types such as `int`, + `size_t`, `int64_t`, `float` or `double` can be used. + - **boolean**: @ref boolean_t / `bool` can be used. + + See the examples below. + + @tparam CompatibleType a type such that: + - @a CompatibleType is not derived from `std::istream`, + - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move + constructors), + - @a CompatibleType is not a different @ref basic_json type (i.e. with different template arguments) + - @a CompatibleType is not a @ref basic_json nested type (e.g., + @ref json_pointer, @ref iterator, etc ...) + - @ref @ref json_serializer has a + `to_json(basic_json_t&, CompatibleType&&)` method + + @tparam U = `uncvref_t` + + @param[in] val the value to be forwarded to the respective constructor + + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. + + @exceptionsafety Depends on the called constructor. For types directly + supported by the library (i.e., all types for which no `to_json()` function + was provided), strong guarantee holds: if an exception is thrown, there are + no changes to any JSON value. + + @liveexample{The following code shows the constructor with several + compatible types.,basic_json__CompatibleType} + + @since version 2.1.0 + */ + template , + detail::enable_if_t< + detail::is_compatible_type::value, int> = 0> + basic_json(CompatibleType && val) noexcept(noexcept( + JSONSerializer::to_json(std::declval(), + std::forward(val)))) + { + JSONSerializer::to_json(*this, std::forward(val)); + assert_invariant(); + } + + /*! + @brief create a JSON value from an existing one + + This is a constructor for existing @ref basic_json types. + It does not hijack copy/move constructors, since the parameter has different + template arguments than the current ones. + + The constructor tries to convert the internal @ref m_value of the parameter. + + @tparam BasicJsonType a type such that: + - @a BasicJsonType is a @ref basic_json type. + - @a BasicJsonType has different template arguments than @ref basic_json_t. + + @param[in] val the @ref basic_json value to be converted. + + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. + + @exceptionsafety Depends on the called constructor. For types directly + supported by the library (i.e., all types for which no `to_json()` function + was provided), strong guarantee holds: if an exception is thrown, there are + no changes to any JSON value. + + @since version 3.1.2 + */ + template ::value and not std::is_same::value, int> = 0> + basic_json(const BasicJsonType& val) + { + using other_boolean_t = typename BasicJsonType::boolean_t; + using other_number_float_t = typename BasicJsonType::number_float_t; + using other_number_integer_t = typename BasicJsonType::number_integer_t; + using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using other_string_t = typename BasicJsonType::string_t; + using other_object_t = typename BasicJsonType::object_t; + using other_array_t = typename BasicJsonType::array_t; + + switch (val.type()) + { + case value_t::boolean: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_float: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_integer: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_unsigned: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::string: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::object: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::array: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::null: + *this = nullptr; + break; + case value_t::discarded: + m_type = value_t::discarded; + break; + } + assert_invariant(); + } + + /*! + @brief create a container (array or object) from an initializer list + + Creates a JSON value of type array or object from the passed initializer + list @a init. In case @a type_deduction is `true` (default), the type of + the JSON value to be created is deducted from the initializer list @a init + according to the following rules: + + 1. If the list is empty, an empty JSON object value `{}` is created. + 2. If the list consists of pairs whose first element is a string, a JSON + object value is created where the first elements of the pairs are + treated as keys and the second elements are as values. + 3. In all other cases, an array is created. + + The rules aim to create the best fit between a C++ initializer list and + JSON values. The rationale is as follows: + + 1. The empty initializer list is written as `{}` which is exactly an empty + JSON object. + 2. C++ has no way of describing mapped types other than to list a list of + pairs. As JSON requires that keys must be of type string, rule 2 is the + weakest constraint one can pose on initializer lists to interpret them + as an object. + 3. In all other cases, the initializer list could not be interpreted as + JSON object type, so interpreting it as JSON array type is safe. + + With the rules described above, the following JSON values cannot be + expressed by an initializer list: + + - the empty array (`[]`): use @ref array(initializer_list_t) + with an empty initializer list in this case + - arrays whose elements satisfy rule 2: use @ref + array(initializer_list_t) with the same initializer list + in this case + + @note When used without parentheses around an empty initializer list, @ref + basic_json() is called instead of this function, yielding the JSON null + value. + + @param[in] init initializer list with JSON values + + @param[in] type_deduction internal parameter; when set to `true`, the type + of the JSON value is deducted from the initializer list @a init; when set + to `false`, the type provided via @a manual_type is forced. This mode is + used by the functions @ref array(initializer_list_t) and + @ref object(initializer_list_t). + + @param[in] manual_type internal parameter; when @a type_deduction is set + to `false`, the created JSON value will use the provided type (only @ref + value_t::array and @ref value_t::object are valid); when @a type_deduction + is set to `true`, this parameter has no effect + + @throw type_error.301 if @a type_deduction is `false`, @a manual_type is + `value_t::object`, but @a init contains an element which is not a pair + whose first element is a string. In this case, the constructor could not + create an object. If @a type_deduction would have be `true`, an array + would have been created. See @ref object(initializer_list_t) + for an example. + + @complexity Linear in the size of the initializer list @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The example below shows how JSON values are created from + initializer lists.,basic_json__list_init_t} + + @sa @ref array(initializer_list_t) -- create a JSON array + value from an initializer list + @sa @ref object(initializer_list_t) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + basic_json(initializer_list_t init, + bool type_deduction = true, + value_t manual_type = value_t::array) + { + // check if each element is an array with two elements whose first + // element is a string + bool is_an_object = std::all_of(init.begin(), init.end(), + [](const detail::json_ref& element_ref) + { + return (element_ref->is_array() and element_ref->size() == 2 and (*element_ref)[0].is_string()); + }); + + // adjust type if type deduction is not wanted + if (not type_deduction) + { + // if array is wanted, do not create an object though possible + if (manual_type == value_t::array) + { + is_an_object = false; + } + + // if object is wanted but impossible, throw an exception + if (JSON_UNLIKELY(manual_type == value_t::object and not is_an_object)) + { + JSON_THROW(type_error::create(301, "cannot create object from initializer list")); + } + } + + if (is_an_object) + { + // the initializer list is a list of pairs -> create object + m_type = value_t::object; + m_value = value_t::object; + + std::for_each(init.begin(), init.end(), [this](const detail::json_ref& element_ref) + { + auto element = element_ref.moved_or_copied(); + m_value.object->emplace( + std::move(*((*element.m_value.array)[0].m_value.string)), + std::move((*element.m_value.array)[1])); + }); + } + else + { + // the initializer list describes an array -> create array + m_type = value_t::array; + m_value.array = create(init.begin(), init.end()); + } + + assert_invariant(); + } + + /*! + @brief explicitly create an array from an initializer list + + Creates a JSON array value from a given initializer list. That is, given a + list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the + initializer list is empty, the empty array `[]` is created. + + @note This function is only needed to express two edge cases that cannot + be realized with the initializer list constructor (@ref + basic_json(initializer_list_t, bool, value_t)). These cases + are: + 1. creating an array whose elements are all pairs whose first element is a + string -- in this case, the initializer list constructor would create an + object, taking the first elements as keys + 2. creating an empty array -- passing the empty initializer list to the + initializer list constructor yields an empty object + + @param[in] init initializer list with JSON values to create an array from + (optional) + + @return JSON array value + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows an example for the `array` + function.,array} + + @sa @ref basic_json(initializer_list_t, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref object(initializer_list_t) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + static basic_json array(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::array); + } + + /*! + @brief explicitly create an object from an initializer list + + Creates a JSON object value from a given initializer list. The initializer + lists elements must be pairs, and their first elements must be strings. If + the initializer list is empty, the empty object `{}` is created. + + @note This function is only added for symmetry reasons. In contrast to the + related function @ref array(initializer_list_t), there are + no cases which can only be expressed by this function. That is, any + initializer list @a init can also be passed to the initializer list + constructor @ref basic_json(initializer_list_t, bool, value_t). + + @param[in] init initializer list to create an object from (optional) + + @return JSON object value + + @throw type_error.301 if @a init is not a list of pairs whose first + elements are strings. In this case, no object can be created. When such a + value is passed to @ref basic_json(initializer_list_t, bool, value_t), + an array would have been created from the passed initializer list @a init. + See example below. + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows an example for the `object` + function.,object} + + @sa @ref basic_json(initializer_list_t, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref array(initializer_list_t) -- create a JSON array + value from an initializer list + + @since version 1.0.0 + */ + static basic_json object(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::object); + } + + /*! + @brief construct an array with count copies of given value + + Constructs a JSON array value by creating @a cnt copies of a passed value. + In case @a cnt is `0`, an empty array is created. + + @param[in] cnt the number of JSON copies of @a val to create + @param[in] val the JSON value to copy + + @post `std::distance(begin(),end()) == cnt` holds. + + @complexity Linear in @a cnt. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows examples for the @ref + basic_json(size_type\, const basic_json&) + constructor.,basic_json__size_type_basic_json} + + @since version 1.0.0 + */ + basic_json(size_type cnt, const basic_json& val) + : m_type(value_t::array) + { + m_value.array = create(cnt, val); + assert_invariant(); + } + + /*! + @brief construct a JSON container given an iterator range + + Constructs the JSON value with the contents of the range `[first, last)`. + The semantics depends on the different types a JSON value can have: + - In case of a null type, invalid_iterator.206 is thrown. + - In case of other primitive types (number, boolean, or string), @a first + must be `begin()` and @a last must be `end()`. In this case, the value is + copied. Otherwise, invalid_iterator.204 is thrown. + - In case of structured types (array, object), the constructor behaves as + similar versions for `std::vector` or `std::map`; that is, a JSON array + or object is constructed from the values in the range. + + @tparam InputIT an input iterator type (@ref iterator or @ref + const_iterator) + + @param[in] first begin of the range to copy from (included) + @param[in] last end of the range to copy from (excluded) + + @pre Iterators @a first and @a last must be initialized. **This + precondition is enforced with an assertion (see warning).** If + assertions are switched off, a violation of this precondition yields + undefined behavior. + + @pre Range `[first, last)` is valid. Usually, this precondition cannot be + checked efficiently. Only certain edge cases are detected; see the + description of the exceptions below. A violation of this precondition + yields undefined behavior. + + @warning A precondition is enforced with a runtime assertion that will + result in calling `std::abort` if this precondition is not met. + Assertions can be disabled by defining `NDEBUG` at compile time. + See http://en.cppreference.com/w/cpp/error/assert for more + information. + + @throw invalid_iterator.201 if iterators @a first and @a last are not + compatible (i.e., do not belong to the same JSON value). In this case, + the range `[first, last)` is undefined. + @throw invalid_iterator.204 if iterators @a first and @a last belong to a + primitive type (number, boolean, or string), but @a first does not point + to the first element any more. In this case, the range `[first, last)` is + undefined. See example code below. + @throw invalid_iterator.206 if iterators @a first and @a last belong to a + null value. In this case, the range `[first, last)` is undefined. + + @complexity Linear in distance between @a first and @a last. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The example below shows several ways to create JSON values by + specifying a subrange with iterators.,basic_json__InputIt_InputIt} + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type = 0> + basic_json(InputIT first, InputIT last) + { + assert(first.m_object != nullptr); + assert(last.m_object != nullptr); + + // make sure iterator fits the current value + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(201, "iterators are not compatible")); + } + + // copy type from first iterator + m_type = first.m_object->m_type; + + // check if iterator range is complete for primitive values + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_UNLIKELY(not first.m_it.primitive_iterator.is_begin() + or not last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + } + break; + } + + default: + break; + } + + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = first.m_object->m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = first.m_object->m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value.number_float = first.m_object->m_value.number_float; + break; + } + + case value_t::boolean: + { + m_value.boolean = first.m_object->m_value.boolean; + break; + } + + case value_t::string: + { + m_value = *first.m_object->m_value.string; + break; + } + + case value_t::object: + { + m_value.object = create(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + m_value.array = create(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " + + std::string(first.m_object->type_name()))); + } + + assert_invariant(); + } + + + /////////////////////////////////////// + // other constructors and destructor // + /////////////////////////////////////// + + /// @private + basic_json(const detail::json_ref& ref) + : basic_json(ref.moved_or_copied()) + {} + + /*! + @brief copy constructor + + Creates a copy of a given JSON value. + + @param[in] other the JSON value to copy + + @post `*this == other` + + @complexity Linear in the size of @a other. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + - As postcondition, it holds: `other == basic_json(other)`. + + @liveexample{The following code shows an example for the copy + constructor.,basic_json__basic_json} + + @since version 1.0.0 + */ + basic_json(const basic_json& other) + : m_type(other.m_type) + { + // check of passed value is valid + other.assert_invariant(); + + switch (m_type) + { + case value_t::object: + { + m_value = *other.m_value.object; + break; + } + + case value_t::array: + { + m_value = *other.m_value.array; + break; + } + + case value_t::string: + { + m_value = *other.m_value.string; + break; + } + + case value_t::boolean: + { + m_value = other.m_value.boolean; + break; + } + + case value_t::number_integer: + { + m_value = other.m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value = other.m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value = other.m_value.number_float; + break; + } + + default: + break; + } + + assert_invariant(); + } + + /*! + @brief move constructor + + Move constructor. Constructs a JSON value with the contents of the given + value @a other using move semantics. It "steals" the resources from @a + other and leaves it as JSON null value. + + @param[in,out] other value to move to this object + + @post `*this` has the same value as @a other before the call. + @post @a other is a JSON null value. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @requirement This function helps `basic_json` satisfying the + [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible) + requirements. + + @liveexample{The code below shows the move constructor explicitly called + via std::move.,basic_json__moveconstructor} + + @since version 1.0.0 + */ + basic_json(basic_json&& other) noexcept + : m_type(std::move(other.m_type)), + m_value(std::move(other.m_value)) + { + // check that passed value is valid + other.assert_invariant(); + + // invalidate payload + other.m_type = value_t::null; + other.m_value = {}; + + assert_invariant(); + } + + /*! + @brief copy assignment + + Copy assignment operator. Copies a JSON value via the "copy and swap" + strategy: It is expressed in terms of the copy constructor, destructor, + and the `swap()` member function. + + @param[in] other value to copy from + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + + @liveexample{The code below shows and example for the copy assignment. It + creates a copy of value `a` which is then swapped with `b`. Finally\, the + copy of `a` (which is the null value after the swap) is + destroyed.,basic_json__copyassignment} + + @since version 1.0.0 + */ + reference& operator=(basic_json other) noexcept ( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + // check that passed value is valid + other.assert_invariant(); + + using std::swap; + swap(m_type, other.m_type); + swap(m_value, other.m_value); + + assert_invariant(); + return *this; + } + + /*! + @brief destructor + + Destroys the JSON value and frees all allocated memory. + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + - All stored elements are destroyed and all memory is freed. + + @since version 1.0.0 + */ + ~basic_json() noexcept + { + assert_invariant(); + m_value.destroy(m_type); + } + + /// @} + + public: + /////////////////////// + // object inspection // + /////////////////////// + + /// @name object inspection + /// Functions to inspect the type of a JSON value. + /// @{ + + /*! + @brief serialization + + Serialization function for JSON values. The function tries to mimic + Python's `json.dumps()` function, and currently supports its @a indent + and @a ensure_ascii parameters. + + @param[in] indent If indent is nonnegative, then array elements and object + members will be pretty-printed with that indent level. An indent level of + `0` will only insert newlines. `-1` (the default) selects the most compact + representation. + @param[in] indent_char The character to use for indentation if @a indent is + greater than `0`. The default is ` ` (space). + @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters + in the output are escaped with `\uXXXX` sequences, and the result consists + of ASCII characters only. + + @return string containing the serialization of the JSON value + + @throw type_error.316 if a string stored inside the JSON value is not + UTF-8 encoded + + @complexity Linear. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @liveexample{The following example shows the effect of different @a indent\, + @a indent_char\, and @a ensure_ascii parameters to the result of the + serialization.,dump} + + @see https://docs.python.org/2/library/json.html#json.dump + + @since version 1.0.0; indentation character @a indent_char, option + @a ensure_ascii and exceptions added in version 3.0.0 + */ + string_t dump(const int indent = -1, const char indent_char = ' ', + const bool ensure_ascii = false) const + { + string_t result; + serializer s(detail::output_adapter(result), indent_char); + + if (indent >= 0) + { + s.dump(*this, true, ensure_ascii, static_cast(indent)); + } + else + { + s.dump(*this, false, ensure_ascii, 0); + } + + return result; + } + + /*! + @brief return the type of the JSON value (explicit) + + Return the type of the JSON value as a value from the @ref value_t + enumeration. + + @return the type of the JSON value + Value type | return value + ------------------------- | ------------------------- + null | value_t::null + boolean | value_t::boolean + string | value_t::string + number (integer) | value_t::number_integer + number (unsigned integer) | value_t::number_unsigned + number (floating-point) | value_t::number_float + object | value_t::object + array | value_t::array + discarded | value_t::discarded + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `type()` for all JSON + types.,type} + + @sa @ref operator value_t() -- return the type of the JSON value (implicit) + @sa @ref type_name() -- return the type as string + + @since version 1.0.0 + */ + constexpr value_t type() const noexcept + { + return m_type; + } + + /*! + @brief return whether type is primitive + + This function returns true if and only if the JSON type is primitive + (string, number, boolean, or null). + + @return `true` if type is primitive (string, number, boolean, or null), + `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_primitive()` for all JSON + types.,is_primitive} + + @sa @ref is_structured() -- returns whether JSON value is structured + @sa @ref is_null() -- returns whether JSON value is `null` + @sa @ref is_string() -- returns whether JSON value is a string + @sa @ref is_boolean() -- returns whether JSON value is a boolean + @sa @ref is_number() -- returns whether JSON value is a number + + @since version 1.0.0 + */ + constexpr bool is_primitive() const noexcept + { + return is_null() or is_string() or is_boolean() or is_number(); + } + + /*! + @brief return whether type is structured + + This function returns true if and only if the JSON type is structured + (array or object). + + @return `true` if type is structured (array or object), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_structured()` for all JSON + types.,is_structured} + + @sa @ref is_primitive() -- returns whether value is primitive + @sa @ref is_array() -- returns whether value is an array + @sa @ref is_object() -- returns whether value is an object + + @since version 1.0.0 + */ + constexpr bool is_structured() const noexcept + { + return is_array() or is_object(); + } + + /*! + @brief return whether value is null + + This function returns true if and only if the JSON value is null. + + @return `true` if type is null, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_null()` for all JSON + types.,is_null} + + @since version 1.0.0 + */ + constexpr bool is_null() const noexcept + { + return (m_type == value_t::null); + } + + /*! + @brief return whether value is a boolean + + This function returns true if and only if the JSON value is a boolean. + + @return `true` if type is boolean, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_boolean()` for all JSON + types.,is_boolean} + + @since version 1.0.0 + */ + constexpr bool is_boolean() const noexcept + { + return (m_type == value_t::boolean); + } + + /*! + @brief return whether value is a number + + This function returns true if and only if the JSON value is a number. This + includes both integer (signed and unsigned) and floating-point values. + + @return `true` if type is number (regardless whether integer, unsigned + integer or floating-type), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number()` for all JSON + types.,is_number} + + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number() const noexcept + { + return is_number_integer() or is_number_float(); + } + + /*! + @brief return whether value is an integer number + + This function returns true if and only if the JSON value is a signed or + unsigned integer number. This excludes floating-point values. + + @return `true` if type is an integer or unsigned integer number, `false` + otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_integer()` for all + JSON types.,is_number_integer} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number_integer() const noexcept + { + return (m_type == value_t::number_integer or m_type == value_t::number_unsigned); + } + + /*! + @brief return whether value is an unsigned integer number + + This function returns true if and only if the JSON value is an unsigned + integer number. This excludes floating-point and signed integer values. + + @return `true` if type is an unsigned integer number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_unsigned()` for all + JSON types.,is_number_unsigned} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 2.0.0 + */ + constexpr bool is_number_unsigned() const noexcept + { + return (m_type == value_t::number_unsigned); + } + + /*! + @brief return whether value is a floating-point number + + This function returns true if and only if the JSON value is a + floating-point number. This excludes signed and unsigned integer values. + + @return `true` if type is a floating-point number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_float()` for all + JSON types.,is_number_float} + + @sa @ref is_number() -- check if value is number + @sa @ref is_number_integer() -- check if value is an integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + + @since version 1.0.0 + */ + constexpr bool is_number_float() const noexcept + { + return (m_type == value_t::number_float); + } + + /*! + @brief return whether value is an object + + This function returns true if and only if the JSON value is an object. + + @return `true` if type is object, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_object()` for all JSON + types.,is_object} + + @since version 1.0.0 + */ + constexpr bool is_object() const noexcept + { + return (m_type == value_t::object); + } + + /*! + @brief return whether value is an array + + This function returns true if and only if the JSON value is an array. + + @return `true` if type is array, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_array()` for all JSON + types.,is_array} + + @since version 1.0.0 + */ + constexpr bool is_array() const noexcept + { + return (m_type == value_t::array); + } + + /*! + @brief return whether value is a string + + This function returns true if and only if the JSON value is a string. + + @return `true` if type is string, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_string()` for all JSON + types.,is_string} + + @since version 1.0.0 + */ + constexpr bool is_string() const noexcept + { + return (m_type == value_t::string); + } + + /*! + @brief return whether value is discarded + + This function returns true if and only if the JSON value was discarded + during parsing with a callback function (see @ref parser_callback_t). + + @note This function will always be `false` for JSON values after parsing. + That is, discarded values can only occur during parsing, but will be + removed when inside a structured value or replaced by null in other cases. + + @return `true` if type is discarded, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_discarded()` for all JSON + types.,is_discarded} + + @since version 1.0.0 + */ + constexpr bool is_discarded() const noexcept + { + return (m_type == value_t::discarded); + } + + /*! + @brief return the type of the JSON value (implicit) + + Implicitly return the type of the JSON value as a value from the @ref + value_t enumeration. + + @return the type of the JSON value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies the @ref value_t operator for + all JSON types.,operator__value_t} + + @sa @ref type() -- return the type of the JSON value (explicit) + @sa @ref type_name() -- return the type as string + + @since version 1.0.0 + */ + constexpr operator value_t() const noexcept + { + return m_type; + } + + /// @} + + private: + ////////////////// + // value access // + ////////////////// + + /// get a boolean (explicit) + boolean_t get_impl(boolean_t* /*unused*/) const + { + if (JSON_LIKELY(is_boolean())) + { + return m_value.boolean; + } + + JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(type_name()))); + } + + /// get a pointer to the value (object) + object_t* get_impl_ptr(object_t* /*unused*/) noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (object) + constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (array) + array_t* get_impl_ptr(array_t* /*unused*/) noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (array) + constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (string) + string_t* get_impl_ptr(string_t* /*unused*/) noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (string) + constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (boolean) + boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (boolean) + constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (integer number) + number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (integer number) + constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (unsigned number) + number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (unsigned number) + constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (floating-point number) + number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /// get a pointer to the value (floating-point number) + constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /*! + @brief helper function to implement get_ref() + + This function helps to implement get_ref() without code duplication for + const and non-const overloads + + @tparam ThisType will be deduced as `basic_json` or `const basic_json` + + @throw type_error.303 if ReferenceType does not match underlying value + type of the current JSON + */ + template + static ReferenceType get_ref_impl(ThisType& obj) + { + // delegate the call to get_ptr<>() + auto ptr = obj.template get_ptr::type>(); + + if (JSON_LIKELY(ptr != nullptr)) + { + return *ptr; + } + + JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + std::string(obj.type_name()))); + } + + public: + /// @name value access + /// Direct access to the stored value of a JSON value. + /// @{ + + /*! + @brief get special-case overload + + This overloads avoids a lot of template boilerplate, it can be seen as the + identity method + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this + + @complexity Constant. + + @since version 2.1.0 + */ + template::type, basic_json_t>::value, + int> = 0> + basic_json get() const + { + return *this; + } + + /*! + @brief get special-case overload + + This overloads converts the current @ref basic_json in a different + @ref basic_json type + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this, converted into @tparam BasicJsonType + + @complexity Depending on the implementation of the called `from_json()` + method. + + @since version 3.1.2 + */ + template::value and + detail::is_basic_json::value, int> = 0> + BasicJsonType get() const + { + return *this; + } + + /*! + @brief get a value (explicit) + + Explicit type conversion between the JSON value and a compatible value + which is [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json, + - @ref json_serializer has a `from_json()` method of the form + `void from_json(const basic_json&, ValueType&)`, and + - @ref json_serializer does not have a `from_json()` method of + the form `ValueType from_json(const basic_json&)` + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,get__ValueType_const} + + @since version 2.1.0 + */ + template, + detail::enable_if_t < + not detail::is_basic_json::value and + detail::has_from_json::value and + not detail::has_non_default_from_json::value, + int> = 0> + ValueType get() const noexcept(noexcept( + JSONSerializer::from_json(std::declval(), std::declval()))) + { + // we cannot static_assert on ValueTypeCV being non-const, because + // there is support for get(), which is why we + // still need the uncvref + static_assert(not std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + static_assert(std::is_default_constructible::value, + "types must be DefaultConstructible when used with get()"); + + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + } + + /*! + @brief get a value (explicit); special case + + Explicit type conversion between the JSON value and a compatible value + which is **not** [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and **not** [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + return JSONSerializer::from_json(*this); + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json and + - @ref json_serializer has a `from_json()` method of the form + `ValueType from_json(const basic_json&)` + + @note If @ref json_serializer has both overloads of + `from_json()`, this one is chosen. + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @since version 2.1.0 + */ + template, + detail::enable_if_t::value and + detail::has_non_default_from_json::value, + int> = 0> + ValueType get() const noexcept(noexcept( + JSONSerializer::from_json(std::declval()))) + { + static_assert(not std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + return JSONSerializer::from_json(*this); + } + + /*! + @brief get a pointer value (explicit) + + Explicit pointer access to the internally stored JSON value. No copies are + made. + + @warning The pointer becomes invalid if the underlying JSON object + changes. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get__PointerType} + + @sa @ref get_ptr() for explicit pointer-member access + + @since version 1.0.0 + */ + template::value, int>::type = 0> + PointerType get() noexcept + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a pointer value (explicit) + @copydoc get() + */ + template::value, int>::type = 0> + constexpr const PointerType get() const noexcept + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a pointer value (implicit) + + Implicit pointer access to the internally stored JSON value. No copies are + made. + + @warning Writing data to the pointee of the result yields an undefined + state. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. Enforced by a static + assertion. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get_ptr} + + @since version 1.0.0 + */ + template::value, int>::type = 0> + PointerType get_ptr() noexcept + { + // get the type of the PointerType (remove pointer and const) + using pointee_t = typename std::remove_const::type>::type>::type; + // make sure the type matches the allowed types + static_assert( + std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + , "incompatible pointer type"); + + // delegate the call to get_impl_ptr<>() + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a pointer value (implicit) + @copydoc get_ptr() + */ + template::value and + std::is_const::type>::value, int>::type = 0> + constexpr const PointerType get_ptr() const noexcept + { + // get the type of the PointerType (remove pointer and const) + using pointee_t = typename std::remove_const::type>::type>::type; + // make sure the type matches the allowed types + static_assert( + std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + , "incompatible pointer type"); + + // delegate the call to get_impl_ptr<>() const + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a reference value (implicit) + + Implicit reference access to the internally stored JSON value. No copies + are made. + + @warning Writing data to the referee of the result yields an undefined + state. + + @tparam ReferenceType reference type; must be a reference to @ref array_t, + @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or + @ref number_float_t. Enforced by static assertion. + + @return reference to the internally stored JSON value if the requested + reference type @a ReferenceType fits to the JSON value; throws + type_error.303 otherwise + + @throw type_error.303 in case passed type @a ReferenceType is incompatible + with the stored JSON value; see example below + + @complexity Constant. + + @liveexample{The example shows several calls to `get_ref()`.,get_ref} + + @since version 1.1.0 + */ + template::value, int>::type = 0> + ReferenceType get_ref() + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a reference value (implicit) + @copydoc get_ref() + */ + template::value and + std::is_const::type>::value, int>::type = 0> + ReferenceType get_ref() const + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a value (implicit) + + Implicit type conversion between the JSON value and a compatible value. + The call is realized by calling @ref get() const. + + @tparam ValueType non-pointer type compatible to the JSON value, for + instance `int` for JSON integer numbers, `bool` for JSON booleans, or + `std::vector` types for JSON arrays. The character type of @ref string_t + as well as an initializer list of this type is excluded to avoid + ambiguities as these types implicitly convert to `std::string`. + + @return copy of the JSON value, converted to type @a ValueType + + @throw type_error.302 in case passed type @a ValueType is incompatible + to the JSON value type (e.g., the JSON value is of type boolean, but a + string is requested); see example below + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,operator__ValueType} + + @since version 1.0.0 + */ + template < typename ValueType, typename std::enable_if < + not std::is_pointer::value and + not std::is_same>::value and + not std::is_same::value and + not detail::is_basic_json::value +#ifndef _MSC_VER // fix for issue #167 operator<< ambiguity under VS2015 + and not std::is_same>::value +#endif +#if defined(JSON_HAS_CPP_17) + and not std::is_same::value +#endif + , int >::type = 0 > + operator ValueType() const + { + // delegate the call to get<>() const + return get(); + } + + /// @} + + + //////////////////// + // element access // + //////////////////// + + /// @name element access + /// Access to the JSON value. + /// @{ + + /*! + @brief access specified array element with bounds checking + + Returns a reference to the element at specified location @a idx, with + bounds checking. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw type_error.304 if the JSON value is not an array; in this case, + calling `at` with an index makes no sense. See example below. + @throw out_of_range.401 if the index @a idx is out of range of the array; + that is, `idx >= size()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 1.0.0 + + @liveexample{The example below shows how array elements can be read and + written using `at()`. It also demonstrates the different exceptions that + can be thrown.,at__size_type} + */ + reference at(size_type idx) + { + // at only works for arrays + if (JSON_LIKELY(is_array())) + { + JSON_TRY + { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified array element with bounds checking + + Returns a const reference to the element at specified location @a idx, + with bounds checking. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw type_error.304 if the JSON value is not an array; in this case, + calling `at` with an index makes no sense. See example below. + @throw out_of_range.401 if the index @a idx is out of range of the array; + that is, `idx >= size()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 1.0.0 + + @liveexample{The example below shows how array elements can be read using + `at()`. It also demonstrates the different exceptions that can be thrown., + at__size_type_const} + */ + const_reference at(size_type idx) const + { + // at only works for arrays + if (JSON_LIKELY(is_array())) + { + JSON_TRY + { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a reference to the element at with specified key @a key, with + bounds checking. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.304 if the JSON value is not an object; in this case, + calling `at` with a key makes no sense. See example below. + @throw out_of_range.403 if the key @a key is is not stored in the object; + that is, `find(key) == end()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Logarithmic in the size of the container. + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + + @liveexample{The example below shows how object elements can be read and + written using `at()`. It also demonstrates the different exceptions that + can be thrown.,at__object_t_key_type} + */ + reference at(const typename object_t::key_type& key) + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + JSON_TRY + { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a const reference to the element at with specified key @a key, + with bounds checking. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw type_error.304 if the JSON value is not an object; in this case, + calling `at` with a key makes no sense. See example below. + @throw out_of_range.403 if the key @a key is is not stored in the object; + that is, `find(key) == end()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Logarithmic in the size of the container. + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + + @liveexample{The example below shows how object elements can be read using + `at()`. It also demonstrates the different exceptions that can be thrown., + at__object_t_key_type_const} + */ + const_reference at(const typename object_t::key_type& key) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + JSON_TRY + { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified array element + + Returns a reference to the element at specified location @a idx. + + @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), + then the array is silently filled up with `null` values to make `idx` a + valid reference to the last stored element. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw type_error.305 if the JSON value is not an array or null; in that + cases, using the [] operator with an index makes no sense. + + @complexity Constant if @a idx is in the range of the array. Otherwise + linear in `idx - size()`. + + @liveexample{The example below shows how array elements can be read and + written using `[]` operator. Note the addition of `null` + values.,operatorarray__size_type} + + @since version 1.0.0 + */ + reference operator[](size_type idx) + { + // implicitly convert null value to an empty array + if (is_null()) + { + m_type = value_t::array; + m_value.array = create(); + assert_invariant(); + } + + // operator[] only works for arrays + if (JSON_LIKELY(is_array())) + { + // fill up array with null values if given idx is outside range + if (idx >= m_value.array->size()) + { + m_value.array->insert(m_value.array->end(), + idx - m_value.array->size() + 1, + basic_json()); + } + + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified array element + + Returns a const reference to the element at specified location @a idx. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw type_error.305 if the JSON value is not an array; in that case, + using the [] operator with an index makes no sense. + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + the `[]` operator.,operatorarray__size_type_const} + + @since version 1.0.0 + */ + const_reference operator[](size_type idx) const + { + // const operator[] only works for arrays + if (JSON_LIKELY(is_array())) + { + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + reference operator[](const typename object_t::key_type& key) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + // operator[] only works for objects + if (JSON_LIKELY(is_object())) + { + return m_value.object->operator[](key); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw type_error.305 if the JSON value is not an object; in that case, + using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + const_reference operator[](const typename object_t::key_type& key) const + { + // const operator[] only works for objects + if (JSON_LIKELY(is_object())) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + reference operator[](T* key) + { + // implicitly convert null to object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // at only works for objects + if (JSON_LIKELY(is_object())) + { + return m_value.object->operator[](key); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw type_error.305 if the JSON value is not an object; in that case, + using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + const_reference operator[](T* key) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified object element with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(key); + } catch(out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const typename object_t::key_type&), this function + does not throw if the given key @a key was not found. + + @note Unlike @ref operator[](const typename object_t::key_type& key), this + function does not implicitly add an element to the position defined by @a + key. This function is furthermore also applicable to const objects. + + @param[in] key key of the element to access + @param[in] default_value the value to return if @a key is not found + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw type_error.306 if the JSON value is not an object; in that case, + using `value()` with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + + @since version 1.0.0 + */ + template::value, int>::type = 0> + ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return *it; + } + + return default_value; + } + + JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const typename object_t::key_type&, ValueType) const + */ + string_t value(const typename object_t::key_type& key, const char* default_value) const + { + return value(key, string_t(default_value)); + } + + /*! + @brief access specified object element via JSON Pointer with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(ptr); + } catch(out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const json_pointer&), this function does not throw + if the given key @a key was not found. + + @param[in] ptr a JSON pointer to the element to access + @param[in] default_value the value to return if @a ptr found no value + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw type_error.306 if the JSON value is not an objec; in that case, + using `value()` with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value_ptr} + + @sa @ref operator[](const json_pointer&) for unchecked access by reference + + @since version 2.0.2 + */ + template::value, int>::type = 0> + ValueType value(const json_pointer& ptr, const ValueType& default_value) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + // if pointer resolves a value, return it or use default value + JSON_TRY + { + return ptr.get_checked(this); + } + JSON_CATCH (out_of_range&) + { + return default_value; + } + } + + JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const json_pointer&, ValueType) const + */ + string_t value(const json_pointer& ptr, const char* default_value) const + { + return value(ptr, string_t(default_value)); + } + + /*! + @brief access the first element + + Returns a reference to the first element in the container. For a JSON + container `c`, the expression `c.front()` is equivalent to `*c.begin()`. + + @return In case of a structured type (array or object), a reference to the + first element is returned. In case of number, string, or boolean values, a + reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw invalid_iterator.214 when called on `null` value + + @liveexample{The following code shows an example for `front()`.,front} + + @sa @ref back() -- access the last element + + @since version 1.0.0 + */ + reference front() + { + return *begin(); + } + + /*! + @copydoc basic_json::front() + */ + const_reference front() const + { + return *cbegin(); + } + + /*! + @brief access the last element + + Returns a reference to the last element in the container. For a JSON + container `c`, the expression `c.back()` is equivalent to + @code {.cpp} + auto tmp = c.end(); + --tmp; + return *tmp; + @endcode + + @return In case of a structured type (array or object), a reference to the + last element is returned. In case of number, string, or boolean values, a + reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw invalid_iterator.214 when called on a `null` value. See example + below. + + @liveexample{The following code shows an example for `back()`.,back} + + @sa @ref front() -- access the first element + + @since version 1.0.0 + */ + reference back() + { + auto tmp = end(); + --tmp; + return *tmp; + } + + /*! + @copydoc basic_json::back() + */ + const_reference back() const + { + auto tmp = cend(); + --tmp; + return *tmp; + } + + /*! + @brief remove element given an iterator + + Removes the element specified by iterator @a pos. The iterator @a pos must + be valid and dereferenceable. Thus the `end()` iterator (which is valid, + but is not dereferenceable) cannot be used as a value for @a pos. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] pos iterator to the element to remove + @return Iterator following the last removed element. If the iterator @a + pos refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw type_error.307 if called on a `null` value; example: `"cannot use + erase() with null"` + @throw invalid_iterator.202 if called on an iterator which does not belong + to the current JSON value; example: `"iterator does not fit current + value"` + @throw invalid_iterator.205 if called on a primitive type with invalid + iterator (i.e., any iterator which is not `begin()`); example: `"iterator + out of range"` + + @complexity The complexity depends on the type: + - objects: amortized constant + - arrays: linear in distance between @a pos and the end of the container + - strings: linear in the length of the string + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType} + + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type + = 0> + IteratorType erase(IteratorType pos) + { + // make sure iterator fits the current value + if (JSON_UNLIKELY(this != pos.m_object)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_UNLIKELY(not pos.m_it.primitive_iterator.is_begin())) + { + JSON_THROW(invalid_iterator::create(205, "iterator out of range")); + } + + if (is_string()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.string); + std::allocator_traits::deallocate(alloc, m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); + break; + } + + default: + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + return result; + } + + /*! + @brief remove elements given an iterator range + + Removes the element specified by the range `[first; last)`. The iterator + @a first does not need to be dereferenceable if `first == last`: erasing + an empty range is a no-op. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] first iterator to the beginning of the range to remove + @param[in] last iterator past the end of the range to remove + @return Iterator following the last removed element. If the iterator @a + second refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw type_error.307 if called on a `null` value; example: `"cannot use + erase() with null"` + @throw invalid_iterator.203 if called on iterators which does not belong + to the current JSON value; example: `"iterators do not fit current value"` + @throw invalid_iterator.204 if called on a primitive type with invalid + iterators (i.e., if `first != begin()` and `last != end()`); example: + `"iterators out of range"` + + @complexity The complexity depends on the type: + - objects: `log(size()) + std::distance(first, last)` + - arrays: linear in the distance between @a first and @a last, plus linear + in the distance between @a last and end of the container + - strings: linear in the length of the string + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType_IteratorType} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type + = 0> + IteratorType erase(IteratorType first, IteratorType last) + { + // make sure iterator fits the current value + if (JSON_UNLIKELY(this != first.m_object or this != last.m_object)) + { + JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value")); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_LIKELY(not first.m_it.primitive_iterator.is_begin() + or not last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + } + + if (is_string()) + { + AllocatorType alloc; + std::allocator_traits::destroy(alloc, m_value.string); + std::allocator_traits::deallocate(alloc, m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + return result; + } + + /*! + @brief remove element from a JSON object given a key + + Removes elements from a JSON object with the key value @a key. + + @param[in] key value of the elements to remove + + @return Number of elements removed. If @a ObjectType is the default + `std::map` type, the return value will always be `0` (@a key was not + found) or `1` (@a key was found). + + @post References and iterators to the erased elements are invalidated. + Other references and iterators are not affected. + + @throw type_error.307 when called on a type other than JSON object; + example: `"cannot use erase() with null"` + + @complexity `log(size()) + count(key)` + + @liveexample{The example shows the effect of `erase()`.,erase__key_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + size_type erase(const typename object_t::key_type& key) + { + // this erase only works for objects + if (JSON_LIKELY(is_object())) + { + return m_value.object->erase(key); + } + + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + /*! + @brief remove element from a JSON array given an index + + Removes element from a JSON array at the index @a idx. + + @param[in] idx index of the element to remove + + @throw type_error.307 when called on a type other than JSON object; + example: `"cannot use erase() with null"` + @throw out_of_range.401 when `idx >= size()`; example: `"array index 17 + is out of range"` + + @complexity Linear in distance between @a idx and the end of the container. + + @liveexample{The example shows the effect of `erase()`.,erase__size_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + + @since version 1.0.0 + */ + void erase(const size_type idx) + { + // this erase only works for arrays + if (JSON_LIKELY(is_array())) + { + if (JSON_UNLIKELY(idx >= size())) + { + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + + m_value.array->erase(m_value.array->begin() + static_cast(idx)); + } + else + { + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + } + + /// @} + + + //////////// + // lookup // + //////////// + + /// @name lookup + /// @{ + + /*! + @brief find an element in a JSON object + + Finds an element in a JSON object with key equivalent to @a key. If the + element is not found or the JSON value is not an object, end() is + returned. + + @note This method always returns @ref end() when executed on a JSON type + that is not an object. + + @param[in] key key value of the element to search for. + + @return Iterator to an element with key equivalent to @a key. If no such + element is found or the JSON value is not an object, past-the-end (see + @ref end()) iterator is returned. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `find()` is used.,find__key_type} + + @since version 1.0.0 + */ + template + iterator find(KeyT&& key) + { + auto result = end(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(std::forward(key)); + } + + return result; + } + + /*! + @brief find an element in a JSON object + @copydoc find(KeyT&&) + */ + template + const_iterator find(KeyT&& key) const + { + auto result = cend(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(std::forward(key)); + } + + return result; + } + + /*! + @brief returns the number of occurrences of a key in a JSON object + + Returns the number of elements with key @a key. If ObjectType is the + default `std::map` type, the return value will always be `0` (@a key was + not found) or `1` (@a key was found). + + @note This method always returns `0` when executed on a JSON type that is + not an object. + + @param[in] key key value of the element to count + + @return Number of elements with key @a key. If the JSON value is not an + object, the return value will be `0`. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `count()` is used.,count} + + @since version 1.0.0 + */ + template + size_type count(KeyT&& key) const + { + // return 0 for all nonobject types + return is_object() ? m_value.object->count(std::forward(key)) : 0; + } + + /// @} + + + /////////////// + // iterators // + /////////////// + + /// @name iterators + /// @{ + + /*! + @brief returns an iterator to the first element + + Returns an iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `begin()`.,begin} + + @sa @ref cbegin() -- returns a const iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + iterator begin() noexcept + { + iterator result(this); + result.set_begin(); + return result; + } + + /*! + @copydoc basic_json::cbegin() + */ + const_iterator begin() const noexcept + { + return cbegin(); + } + + /*! + @brief returns a const iterator to the first element + + Returns a const iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).begin()`. + + @liveexample{The following code shows an example for `cbegin()`.,cbegin} + + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + const_iterator cbegin() const noexcept + { + const_iterator result(this); + result.set_begin(); + return result; + } + + /*! + @brief returns an iterator to one past the last element + + Returns an iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `end()`.,end} + + @sa @ref cend() -- returns a const iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + iterator end() noexcept + { + iterator result(this); + result.set_end(); + return result; + } + + /*! + @copydoc basic_json::cend() + */ + const_iterator end() const noexcept + { + return cend(); + } + + /*! + @brief returns a const iterator to one past the last element + + Returns a const iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).end()`. + + @liveexample{The following code shows an example for `cend()`.,cend} + + @sa @ref end() -- returns an iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + const_iterator cend() const noexcept + { + const_iterator result(this); + result.set_end(); + return result; + } + + /*! + @brief returns an iterator to the reverse-beginning + + Returns an iterator to the reverse-beginning; that is, the last element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(end())`. + + @liveexample{The following code shows an example for `rbegin()`.,rbegin} + + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + reverse_iterator rbegin() noexcept + { + return reverse_iterator(end()); + } + + /*! + @copydoc basic_json::crbegin() + */ + const_reverse_iterator rbegin() const noexcept + { + return crbegin(); + } + + /*! + @brief returns an iterator to the reverse-end + + Returns an iterator to the reverse-end; that is, one before the first + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(begin())`. + + @liveexample{The following code shows an example for `rend()`.,rend} + + @sa @ref crend() -- returns a const reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + reverse_iterator rend() noexcept + { + return reverse_iterator(begin()); + } + + /*! + @copydoc basic_json::crend() + */ + const_reverse_iterator rend() const noexcept + { + return crend(); + } + + /*! + @brief returns a const reverse iterator to the last element + + Returns a const iterator to the reverse-beginning; that is, the last + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rbegin()`. + + @liveexample{The following code shows an example for `crbegin()`.,crbegin} + + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(cend()); + } + + /*! + @brief returns a const reverse iterator to one before the first + + Returns a const reverse iterator to the reverse-end; that is, one before + the first element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rend()`. + + @liveexample{The following code shows an example for `crend()`.,crend} + + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(cbegin()); + } + + public: + /*! + @brief wrapper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + For loop without iterator_wrapper: + + @code{cpp} + for (auto it = j_object.begin(); it != j_object.end(); ++it) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + Range-based for loop without iterator proxy: + + @code{cpp} + for (auto it : j_object) + { + // "it" is of type json::reference and has no key() member + std::cout << "value: " << it << '\n'; + } + @endcode + + Range-based for loop with iterator proxy: + + @code{cpp} + for (auto it : json::iterator_wrapper(j_object)) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + @note When iterating over an array, `key()` will return the index of the + element as string (see example). + + @param[in] ref reference to a JSON value + @return iteration proxy object wrapping @a ref with an interface to use in + range-based for loops + + @liveexample{The following code shows how the wrapper is used,iterator_wrapper} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @note The name of this function is not yet final and may change in the + future. + + @deprecated This stream operator is deprecated and will be removed in + future 4.0.0 of the library. Please use @ref items() instead; + that is, replace `json::iterator_wrapper(j)` with `j.items()`. + */ + JSON_DEPRECATED + static iteration_proxy iterator_wrapper(reference ref) noexcept + { + return ref.items(); + } + + /*! + @copydoc iterator_wrapper(reference) + */ + JSON_DEPRECATED + static iteration_proxy iterator_wrapper(const_reference ref) noexcept + { + return ref.items(); + } + + /*! + @brief helper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + For loop without `items()` function: + + @code{cpp} + for (auto it = j_object.begin(); it != j_object.end(); ++it) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + Range-based for loop without `items()` function: + + @code{cpp} + for (auto it : j_object) + { + // "it" is of type json::reference and has no key() member + std::cout << "value: " << it << '\n'; + } + @endcode + + Range-based for loop with `items()` function: + + @code{cpp} + for (auto it : j_object.items()) + { + std::cout << "key: " << it.key() << ", value:" << it.value() << '\n'; + } + @endcode + + @note When iterating over an array, `key()` will return the index of the + element as string (see example). For primitive types (e.g., numbers), + `key()` returns an empty string. + + @return iteration proxy object wrapping @a ref with an interface to use in + range-based for loops + + @liveexample{The following code shows how the function is used.,items} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 3.x.x. + */ + iteration_proxy items() noexcept + { + return iteration_proxy(*this); + } + + /*! + @copydoc items() + */ + iteration_proxy items() const noexcept + { + return iteration_proxy(*this); + } + + /// @} + + + ////////////// + // capacity // + ////////////// + + /// @name capacity + /// @{ + + /*! + @brief checks whether the container is empty. + + Checks if a JSON value has no elements (i.e. whether its @ref size is `0`). + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `true` + boolean | `false` + string | `false` + number | `false` + object | result of function `object_t::empty()` + array | result of function `array_t::empty()` + + @liveexample{The following code uses `empty()` to check if a JSON + object contains any elements.,empty} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `empty()` functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return whether a string stored as JSON value + is empty - it returns whether the JSON container itself is empty which is + false in the case of a string. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `begin() == end()`. + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + bool empty() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return true; + } + + case value_t::array: + { + // delegate call to array_t::empty() + return m_value.array->empty(); + } + + case value_t::object: + { + // delegate call to object_t::empty() + return m_value.object->empty(); + } + + default: + { + // all other types are nonempty + return false; + } + } + } + + /*! + @brief returns the number of elements + + Returns the number of elements in a JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` + boolean | `1` + string | `1` + number | `1` + object | result of function object_t::size() + array | result of function array_t::size() + + @liveexample{The following code calls `size()` on the different value + types.,size} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their size() functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return the length of a string stored as JSON + value - it returns the number of elements in the JSON value which is 1 in + the case of a string. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `std::distance(begin(), end())`. + + @sa @ref empty() -- checks whether the container is empty + @sa @ref max_size() -- returns the maximal number of elements + + @since version 1.0.0 + */ + size_type size() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return 0; + } + + case value_t::array: + { + // delegate call to array_t::size() + return m_value.array->size(); + } + + case value_t::object: + { + // delegate call to object_t::size() + return m_value.object->size(); + } + + default: + { + // all other types have size 1 + return 1; + } + } + } + + /*! + @brief returns the maximum possible number of elements + + Returns the maximum number of elements a JSON value is able to hold due to + system or library implementation limitations, i.e. `std::distance(begin(), + end())` for the JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` (same as `size()`) + boolean | `1` (same as `size()`) + string | `1` (same as `size()`) + number | `1` (same as `size()`) + object | result of function `object_t::max_size()` + array | result of function `array_t::max_size()` + + @liveexample{The following code calls `max_size()` on the different value + types. Note the output is implementation specific.,max_size} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `max_size()` functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of returning `b.size()` where `b` is the largest + possible JSON value. + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + size_type max_size() const noexcept + { + switch (m_type) + { + case value_t::array: + { + // delegate call to array_t::max_size() + return m_value.array->max_size(); + } + + case value_t::object: + { + // delegate call to object_t::max_size() + return m_value.object->max_size(); + } + + default: + { + // all other types have max_size() == size() + return size(); + } + } + } + + /// @} + + + /////////////// + // modifiers // + /////////////// + + /// @name modifiers + /// @{ + + /*! + @brief clears the contents + + Clears the content of a JSON value and resets it to the default value as + if @ref basic_json(value_t) would have been called with the current value + type from @ref type(): + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @post Has the same effect as calling + @code {.cpp} + *this = basic_json(type()); + @endcode + + @liveexample{The example below shows the effect of `clear()` to different + JSON types.,clear} + + @complexity Linear in the size of the JSON value. + + @iterators All iterators, pointers and references related to this container + are invalidated. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @sa @ref basic_json(value_t) -- constructor that creates an object with the + same value than calling `clear()` + + @since version 1.0.0 + */ + void clear() noexcept + { + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = 0; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = 0; + break; + } + + case value_t::number_float: + { + m_value.number_float = 0.0; + break; + } + + case value_t::boolean: + { + m_value.boolean = false; + break; + } + + case value_t::string: + { + m_value.string->clear(); + break; + } + + case value_t::array: + { + m_value.array->clear(); + break; + } + + case value_t::object: + { + m_value.object->clear(); + break; + } + + default: + break; + } + } + + /*! + @brief add an object to an array + + Appends the given element @a val to the end of the JSON value. If the + function is called on a JSON null value, an empty array is created before + appending @a val. + + @param[in] val the value to add to the JSON array + + @throw type_error.308 when called on a type other than JSON array or + null; example: `"cannot use push_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON array. Note how the `null` value was silently + converted to a JSON array.,push_back} + + @since version 1.0.0 + */ + void push_back(basic_json&& val) + { + // push_back only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_array()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (move semantics) + m_value.array->push_back(std::move(val)); + // invalidate object + val.m_type = value_t::null; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(basic_json&& val) + { + push_back(std::move(val)); + return *this; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + void push_back(const basic_json& val) + { + // push_back only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_array()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array + m_value.array->push_back(val); + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(const basic_json& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + Inserts the given element @a val to the JSON object. If the function is + called on a JSON null value, an empty object is created before inserting + @a val. + + @param[in] val the value to add to the JSON object + + @throw type_error.308 when called on a type other than JSON object or + null; example: `"cannot use push_back() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON object. Note how the `null` value was silently + converted to a JSON object.,push_back__object_t__value} + + @since version 1.0.0 + */ + void push_back(const typename object_t::value_type& val) + { + // push_back only works for null objects or objects + if (JSON_UNLIKELY(not(is_null() or is_object()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array + m_value.object->insert(val); + } + + /*! + @brief add an object to an object + @copydoc push_back(const typename object_t::value_type&) + */ + reference operator+=(const typename object_t::value_type& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + This function allows to use `push_back` with an initializer list. In case + + 1. the current value is an object, + 2. the initializer list @a init contains only two elements, and + 3. the first element of @a init is a string, + + @a init is converted into an object element and added using + @ref push_back(const typename object_t::value_type&). Otherwise, @a init + is converted to a JSON value and added using @ref push_back(basic_json&&). + + @param[in] init an initializer list + + @complexity Linear in the size of the initializer list @a init. + + @note This function is required to resolve an ambiguous overload error, + because pairs like `{"key", "value"}` can be both interpreted as + `object_t::value_type` or `std::initializer_list`, see + https://github.com/nlohmann/json/issues/235 for more information. + + @liveexample{The example shows how initializer lists are treated as + objects when possible.,push_back__initializer_list} + */ + void push_back(initializer_list_t init) + { + if (is_object() and init.size() == 2 and (*init.begin())->is_string()) + { + basic_json&& key = init.begin()->moved_or_copied(); + push_back(typename object_t::value_type( + std::move(key.get_ref()), (init.begin() + 1)->moved_or_copied())); + } + else + { + push_back(basic_json(init)); + } + } + + /*! + @brief add an object to an object + @copydoc push_back(initializer_list_t) + */ + reference operator+=(initializer_list_t init) + { + push_back(init); + return *this; + } + + /*! + @brief add an object to an array + + Creates a JSON value from the passed parameters @a args to the end of the + JSON value. If the function is called on a JSON null value, an empty array + is created before appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @throw type_error.311 when called on a type other than JSON array or + null; example: `"cannot use emplace_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` can be used to add + elements to a JSON array. Note how the `null` value was silently converted + to a JSON array.,emplace_back} + + @since version 2.0.8 + */ + template + void emplace_back(Args&& ... args) + { + // emplace_back only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_array()))) + { + JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (perfect forwarding) + m_value.array->emplace_back(std::forward(args)...); + } + + /*! + @brief add an object to an object if key does not exist + + Inserts a new element into a JSON object constructed in-place with the + given @a args if there is no element with the key in the container. If the + function is called on a JSON null value, an empty object is created before + appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @return a pair consisting of an iterator to the inserted element, or the + already-existing element if no insertion happened, and a bool + denoting whether the insertion took place. + + @throw type_error.311 when called on a type other than JSON object or + null; example: `"cannot use emplace() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `emplace()` can be used to add elements + to a JSON object. Note how the `null` value was silently converted to a + JSON object. Further note how no value is added if there was already one + value stored with the same key.,emplace} + + @since version 2.0.8 + */ + template + std::pair emplace(Args&& ... args) + { + // emplace only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_object()))) + { + JSON_THROW(type_error::create(311, "cannot use emplace() with " + std::string(type_name()))); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array (perfect forwarding) + auto res = m_value.object->emplace(std::forward(args)...); + // create result iterator and set iterator to the result of emplace + auto it = begin(); + it.m_it.object_iterator = res.first; + + // return pair of iterator and boolean + return {it, res.second}; + } + + /*! + @brief inserts element + + Inserts element @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] val element to insert + @return iterator pointing to the inserted @a val. + + @throw type_error.309 if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @complexity Constant plus linear in the distance between @a pos and end of + the container. + + @liveexample{The example shows how `insert()` is used.,insert} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const basic_json& val) + { + // insert only works for arrays + if (JSON_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); + return result; + } + + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + /*! + @brief inserts element + @copydoc insert(const_iterator, const basic_json&) + */ + iterator insert(const_iterator pos, basic_json&& val) + { + return insert(pos, val); + } + + /*! + @brief inserts elements + + Inserts @a cnt copies of @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] cnt number of copies of @a val to insert + @param[in] val element to insert + @return iterator pointing to the first element inserted, or @a pos if + `cnt==0` + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @complexity Linear in @a cnt plus linear in the distance between @a pos + and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__count} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) + { + // insert only works for arrays + if (JSON_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); + return result; + } + + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)` before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + @throw invalid_iterator.211 if @a first or @a last are iterators into + container for which insert is called; example: `"passed iterators may not + belong to container"` + + @return iterator pointing to the first element inserted, or @a pos if + `first==last` + + @complexity Linear in `std::distance(first, last)` plus linear in the + distance between @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__range} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const_iterator first, const_iterator last) + { + // insert only works for arrays + if (JSON_UNLIKELY(not is_array())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // check if range iterators belong to the same JSON object + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + if (JSON_UNLIKELY(first.m_object == this)) + { + JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert( + pos.m_it.array_iterator, + first.m_it.array_iterator, + last.m_it.array_iterator); + return result; + } + + /*! + @brief inserts elements + + Inserts elements from initializer list @a ilist before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] ilist initializer list to insert the values from + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @return iterator pointing to the first element inserted, or @a pos if + `ilist` is empty + + @complexity Linear in `ilist.size()` plus linear in the distance between + @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__ilist} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, initializer_list_t ilist) + { + // insert only works for arrays + if (JSON_UNLIKELY(not is_array())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist.begin(), ilist.end()); + return result; + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)`. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.309 if called on JSON values other than objects; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity Logarithmic: `O(N*log(size() + N))`, where `N` is the number + of elements to insert. + + @liveexample{The example shows how `insert()` is used.,insert__range_object} + + @since version 3.0.0 + */ + void insert(const_iterator first, const_iterator last) + { + // insert only works for objects + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if range iterators belong to the same JSON object + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + // passed iterators must belong to objects + if (JSON_UNLIKELY(not first.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); + } + + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from JSON object @a j and overwrites existing keys. + + @param[in] j JSON object to read values from + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_reference j) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); + } + if (JSON_UNLIKELY(not j.is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name()))); + } + + for (auto it = j.cbegin(); it != j.cend(); ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } + } + + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from from range `[first, last)` and overwrites existing + keys. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used__range.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_iterator first, const_iterator last) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); + } + + // check if range iterators belong to the same JSON object + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + // passed iterators must belong to objects + if (JSON_UNLIKELY(not first.m_object->is_object() + or not last.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + for (auto it = first; it != last; ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of the JSON value with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other JSON value to exchange the contents with + + @complexity Constant. + + @liveexample{The example below shows how JSON values can be swapped with + `swap()`.,swap__reference} + + @since version 1.0.0 + */ + void swap(reference other) noexcept ( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + std::swap(m_type, other.m_type); + std::swap(m_value, other.m_value); + assert_invariant(); + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON array with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other array to exchange the contents with + + @throw type_error.310 when JSON value is not an array; example: `"cannot + use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how arrays can be swapped with + `swap()`.,swap__array_t} + + @since version 1.0.0 + */ + void swap(array_t& other) + { + // swap only works for arrays + if (JSON_LIKELY(is_array())) + { + std::swap(*(m_value.array), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON object with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other object to exchange the contents with + + @throw type_error.310 when JSON value is not an object; example: + `"cannot use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how objects can be swapped with + `swap()`.,swap__object_t} + + @since version 1.0.0 + */ + void swap(object_t& other) + { + // swap only works for objects + if (JSON_LIKELY(is_object())) + { + std::swap(*(m_value.object), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON string with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other string to exchange the contents with + + @throw type_error.310 when JSON value is not a string; example: `"cannot + use swap() with boolean"` + + @complexity Constant. + + @liveexample{The example below shows how strings can be swapped with + `swap()`.,swap__string_t} + + @since version 1.0.0 + */ + void swap(string_t& other) + { + // swap only works for strings + if (JSON_LIKELY(is_string())) + { + std::swap(*(m_value.string), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /// @} + + public: + ////////////////////////////////////////// + // lexicographical comparison operators // + ////////////////////////////////////////// + + /// @name lexicographical comparison operators + /// @{ + + /*! + @brief comparison: equal + + Compares two JSON values for equality according to the following rules: + - Two JSON values are equal if (1) they are from the same type and (2) + their stored values are the same according to their respective + `operator==`. + - Integer and floating-point numbers are automatically converted before + comparison. Note than two NaN values are always treated as unequal. + - Two JSON null values are equal. + + @note Floating-point inside JSON values numbers are compared with + `json::number_float_t::operator==` which is `double::operator==` by + default. To compare floating-point while respecting an epsilon, an alternative + [comparison function](https://github.com/mariokonrad/marnav/blob/master/src/marnav/math/floatingpoint.hpp#L34-#L39) + could be used, for instance + @code {.cpp} + template::value, T>::type> + inline bool is_same(T a, T b, T epsilon = std::numeric_limits::epsilon()) noexcept + { + return std::abs(a - b) <= epsilon; + } + @endcode + + @note NaN values never compare equal to themselves or to other NaN values. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are equal + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__equal} + + @since version 1.0.0 + */ + friend bool operator==(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + return (*lhs.m_value.array == *rhs.m_value.array); + + case value_t::object: + return (*lhs.m_value.object == *rhs.m_value.object); + + case value_t::null: + return true; + + case value_t::string: + return (*lhs.m_value.string == *rhs.m_value.string); + + case value_t::boolean: + return (lhs.m_value.boolean == rhs.m_value.boolean); + + case value_t::number_integer: + return (lhs.m_value.number_integer == rhs.m_value.number_integer); + + case value_t::number_unsigned: + return (lhs.m_value.number_unsigned == rhs.m_value.number_unsigned); + + case value_t::number_float: + return (lhs.m_value.number_float == rhs.m_value.number_float); + + default: + return false; + } + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) + { + return (static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float); + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) + { + return (lhs.m_value.number_float == static_cast(rhs.m_value.number_integer)); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return (static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float); + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) + { + return (lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned)); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return (static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) + { + return (lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned)); + } + + return false; + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs == basic_json(rhs)); + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) == rhs); + } + + /*! + @brief comparison: not equal + + Compares two JSON values for inequality by calculating `not (lhs == rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are not equal + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__notequal} + + @since version 1.0.0 + */ + friend bool operator!=(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs == rhs); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs != basic_json(rhs)); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) != rhs); + } + + /*! + @brief comparison: less than + + Compares whether one JSON value @a lhs is less than another JSON value @a + rhs according to the following rules: + - If @a lhs and @a rhs have the same type, the values are compared using + the default `<` operator. + - Integer and floating-point numbers are automatically converted before + comparison + - In case @a lhs and @a rhs have different types, the values are ignored + and the order of the types is considered, see + @ref operator<(const value_t, const value_t). + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__less} + + @since version 1.0.0 + */ + friend bool operator<(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + return (*lhs.m_value.array) < (*rhs.m_value.array); + + case value_t::object: + return *lhs.m_value.object < *rhs.m_value.object; + + case value_t::null: + return false; + + case value_t::string: + return *lhs.m_value.string < *rhs.m_value.string; + + case value_t::boolean: + return lhs.m_value.boolean < rhs.m_value.boolean; + + case value_t::number_integer: + return lhs.m_value.number_integer < rhs.m_value.number_integer; + + case value_t::number_unsigned: + return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned; + + case value_t::number_float: + return lhs.m_value.number_float < rhs.m_value.number_float; + + default: + return false; + } + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; + } + + // We only reach this line if we cannot compare values. In that case, + // we compare types. Note we have to call the operator explicitly, + // because MSVC has problems otherwise. + return operator<(lhs_type, rhs_type); + } + + /*! + @brief comparison: less than + @copydoc operator<(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs < basic_json(rhs)); + } + + /*! + @brief comparison: less than + @copydoc operator<(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) < rhs); + } + + /*! + @brief comparison: less than or equal + + Compares whether one JSON value @a lhs is less than or equal to another + JSON value by calculating `not (rhs < lhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than or equal to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greater} + + @since version 1.0.0 + */ + friend bool operator<=(const_reference lhs, const_reference rhs) noexcept + { + return not (rhs < lhs); + } + + /*! + @brief comparison: less than or equal + @copydoc operator<=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<=(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs <= basic_json(rhs)); + } + + /*! + @brief comparison: less than or equal + @copydoc operator<=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator<=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) <= rhs); + } + + /*! + @brief comparison: greater than + + Compares whether one JSON value @a lhs is greater than another + JSON value by calculating `not (lhs <= rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__lessequal} + + @since version 1.0.0 + */ + friend bool operator>(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs <= rhs); + } + + /*! + @brief comparison: greater than + @copydoc operator>(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs > basic_json(rhs)); + } + + /*! + @brief comparison: greater than + @copydoc operator>(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) > rhs); + } + + /*! + @brief comparison: greater than or equal + + Compares whether one JSON value @a lhs is greater than or equal to another + JSON value by calculating `not (lhs < rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than or equal to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greaterequal} + + @since version 1.0.0 + */ + friend bool operator>=(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs < rhs); + } + + /*! + @brief comparison: greater than or equal + @copydoc operator>=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>=(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs >= basic_json(rhs)); + } + + /*! + @brief comparison: greater than or equal + @copydoc operator>=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator>=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) >= rhs); + } + + /// @} + + /////////////////// + // serialization // + /////////////////// + + /// @name serialization + /// @{ + + /*! + @brief serialize to stream + + Serialize the given JSON value @a j to the output stream @a o. The JSON + value will be serialized using the @ref dump member function. + + - The indentation of the output can be controlled with the member variable + `width` of the output stream @a o. For instance, using the manipulator + `std::setw(4)` on @a o sets the indentation level to `4` and the + serialization result is the same as calling `dump(4)`. + + - The indentation character can be controlled with the member variable + `fill` of the output stream @a o. For instance, the manipulator + `std::setfill('\\t')` sets indentation to use a tab character rather than + the default space character. + + @param[in,out] o stream to serialize to + @param[in] j JSON value to serialize + + @return the stream @a o + + @throw type_error.316 if a string stored inside the JSON value is not + UTF-8 encoded + + @complexity Linear. + + @liveexample{The example below shows the serialization with different + parameters to `width` to adjust the indentation level.,operator_serialize} + + @since version 1.0.0; indentation character added in version 3.0.0 + */ + friend std::ostream& operator<<(std::ostream& o, const basic_json& j) + { + // read width member and use it as indentation parameter if nonzero + const bool pretty_print = (o.width() > 0); + const auto indentation = (pretty_print ? o.width() : 0); + + // reset width to 0 for subsequent calls to this stream + o.width(0); + + // do the actual serialization + serializer s(detail::output_adapter(o), o.fill()); + s.dump(j, pretty_print, false, static_cast(indentation)); + return o; + } + + /*! + @brief serialize to stream + @deprecated This stream operator is deprecated and will be removed in + future 4.0.0 of the library. Please use + @ref operator<<(std::ostream&, const basic_json&) + instead; that is, replace calls like `j >> o;` with `o << j;`. + @since version 1.0.0; deprecated since version 3.0.0 + */ + JSON_DEPRECATED + friend std::ostream& operator>>(const basic_json& j, std::ostream& o) + { + return o << j; + } + + /// @} + + + ///////////////////// + // deserialization // + ///////////////////// + + /// @name deserialization + /// @{ + + /*! + @brief deserialize from a compatible input + + This function reads from a compatible input. Examples are: + - an array of 1-byte values + - strings with character/literal type with size of 1 byte + - input streams + - container with contiguous storage of 1-byte values. Compatible container + types include `std::vector`, `std::string`, `std::array`, + `std::valarray`, and `std::initializer_list`. Furthermore, C-style + arrays can be used with `std::begin()`/`std::end()`. User-defined + containers can be used as long as they implement random-access iterators + and a contiguous storage. + + @pre Each element of the container has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @pre The container storage is contiguous. Violating this precondition + yields undefined behavior. **This precondition is enforced with an + assertion.** + @pre Each element of the container has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @warning There is no way to enforce all preconditions at compile-time. If + the function is called with a noncompliant container and with + assertions switched off, the behavior is undefined and will most + likely yield segmentation violation. + + @param[in] i input to read from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @throw parse_error.101 if a parse error occurs; example: `""unexpected end + of input; expected string literal""` + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an array.,parse__array__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__string__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__istream__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function reading + from a contiguous container.,parse__contiguouscontainer__parser_callback_t} + + @since version 2.0.3 (contiguous containers) + */ + static basic_json parse(detail::input_adapter i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) + { + basic_json result; + parser(i, cb, allow_exceptions).parse(true, result); + return result; + } + + /*! + @copydoc basic_json parse(detail::input_adapter, const parser_callback_t) + */ + static basic_json parse(detail::input_adapter& i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) + { + basic_json result; + parser(i, cb, allow_exceptions).parse(true, result); + return result; + } + + static bool accept(detail::input_adapter i) + { + return parser(i).accept(true); + } + + static bool accept(detail::input_adapter& i) + { + return parser(i).accept(true); + } + + /*! + @brief deserialize from an iterator range with contiguous storage + + This function reads from an iterator range of a container with contiguous + storage of 1-byte values. Compatible container types include + `std::vector`, `std::string`, `std::array`, `std::valarray`, and + `std::initializer_list`. Furthermore, C-style arrays can be used with + `std::begin()`/`std::end()`. User-defined containers can be used as long + as they implement random-access iterators and a contiguous storage. + + @pre The iterator range is contiguous. Violating this precondition yields + undefined behavior. **This precondition is enforced with an assertion.** + @pre Each element in the range has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @warning There is no way to enforce all preconditions at compile-time. If + the function is called with noncompliant iterators and with + assertions switched off, the behavior is undefined and will most + likely yield segmentation violation. + + @tparam IteratorType iterator of container with contiguous storage + @param[in] first begin of the range to parse (included) + @param[in] last end of the range to parse (excluded) + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + + @return result of the deserialization + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an iterator range.,parse__iteratortype__parser_callback_t} + + @since version 2.0.3 + */ + template::iterator_category>::value, int>::type = 0> + static basic_json parse(IteratorType first, IteratorType last, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) + { + basic_json result; + parser(detail::input_adapter(first, last), cb, allow_exceptions).parse(true, result); + return result; + } + + template::iterator_category>::value, int>::type = 0> + static bool accept(IteratorType first, IteratorType last) + { + return parser(detail::input_adapter(first, last)).accept(true); + } + + /*! + @brief deserialize from stream + @deprecated This stream operator is deprecated and will be removed in + version 4.0.0 of the library. Please use + @ref operator>>(std::istream&, basic_json&) + instead; that is, replace calls like `j << i;` with `i >> j;`. + @since version 1.0.0; deprecated since version 3.0.0 + */ + JSON_DEPRECATED + friend std::istream& operator<<(basic_json& j, std::istream& i) + { + return operator>>(i, j); + } + + /*! + @brief deserialize from stream + + Deserializes an input stream to a JSON value. + + @param[in,out] i input stream to read a serialized JSON value from + @param[in,out] j JSON value to write the deserialized input to + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below shows how a JSON value is constructed by + reading a serialization from a stream.,operator_deserialize} + + @sa parse(std::istream&, const parser_callback_t) for a variant with a + parser callback function to filter values while parsing + + @since version 1.0.0 + */ + friend std::istream& operator>>(std::istream& i, basic_json& j) + { + parser(detail::input_adapter(i)).parse(false, j); + return i; + } + + /// @} + + /////////////////////////// + // convenience functions // + /////////////////////////// + + /*! + @brief return the type as string + + Returns the type name as string to be used in error messages - usually to + indicate that a function was called on a wrong JSON type. + + @return a string representation of a the @a m_type member: + Value type | return value + ----------- | ------------- + null | `"null"` + boolean | `"boolean"` + string | `"string"` + number | `"number"` (for all number types) + object | `"object"` + array | `"array"` + discarded | `"discarded"` + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @complexity Constant. + + @liveexample{The following code exemplifies `type_name()` for all JSON + types.,type_name} + + @sa @ref type() -- return the type of the JSON value + @sa @ref operator value_t() -- return the type of the JSON value (implicit) + + @since version 1.0.0, public since 2.1.0, `const char*` and `noexcept` + since 3.0.0 + */ + const char* type_name() const noexcept + { + { + switch (m_type) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::discarded: + return "discarded"; + default: + return "number"; + } + } + } + + + private: + ////////////////////// + // member variables // + ////////////////////// + + /// the type of the current element + value_t m_type = value_t::null; + + /// the value of the current element + json_value m_value = {}; + + ////////////////////////////////////////// + // binary serialization/deserialization // + ////////////////////////////////////////// + + /// @name binary serialization/deserialization support + /// @{ + + public: + /*! + @brief create a CBOR serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the CBOR (Concise + Binary Object Representation) serialization format. CBOR is a binary + serialization format which aims to be more compact than JSON itself, yet + more efficient to parse. + + The library uses the following mapping from JSON values types to + CBOR types according to the CBOR specification (RFC 7049): + + JSON value type | value/range | CBOR type | first byte + --------------- | ------------------------------------------ | ---------------------------------- | --------------- + null | `null` | Null | 0xF6 + boolean | `true` | True | 0xF5 + boolean | `false` | False | 0xF4 + number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3B + number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3A + number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39 + number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38 + number_integer | -24..-1 | Negative integer | 0x20..0x37 + number_integer | 0..23 | Integer | 0x00..0x17 + number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 + number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 + number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A + number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B + number_unsigned | 0..23 | Integer | 0x00..0x17 + number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 + number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 + number_unsigned | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A + number_unsigned | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B + number_float | *any value* | Double-Precision Float | 0xFB + string | *length*: 0..23 | UTF-8 string | 0x60..0x77 + string | *length*: 23..255 | UTF-8 string (1 byte follow) | 0x78 + string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79 + string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7A + string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7B + array | *size*: 0..23 | array | 0x80..0x97 + array | *size*: 23..255 | array (1 byte follow) | 0x98 + array | *size*: 256..65535 | array (2 bytes follow) | 0x99 + array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9A + array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9B + object | *size*: 0..23 | map | 0xA0..0xB7 + object | *size*: 23..255 | map (1 byte follow) | 0xB8 + object | *size*: 256..65535 | map (2 bytes follow) | 0xB9 + object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA + object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a CBOR value. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The following CBOR types are not used in the conversion: + - byte strings (0x40..0x5F) + - UTF-8 strings terminated by "break" (0x7F) + - arrays terminated by "break" (0x9F) + - maps terminated by "break" (0xBF) + - date/time (0xC0..0xC1) + - bignum (0xC2..0xC3) + - decimal fraction (0xC4) + - bigfloat (0xC5) + - tagged items (0xC6..0xD4, 0xD8..0xDB) + - expected conversions (0xD5..0xD7) + - simple values (0xE0..0xF3, 0xF8) + - undefined (0xF7) + - half and single-precision floats (0xF9-0xFA) + - break (0xFF) + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in CBOR format.,to_cbor} + + @sa http://cbor.io + @sa @ref from_cbor(detail::input_adapter, const bool strict) for the + analogous deserialization + @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format + + @since version 2.0.9 + */ + static std::vector to_cbor(const basic_json& j) + { + std::vector result; + to_cbor(j, result); + return result; + } + + static void to_cbor(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_cbor(j); + } + + static void to_cbor(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_cbor(j); + } + + /*! + @brief create a MessagePack serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the MessagePack + serialization format. MessagePack is a binary serialization format which + aims to be more compact than JSON itself, yet more efficient to parse. + + The library uses the following mapping from JSON values types to + MessagePack types according to the MessagePack specification: + + JSON value type | value/range | MessagePack type | first byte + --------------- | --------------------------------- | ---------------- | ---------- + null | `null` | nil | 0xC0 + boolean | `true` | true | 0xC3 + boolean | `false` | false | 0xC2 + number_integer | -9223372036854775808..-2147483649 | int64 | 0xD3 + number_integer | -2147483648..-32769 | int32 | 0xD2 + number_integer | -32768..-129 | int16 | 0xD1 + number_integer | -128..-33 | int8 | 0xD0 + number_integer | -32..-1 | negative fixint | 0xE0..0xFF + number_integer | 0..127 | positive fixint | 0x00..0x7F + number_integer | 128..255 | uint 8 | 0xCC + number_integer | 256..65535 | uint 16 | 0xCD + number_integer | 65536..4294967295 | uint 32 | 0xCE + number_integer | 4294967296..18446744073709551615 | uint 64 | 0xCF + number_unsigned | 0..127 | positive fixint | 0x00..0x7F + number_unsigned | 128..255 | uint 8 | 0xCC + number_unsigned | 256..65535 | uint 16 | 0xCD + number_unsigned | 65536..4294967295 | uint 32 | 0xCE + number_unsigned | 4294967296..18446744073709551615 | uint 64 | 0xCF + number_float | *any value* | float 64 | 0xCB + string | *length*: 0..31 | fixstr | 0xA0..0xBF + string | *length*: 32..255 | str 8 | 0xD9 + string | *length*: 256..65535 | str 16 | 0xDA + string | *length*: 65536..4294967295 | str 32 | 0xDB + array | *size*: 0..15 | fixarray | 0x90..0x9F + array | *size*: 16..65535 | array 16 | 0xDC + array | *size*: 65536..4294967295 | array 32 | 0xDD + object | *size*: 0..15 | fix map | 0x80..0x8F + object | *size*: 16..65535 | map 16 | 0xDE + object | *size*: 65536..4294967295 | map 32 | 0xDF + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a MessagePack value. + + @note The following values can **not** be converted to a MessagePack value: + - strings with more than 4294967295 bytes + - arrays with more than 4294967295 elements + - objects with more than 4294967295 elements + + @note The following MessagePack types are not used in the conversion: + - bin 8 - bin 32 (0xC4..0xC6) + - ext 8 - ext 32 (0xC7..0xC9) + - float 32 (0xCA) + - fixext 1 - fixext 16 (0xD4..0xD8) + + @note Any MessagePack output created @ref to_msgpack can be successfully + parsed by @ref from_msgpack. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in MessagePack format.,to_msgpack} + + @sa http://msgpack.org + @sa @ref from_msgpack(const std::vector&, const size_t) for the + analogous deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + related UBJSON format + + @since version 2.0.9 + */ + static std::vector to_msgpack(const basic_json& j) + { + std::vector result; + to_msgpack(j, result); + return result; + } + + static void to_msgpack(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_msgpack(j); + } + + static void to_msgpack(const basic_json& j, detail::output_adapter o) + { + binary_writer(o).write_msgpack(j); + } + + /*! + @brief create a UBJSON serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the UBJSON + (Universal Binary JSON) serialization format. UBJSON aims to be more compact + than JSON itself, yet more efficient to parse. + + The library uses the following mapping from JSON values types to + UBJSON types according to the UBJSON specification: + + JSON value type | value/range | UBJSON type | marker + --------------- | --------------------------------- | ----------- | ------ + null | `null` | null | `Z` + boolean | `true` | true | `T` + boolean | `false` | false | `F` + number_integer | -9223372036854775808..-2147483649 | int64 | `L` + number_integer | -2147483648..-32769 | int32 | `l` + number_integer | -32768..-129 | int16 | `I` + number_integer | -128..127 | int8 | `i` + number_integer | 128..255 | uint8 | `U` + number_integer | 256..32767 | int16 | `I` + number_integer | 32768..2147483647 | int32 | `l` + number_integer | 2147483648..9223372036854775807 | int64 | `L` + number_unsigned | 0..127 | int8 | `i` + number_unsigned | 128..255 | uint8 | `U` + number_unsigned | 256..32767 | int16 | `I` + number_unsigned | 32768..2147483647 | int32 | `l` + number_unsigned | 2147483648..9223372036854775807 | int64 | `L` + number_float | *any value* | float64 | `D` + string | *with shortest length indicator* | string | `S` + array | *see notes on optimized format* | array | `[` + object | *see notes on optimized format* | map | `{` + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a UBJSON value. + + @note The following values can **not** be converted to a UBJSON value: + - strings with more than 9223372036854775807 bytes (theoretical) + - unsigned integer numbers above 9223372036854775807 + + @note The following markers are not used in the conversion: + - `Z`: no-op values are not created. + - `C`: single-byte strings are serialized with `S` markers. + + @note Any UBJSON output created @ref to_ubjson can be successfully parsed + by @ref from_ubjson. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The optimized formats for containers are supported: Parameter + @a use_size adds size information to the beginning of a container and + removes the closing marker. Parameter @a use_type further checks + whether all elements of a container have the same type and adds the + type marker to the beginning of the container. The @a use_type + parameter must only be used together with @a use_size = true. Note + that @a use_size = true alone may result in larger representations - + the benefit of this parameter is that the receiving side is + immediately informed on the number of elements of the container. + + @param[in] j JSON value to serialize + @param[in] use_size whether to add size annotations to container types + @param[in] use_type whether to add type annotations to container types + (must be combined with @a use_size = true) + @return UBJSON serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in UBJSON format.,to_ubjson} + + @sa http://ubjson.org + @sa @ref from_ubjson(detail::input_adapter, const bool strict) for the + analogous deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + + @since version 3.1.0 + */ + static std::vector to_ubjson(const basic_json& j, + const bool use_size = false, + const bool use_type = false) + { + std::vector result; + to_ubjson(j, result, use_size, use_type); + return result; + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + static void to_ubjson(const basic_json& j, detail::output_adapter o, + const bool use_size = false, const bool use_type = false) + { + binary_writer(o).write_ubjson(j, use_size, use_type); + } + + /*! + @brief create a JSON value from an input in CBOR format + + Deserializes a given input @a i to a JSON value using the CBOR (Concise + Binary Object Representation) serialization format. + + The library maps CBOR types to JSON value types as follows: + + CBOR type | JSON value type | first byte + ---------------------- | --------------- | ---------- + Integer | number_unsigned | 0x00..0x17 + Unsigned integer | number_unsigned | 0x18 + Unsigned integer | number_unsigned | 0x19 + Unsigned integer | number_unsigned | 0x1A + Unsigned integer | number_unsigned | 0x1B + Negative integer | number_integer | 0x20..0x37 + Negative integer | number_integer | 0x38 + Negative integer | number_integer | 0x39 + Negative integer | number_integer | 0x3A + Negative integer | number_integer | 0x3B + Negative integer | number_integer | 0x40..0x57 + UTF-8 string | string | 0x60..0x77 + UTF-8 string | string | 0x78 + UTF-8 string | string | 0x79 + UTF-8 string | string | 0x7A + UTF-8 string | string | 0x7B + UTF-8 string | string | 0x7F + array | array | 0x80..0x97 + array | array | 0x98 + array | array | 0x99 + array | array | 0x9A + array | array | 0x9B + array | array | 0x9F + map | object | 0xA0..0xB7 + map | object | 0xB8 + map | object | 0xB9 + map | object | 0xBA + map | object | 0xBB + map | object | 0xBF + False | `false` | 0xF4 + True | `true` | 0xF5 + Nill | `null` | 0xF6 + Half-Precision Float | number_float | 0xF9 + Single-Precision Float | number_float | 0xFA + Double-Precision Float | number_float | 0xFB + + @warning The mapping is **incomplete** in the sense that not all CBOR + types can be converted to a JSON value. The following CBOR types + are not supported and will yield parse errors (parse_error.112): + - byte strings (0x40..0x5F) + - date/time (0xC0..0xC1) + - bignum (0xC2..0xC3) + - decimal fraction (0xC4) + - bigfloat (0xC5) + - tagged items (0xC6..0xD4, 0xD8..0xDB) + - expected conversions (0xD5..0xD7) + - simple values (0xE0..0xF3, 0xF8) + - undefined (0xF7) + + @warning CBOR allows map keys of any type, whereas JSON only allows + strings as keys in object values. Therefore, CBOR maps with keys + other than UTF-8 strings are rejected (parse_error.113). + + @note Any CBOR output created @ref to_cbor can be successfully parsed by + @ref from_cbor. + + @param[in] i an input in CBOR format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + @return deserialized JSON value + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if unsupported features from CBOR were + used in the given input @a v or if the input is not valid CBOR + @throw parse_error.113 if a string was expected as map key, but not found + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in CBOR + format to a JSON value.,from_cbor} + + @sa http://cbor.io + @sa @ref to_cbor(const basic_json&) for the analogous serialization + @sa @ref from_msgpack(detail::input_adapter, const bool) for the + related MessagePack format + @sa @ref from_ubjson(detail::input_adapter, const bool) for the related + UBJSON format + + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0 + */ + static basic_json from_cbor(detail::input_adapter i, + const bool strict = true) + { + return binary_reader(i).parse_cbor(strict); + } + + /*! + @copydoc from_cbor(detail::input_adapter, const bool) + */ + template::value, int> = 0> + static basic_json from_cbor(A1 && a1, A2 && a2, const bool strict = true) + { + return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_cbor(strict); + } + + /*! + @brief create a JSON value from an input in MessagePack format + + Deserializes a given input @a i to a JSON value using the MessagePack + serialization format. + + The library maps MessagePack types to JSON value types as follows: + + MessagePack type | JSON value type | first byte + ---------------- | --------------- | ---------- + positive fixint | number_unsigned | 0x00..0x7F + fixmap | object | 0x80..0x8F + fixarray | array | 0x90..0x9F + fixstr | string | 0xA0..0xBF + nil | `null` | 0xC0 + false | `false` | 0xC2 + true | `true` | 0xC3 + float 32 | number_float | 0xCA + float 64 | number_float | 0xCB + uint 8 | number_unsigned | 0xCC + uint 16 | number_unsigned | 0xCD + uint 32 | number_unsigned | 0xCE + uint 64 | number_unsigned | 0xCF + int 8 | number_integer | 0xD0 + int 16 | number_integer | 0xD1 + int 32 | number_integer | 0xD2 + int 64 | number_integer | 0xD3 + str 8 | string | 0xD9 + str 16 | string | 0xDA + str 32 | string | 0xDB + array 16 | array | 0xDC + array 32 | array | 0xDD + map 16 | object | 0xDE + map 32 | object | 0xDF + negative fixint | number_integer | 0xE0-0xFF + + @warning The mapping is **incomplete** in the sense that not all + MessagePack types can be converted to a JSON value. The following + MessagePack types are not supported and will yield parse errors: + - bin 8 - bin 32 (0xC4..0xC6) + - ext 8 - ext 32 (0xC7..0xC9) + - fixext 1 - fixext 16 (0xD4..0xD8) + + @note Any MessagePack output created @ref to_msgpack can be successfully + parsed by @ref from_msgpack. + + @param[in] i an input in MessagePack format convertible to an input + adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if unsupported features from MessagePack were + used in the given input @a i or if the input is not valid MessagePack + @throw parse_error.113 if a string was expected as map key, but not found + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + MessagePack format to a JSON value.,from_msgpack} + + @sa http://msgpack.org + @sa @ref to_msgpack(const basic_json&) for the analogous serialization + @sa @ref from_cbor(detail::input_adapter, const bool) for the related CBOR + format + @sa @ref from_ubjson(detail::input_adapter, const bool) for the related + UBJSON format + + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0 + */ + static basic_json from_msgpack(detail::input_adapter i, + const bool strict = true) + { + return binary_reader(i).parse_msgpack(strict); + } + + /*! + @copydoc from_msgpack(detail::input_adapter, const bool) + */ + template::value, int> = 0> + static basic_json from_msgpack(A1 && a1, A2 && a2, const bool strict = true) + { + return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_msgpack(strict); + } + + /*! + @brief create a JSON value from an input in UBJSON format + + Deserializes a given input @a i to a JSON value using the UBJSON (Universal + Binary JSON) serialization format. + + The library maps UBJSON types to JSON value types as follows: + + UBJSON type | JSON value type | marker + ----------- | --------------------------------------- | ------ + no-op | *no value, next value is read* | `N` + null | `null` | `Z` + false | `false` | `F` + true | `true` | `T` + float32 | number_float | `d` + float64 | number_float | `D` + uint8 | number_unsigned | `U` + int8 | number_integer | `i` + int16 | number_integer | `I` + int32 | number_integer | `l` + int64 | number_integer | `L` + string | string | `S` + char | string | `C` + array | array (optimized values are supported) | `[` + object | object (optimized values are supported) | `{` + + @note The mapping is **complete** in the sense that any UBJSON value can + be converted to a JSON value. + + @param[in] i an input in UBJSON format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if a parse error occurs + @throw parse_error.113 if a string could not be parsed successfully + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + UBJSON format to a JSON value.,from_ubjson} + + @sa http://ubjson.org + @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the + analogous serialization + @sa @ref from_cbor(detail::input_adapter, const bool) for the related CBOR + format + @sa @ref from_msgpack(detail::input_adapter, const bool) for the related + MessagePack format + + @since version 3.1.0 + */ + static basic_json from_ubjson(detail::input_adapter i, + const bool strict = true) + { + return binary_reader(i).parse_ubjson(strict); + } + + template::value, int> = 0> + static basic_json from_ubjson(A1 && a1, A2 && a2, const bool strict = true) + { + return binary_reader(detail::input_adapter(std::forward(a1), std::forward(a2))).parse_ubjson(strict); + } + + /// @} + + ////////////////////////// + // JSON Pointer support // + ////////////////////////// + + /// @name JSON Pointer functions + /// @{ + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. Similar to @ref operator[](const typename + object_t::key_type&), `null` values are created in arrays and objects if + necessary. + + In particular: + - If the JSON pointer points to an object key that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. + - If the JSON pointer points to an array index that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. All indices between the current maximum and the given + index are also filled with `null`. + - The special value `-` is treated as a synonym for the index past the + end. + + @param[in] ptr a JSON pointer + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + + @liveexample{The behavior is shown in the example.,operatorjson_pointer} + + @since version 2.0.0 + */ + reference operator[](const json_pointer& ptr) + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. The function does not change the JSON + value; no `null` values are created. In particular, the the special value + `-` yields an exception. + + @param[in] ptr JSON pointer to the desired element + + @return const reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + + @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} + + @since version 2.0.0 + */ + const_reference operator[](const json_pointer& ptr) const + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a reference to the element at with specified JSON pointer @a ptr, + with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @throw parse_error.106 if an array index in the passed JSON pointer @a ptr + begins with '0'. See example below. + + @throw parse_error.109 if an array index in the passed JSON pointer @a ptr + is not a number. See example below. + + @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr + is out of range. See example below. + + @throw out_of_range.402 if the array index '-' is used in the passed JSON + pointer @a ptr. As `at` provides checked access (and no elements are + implicitly inserted), the index '-' is always invalid. See example below. + + @throw out_of_range.403 if the JSON pointer describes a key of an object + which cannot be found. See example below. + + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. + See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 2.0.0 + + @liveexample{The behavior is shown in the example.,at_json_pointer} + */ + reference at(const json_pointer& ptr) + { + return ptr.get_checked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a const reference to the element at with specified JSON pointer @a + ptr, with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @throw parse_error.106 if an array index in the passed JSON pointer @a ptr + begins with '0'. See example below. + + @throw parse_error.109 if an array index in the passed JSON pointer @a ptr + is not a number. See example below. + + @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr + is out of range. See example below. + + @throw out_of_range.402 if the array index '-' is used in the passed JSON + pointer @a ptr. As `at` provides checked access (and no elements are + implicitly inserted), the index '-' is always invalid. See example below. + + @throw out_of_range.403 if the JSON pointer describes a key of an object + which cannot be found. See example below. + + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. + See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 2.0.0 + + @liveexample{The behavior is shown in the example.,at_json_pointer_const} + */ + const_reference at(const json_pointer& ptr) const + { + return ptr.get_checked(this); + } + + /*! + @brief return flattened JSON value + + The function creates a JSON object whose keys are JSON pointers (see [RFC + 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all + primitive. The original JSON value can be restored using the @ref + unflatten() function. + + @return an object that maps JSON pointers to primitive values + + @note Empty objects and arrays are flattened to `null` and will not be + reconstructed correctly by the @ref unflatten() function. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a JSON object is flattened to an + object whose keys consist of JSON pointers.,flatten} + + @sa @ref unflatten() for the reverse function + + @since version 2.0.0 + */ + basic_json flatten() const + { + basic_json result(value_t::object); + json_pointer::flatten("", *this, result); + return result; + } + + /*! + @brief unflatten a previously flattened JSON value + + The function restores the arbitrary nesting of a JSON value that has been + flattened before using the @ref flatten() function. The JSON value must + meet certain constraints: + 1. The value must be an object. + 2. The keys must be JSON pointers (see + [RFC 6901](https://tools.ietf.org/html/rfc6901)) + 3. The mapped values must be primitive JSON types. + + @return the original JSON from a flattened version + + @note Empty objects and arrays are flattened by @ref flatten() to `null` + values and can not unflattened to their original type. Apart from + this example, for a JSON value `j`, the following is always true: + `j == j.flatten().unflatten()`. + + @complexity Linear in the size the JSON value. + + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + + @liveexample{The following code shows how a flattened JSON object is + unflattened into the original nested JSON object.,unflatten} + + @sa @ref flatten() for the reverse function + + @since version 2.0.0 + */ + basic_json unflatten() const + { + return json_pointer::unflatten(*this); + } + + /// @} + + ////////////////////////// + // JSON Patch functions // + ////////////////////////// + + /// @name JSON Patch functions + /// @{ + + /*! + @brief applies a JSON patch + + [JSON Patch](http://jsonpatch.com) defines a JSON document structure for + expressing a sequence of operations to apply to a JSON) document. With + this function, a JSON Patch is applied to the current JSON value by + executing all operations from the patch. + + @param[in] json_patch JSON patch document + @return patched document + + @note The application of a patch is atomic: Either all operations succeed + and the patched document is returned or an exception is thrown. In + any case, the original value is not changed: the patch is applied + to a copy of the value. + + @throw parse_error.104 if the JSON patch does not consist of an array of + objects + + @throw parse_error.105 if the JSON patch is malformed (e.g., mandatory + attributes are missing); example: `"operation add must have member path"` + + @throw out_of_range.401 if an array index is out of range. + + @throw out_of_range.403 if a JSON pointer inside the patch could not be + resolved successfully in the current JSON value; example: `"key baz not + found"` + + @throw out_of_range.405 if JSON pointer has no parent ("add", "remove", + "move") + + @throw other_error.501 if "test" operation was unsuccessful + + @complexity Linear in the size of the JSON value and the length of the + JSON patch. As usually only a fraction of the JSON value is affected by + the patch, the complexity can usually be neglected. + + @liveexample{The following code shows how a JSON patch is applied to a + value.,patch} + + @sa @ref diff -- create a JSON patch by comparing two JSON values + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + basic_json patch(const basic_json& json_patch) const + { + // make a working copy to apply the patch to + basic_json result = *this; + + // the valid JSON Patch operations + enum class patch_operations {add, remove, replace, move, copy, test, invalid}; + + const auto get_op = [](const std::string & op) + { + if (op == "add") + { + return patch_operations::add; + } + if (op == "remove") + { + return patch_operations::remove; + } + if (op == "replace") + { + return patch_operations::replace; + } + if (op == "move") + { + return patch_operations::move; + } + if (op == "copy") + { + return patch_operations::copy; + } + if (op == "test") + { + return patch_operations::test; + } + + return patch_operations::invalid; + }; + + // wrapper for "add" operation; add value at ptr + const auto operation_add = [&result](json_pointer & ptr, basic_json val) + { + // adding to the root of the target document means replacing it + if (ptr.is_root()) + { + result = val; + } + else + { + // make sure the top element of the pointer exists + json_pointer top_pointer = ptr.top(); + if (top_pointer != ptr) + { + result.at(top_pointer); + } + + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result[ptr]; + + switch (parent.m_type) + { + case value_t::null: + case value_t::object: + { + // use operator[] to add value + parent[last_path] = val; + break; + } + + case value_t::array: + { + if (last_path == "-") + { + // special case: append to back + parent.push_back(val); + } + else + { + const auto idx = json_pointer::array_index(last_path); + if (JSON_UNLIKELY(static_cast(idx) > parent.size())) + { + // avoid undefined behavior + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + else + { + // default case: insert add offset + parent.insert(parent.begin() + static_cast(idx), val); + } + } + break; + } + + default: + { + // if there exists a parent it cannot be primitive + assert(false); // LCOV_EXCL_LINE + } + } + } + }; + + // wrapper for "remove" operation; remove value at ptr + const auto operation_remove = [&result](json_pointer & ptr) + { + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result.at(ptr); + + // remove child + if (parent.is_object()) + { + // perform range check + auto it = parent.find(last_path); + if (JSON_LIKELY(it != parent.end())) + { + parent.erase(it); + } + else + { + JSON_THROW(out_of_range::create(403, "key '" + last_path + "' not found")); + } + } + else if (parent.is_array()) + { + // note erase performs range check + parent.erase(static_cast(json_pointer::array_index(last_path))); + } + }; + + // type check: top level value must be an array + if (JSON_UNLIKELY(not json_patch.is_array())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); + } + + // iterate and apply the operations + for (const auto& val : json_patch) + { + // wrapper to get a value for an operation + const auto get_value = [&val](const std::string & op, + const std::string & member, + bool string_type) -> basic_json & + { + // find value + auto it = val.m_value.object->find(member); + + // context-sensitive error message + const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; + + // check if desired value is present + if (JSON_UNLIKELY(it == val.m_value.object->end())) + { + JSON_THROW(parse_error::create(105, 0, error_msg + " must have member '" + member + "'")); + } + + // check if result is of type string + if (JSON_UNLIKELY(string_type and not it->second.is_string())) + { + JSON_THROW(parse_error::create(105, 0, error_msg + " must have string member '" + member + "'")); + } + + // no error: return value + return it->second; + }; + + // type check: every element of the array must be an object + if (JSON_UNLIKELY(not val.is_object())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); + } + + // collect mandatory members + const std::string op = get_value("op", "op", true); + const std::string path = get_value(op, "path", true); + json_pointer ptr(path); + + switch (get_op(op)) + { + case patch_operations::add: + { + operation_add(ptr, get_value("add", "value", false)); + break; + } + + case patch_operations::remove: + { + operation_remove(ptr); + break; + } + + case patch_operations::replace: + { + // the "path" location must exist - use at() + result.at(ptr) = get_value("replace", "value", false); + break; + } + + case patch_operations::move: + { + const std::string from_path = get_value("move", "from", true); + json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The move operation is functionally identical to a + // "remove" operation on the "from" location, followed + // immediately by an "add" operation at the target + // location with the value that was just removed. + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } + + case patch_operations::copy: + { + const std::string from_path = get_value("copy", "from", true); + const json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The copy is functionally identical to an "add" + // operation at the target location using the value + // specified in the "from" member. + operation_add(ptr, v); + break; + } + + case patch_operations::test: + { + bool success = false; + JSON_TRY + { + // check if "value" matches the one at "path" + // the "path" location must exist - use at() + success = (result.at(ptr) == get_value("test", "value", false)); + } + JSON_CATCH (out_of_range&) + { + // ignore out of range errors: success remains false + } + + // throw an exception if test fails + if (JSON_UNLIKELY(not success)) + { + JSON_THROW(other_error::create(501, "unsuccessful: " + val.dump())); + } + + break; + } + + case patch_operations::invalid: + { + // op must be "add", "remove", "replace", "move", "copy", or + // "test" + JSON_THROW(parse_error::create(105, 0, "operation value '" + op + "' is invalid")); + } + } + } + + return result; + } + + /*! + @brief creates a diff as a JSON patch + + Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can + be changed into the value @a target by calling @ref patch function. + + @invariant For two JSON values @a source and @a target, the following code + yields always `true`: + @code {.cpp} + source.patch(diff(source, target)) == target; + @endcode + + @note Currently, only `remove`, `add`, and `replace` operations are + generated. + + @param[in] source JSON value to compare from + @param[in] target JSON value to compare against + @param[in] path helper value to create JSON pointers + + @return a JSON patch to convert the @a source to @a target + + @complexity Linear in the lengths of @a source and @a target. + + @liveexample{The following code shows how a JSON patch is created as a + diff for two JSON values.,diff} + + @sa @ref patch -- apply a JSON patch + @sa @ref merge_patch -- apply a JSON Merge Patch + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + + @since version 2.0.0 + */ + static basic_json diff(const basic_json& source, const basic_json& target, + const std::string& path = "") + { + // the patch + basic_json result(value_t::array); + + // if the values are the same, return empty patch + if (source == target) + { + return result; + } + + if (source.type() != target.type()) + { + // different types: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + } + else + { + switch (source.type()) + { + case value_t::array: + { + // first pass: traverse common elements + std::size_t i = 0; + while (i < source.size() and i < target.size()) + { + // recursive call to compare array values at index i + auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + ++i; + } + + // i now reached the end of at least one array + // in a second pass, traverse the remaining elements + + // remove my remaining elements + const auto end_index = static_cast(result.size()); + while (i < source.size()) + { + // add operations in reverse order to avoid invalid + // indices + result.insert(result.begin() + end_index, object( + { + {"op", "remove"}, + {"path", path + "/" + std::to_string(i)} + })); + ++i; + } + + // add other remaining elements + while (i < target.size()) + { + result.push_back( + { + {"op", "add"}, + {"path", path + "/" + std::to_string(i)}, + {"value", target[i]} + }); + ++i; + } + + break; + } + + case value_t::object: + { + // first pass: traverse this object's elements + for (auto it = source.cbegin(); it != source.cend(); ++it) + { + // escape the key name to be used in a JSON patch + const auto key = json_pointer::escape(it.key()); + + if (target.find(it.key()) != target.end()) + { + // recursive call to compare object values at key it + auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } + else + { + // found a key that is not in o -> remove it + result.push_back(object( + { + {"op", "remove"}, {"path", path + "/" + key} + })); + } + } + + // second pass: traverse other object's elements + for (auto it = target.cbegin(); it != target.cend(); ++it) + { + if (source.find(it.key()) == source.end()) + { + // found a key that is not in this -> add it + const auto key = json_pointer::escape(it.key()); + result.push_back( + { + {"op", "add"}, {"path", path + "/" + key}, + {"value", it.value()} + }); + } + } + + break; + } + + default: + { + // both primitive type: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + break; + } + } + } + + return result; + } + + /// @} + + //////////////////////////////// + // JSON Merge Patch functions // + //////////////////////////////// + + /// @name JSON Merge Patch functions + /// @{ + + /*! + @brief applies a JSON Merge Patch + + The merge patch format is primarily intended for use with the HTTP PATCH + method as a means of describing a set of modifications to a target + resource's content. This function applies a merge patch to the current + JSON value. + + The function implements the following algorithm from Section 2 of + [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396): + + ``` + define MergePatch(Target, Patch): + if Patch is an Object: + if Target is not an Object: + Target = {} // Ignore the contents and set it to an empty Object + for each Name/Value pair in Patch: + if Value is null: + if Name exists in Target: + remove the Name/Value pair from Target + else: + Target[Name] = MergePatch(Target[Name], Value) + return Target + else: + return Patch + ``` + + Thereby, `Target` is the current object; that is, the patch is applied to + the current value. + + @param[in] patch the patch to apply + + @complexity Linear in the lengths of @a patch. + + @liveexample{The following code shows how a JSON Merge Patch is applied to + a JSON document.,merge_patch} + + @sa @ref patch -- apply a JSON patch + @sa [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396) + + @since version 3.0.0 + */ + void merge_patch(const basic_json& patch) + { + if (patch.is_object()) + { + if (not is_object()) + { + *this = object(); + } + for (auto it = patch.begin(); it != patch.end(); ++it) + { + if (it.value().is_null()) + { + erase(it.key()); + } + else + { + operator[](it.key()).merge_patch(it.value()); + } + } + } + else + { + *this = patch; + } + } + + /// @} +}; +} // namespace nlohmann + +/////////////////////// +// nonmember support // +/////////////////////// + +// specialization of std::swap, and std::hash +namespace std +{ +/*! +@brief exchanges the values of two JSON objects + +@since version 1.0.0 +*/ +template<> +inline void swap(nlohmann::json& j1, + nlohmann::json& j2) noexcept( + is_nothrow_move_constructible::value and + is_nothrow_move_assignable::value + ) +{ + j1.swap(j2); +} + +/// hash value for JSON objects +template<> +struct hash +{ + /*! + @brief return a hash value for a JSON object + + @since version 1.0.0 + */ + std::size_t operator()(const nlohmann::json& j) const + { + // a naive hashing via the string representation + const auto& h = hash(); + return h(j.dump()); + } +}; + +/// specialization for std::less +/// @note: do not remove the space after '<', +/// see https://github.com/nlohmann/json/pull/679 +template<> +struct less< ::nlohmann::detail::value_t> +{ + /*! + @brief compare two value_t enum values + @since version 3.0.0 + */ + bool operator()(nlohmann::detail::value_t lhs, + nlohmann::detail::value_t rhs) const noexcept + { + return nlohmann::detail::operator<(lhs, rhs); + } +}; + +} // namespace std + +/*! +@brief user-defined string literal for JSON values + +This operator implements a user-defined string literal for JSON objects. It +can be used by adding `"_json"` to a string literal and returns a JSON object +if no parse error occurred. + +@param[in] s a string representation of a JSON object +@param[in] n the length of string @a s +@return a JSON object + +@since version 1.0.0 +*/ +inline nlohmann::json operator "" _json(const char* s, std::size_t n) +{ + return nlohmann::json::parse(s, s + n); +} + +/*! +@brief user-defined string literal for JSON pointer + +This operator implements a user-defined string literal for JSON Pointers. It +can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer +object if no parse error occurred. + +@param[in] s a string representation of a JSON Pointer +@param[in] n the length of string @a s +@return a JSON pointer object + +@since version 2.0.0 +*/ +inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) +{ + return nlohmann::json::json_pointer(std::string(s, n)); +} + +// #include + + +// restore GCC/clang diagnostic settings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic pop +#endif +#if defined(__clang__) + #pragma GCC diagnostic pop +#endif + +// clean up +#undef JSON_CATCH +#undef JSON_THROW +#undef JSON_TRY +#undef JSON_LIKELY +#undef JSON_UNLIKELY +#undef JSON_DEPRECATED +#undef JSON_HAS_CPP_14 +#undef JSON_HAS_CPP_17 +#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION +#undef NLOHMANN_BASIC_JSON_TPL +#undef NLOHMANN_JSON_HAS_HELPER + + +#endif diff --git a/thirdparty/graph-tools-master/include/graphIO/BamWriter.hh b/thirdparty/graph-tools-master/include/graphIO/BamWriter.hh new file mode 100755 index 0000000..5740aef --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphIO/BamWriter.hh @@ -0,0 +1,126 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Felix Schlesinger +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include + +// cppcheck-suppress missingInclude +#include "htslib/hts.h" +// cppcheck-suppress missingInclude +#include "htslib/sam.h" + +#include "graphalign/GraphAlignment.hh" +#include "graphcore/GraphReferenceMapping.hh" + +using namespace graphtools; +using Sequence = std::string; // Type of read sequences +namespace graphIO +{ + +using ReferenceContigs = std::vector>; + +/** + * Subset of information on a BAM record for graph-alignment output + */ +struct BamAlignment +{ + std::string chromName; // Has to match a contig in the BAM header + int pos = -1; // 0-based + bool isPaired = false; + bool isMate1 = false; + std::string fragmentName; + Sequence sequence; + std::vector BaseQualities; + std::string graphCigar; // Represents the graph alignment of the read (in a string BAM tag) +}; + +/** + * Write Graph-alignments to a BAM file + */ +class BamWriter +{ +public: + /** + * Paired-end status of an alignment + */ + enum class PairingInfo + { + Unpaired, + FirstMate, + SecondMate + }; + + /** + * @param bamPath Path to output BAM file + * @param contigs Contig names and sequence lengths for BAM header + * @throws If BamFile cannot be created + */ + explicit BamWriter(std::string const& bamPath, ReferenceContigs& contigs); + /** + * Create an unplaced BAM alignment with a graph CIGAR tag + * @param fragmentName Name of the aligned read (fragment) + * @param sequence Sequence of the aligned read + * @param qualities BaseQ. Must be empty or have the same length as sequence + * @param pairing If paired-end, which mate is this? + * @param graphAlign Graph-CIGAR string of alignment + */ + BamAlignment makeAlignment( + std::string const& fragmentName, Sequence const& sequence, std::vector const& qualities, + BamWriter::PairingInfo pairing, std::string const& graphAlign) const; + + /** + * Project a graph alignment to the reference genome and output as placed but unmapped BAM record + * @param refMap Graph to refernece genome coordinate projection + * @param fragmentName Name of the aligned read (fragment) + * @param sequence Sequence of the aligned read + * @param qualities BaseQ. Must be empty or have the same length as sequence + * @param pairing If paired-end, which mate is this? + * @param graphAlign Graph-aligment to project and output + */ + BamAlignment makeAlignment( + GraphReferenceMapping const& refMap, std::string const& fragmentName, Sequence const& sequence, + std::vector const& qualities, BamWriter::PairingInfo pairing, GraphAlignment const& align) const; + /** + * Write a BAM alignment + * @param align Alignment to write + */ + void writeAlignment(BamAlignment& align); + +private: + int writeHeader(std::string const& initHeader, ReferenceContigs& contigs); + + std::unique_ptr filePtr_; + std::unique_ptr bamHeader_; + + static std::string const initHeader; // Dummy header line + static std::string const graphCigarBamTag; // Custom tag to use for graphCIGAR string +}; +} \ No newline at end of file diff --git a/thirdparty/graph-tools-master/include/graphIO/GraphJson.hh b/thirdparty/graph-tools-master/include/graphIO/GraphJson.hh new file mode 100755 index 0000000..7faf47d --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphIO/GraphJson.hh @@ -0,0 +1,68 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Felix Schlesinger +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +// cppcheck-suppress missingInclude +#include "nlohmann/json.hpp" + +#include "graphcore/Graph.hh" +#include "graphcore/GraphReferenceMapping.hh" + +using namespace graphtools; + +namespace graphIO +{ + +using Json = nlohmann::json; + +/** + * Load JSON from file and parse Graph. + * Graph can be either directly the top-level object in the json or under 'graph' + */ +Graph loadGraph(std::string const& jsonPath); + +/** + * Create graph from Json representation. + * @throws if the Json does not represent a valid graph + */ +Graph parseGraph(Json const& jsonGraph); + +/** + * Create Json representation of the graph + */ +Json graphToJson(Graph const& graph); + +/** + * Load Reference mapping from graph description + * @param jRefmap Json representation of referenceMapping. Must match the graph + * @param graph Graph to map to reference. + * @throws if the Json does not represent a valid reference map or does not match the graph + */ +GraphReferenceMapping parseReferenceMapping(Json const& jRefmap, Graph const& graph); +} diff --git a/thirdparty/graph-tools-master/include/graphIO/ReferenceGenome.hh b/thirdparty/graph-tools-master/include/graphIO/ReferenceGenome.hh new file mode 100755 index 0000000..7a2e4aa --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphIO/ReferenceGenome.hh @@ -0,0 +1,60 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Felix Schlesinger +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include + +// cppcheck-suppress missingInclude +#include "htslib/faidx.h" + +#include "graphcore/GraphReferenceMapping.hh" + +using namespace graphtools; + +namespace graphIO +{ + +class RefGenome +{ +public: + explicit RefGenome(std::string const& genome_path); + + /** + * Retrieve a piece of reference sequence + * @return The sequence in upper case + * @throws If not a valid region in the reference genome + */ + std::string extractSeq(ReferenceInterval const&) const; + +private: + std::string const fastaPath_; + std::unique_ptr fai_; +}; +} \ No newline at end of file diff --git a/thirdparty/graph-tools-master/include/graphalign/DagAlignerAffine.hh b/thirdparty/graph-tools-master/include/graphalign/DagAlignerAffine.hh new file mode 100755 index 0000000..c109abe --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/DagAlignerAffine.hh @@ -0,0 +1,393 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Roman Petrovski +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include + +#include +#include + +#include "dagAligner/AffineAlignMatrix.hh" +#include "dagAligner/AffineAlignMatrixVectorized.hh" +#include "dagAligner/PenaltyMatrix.hh" + +namespace graphalign +{ + +namespace dagAligner +{ + /** + * Performs global alignment of query against DAG of target nodes. + * \param clipFront true instructs to represent insertions at the start of CIGAR as soft clips + */ + template class Aligner + { + AlignMatrix alignMatrix_; + + // max number of best paths to backtrack + const std::size_t maxRepeats_; + + public: + Aligner( + const typename AlignMatrix::PenaltyMatrix& penaltyMatrix, Score gapOpen, Score gapExt, + std::size_t maxRepeats = 10) + : alignMatrix_(penaltyMatrix, gapOpen, gapExt) + , maxRepeats_(maxRepeats) + { + } + + template + void __attribute((noinline)) + align(QueryIt queryBegin, QueryIt queryEnd, TargetIt targetBegin, TargetIt targetEnd, const EdgeMap& edgeMap) + { + alignMatrix_.init(queryBegin, queryEnd, targetBegin, targetEnd, edgeMap); + } + + struct Step + { + Cigar::OpCode operation_; + int q_; + int t_; + }; + + static bool removeDuplicateCigars(std::vector& cigars) + { + std::sort(cigars.begin(), cigars.end()); + std::vector::iterator uniq = std::unique(cigars.begin(), cigars.end()); + if (cigars.end() != uniq) + { + cigars.erase(uniq, cigars.end()); + return true; + } + + return false; + } + template + Score __attribute((noinline)) + backtrackAllPaths(const EdgeMap& edgeMap, std::vector& cigars, Score& secondBestScore) const + { + Score bestScore = SCORE_MIN; + secondBestScore = SCORE_MIN; + typename AlignMatrix::const_iterator bestCell + = alignMatrix_.template nextBestAlign(alignMatrix_.alignBegin(), secondBestScore); + for (; alignMatrix_.alignEnd() != bestCell && bestScore <= secondBestScore; + bestCell = alignMatrix_.template nextBestAlign(bestCell + 1, secondBestScore)) + { + bestScore = secondBestScore; + const int t = alignMatrix_.targetOffset(bestCell); + const int q = alignMatrix_.queryOffset(bestCell); + const int softClip = alignMatrix_.queryLen() - 1 - q; + + std::size_t firstNodeId = edgeMap.getNodeId(t); + Cigar start; + start.push_back(Cigar::Operation(Cigar::NODE_START, firstNodeId)); + if (softClip) + { + start.push_back(Cigar::Operation(Cigar::SOFT_CLIP, softClip)); + } + + if (!backtrackPath(edgeMap, start, firstNodeId, q, t, cigars)) + { + // ran out of cigars buffer + return bestScore; + } + } + + removeDuplicateCigars(cigars); + if (1 < cigars.size()) + { + // at least one duplicate, reset secondBestScore + secondBestScore = bestScore; + } + else if (alignMatrix_.alignEnd() == bestCell) + { + // one candidate only, no second best. Might had some duplicates, reset secondBestScore + secondBestScore = SCORE_MIN; + } + // else scondBest is set properly + return bestScore; + } + + template + Cigar __attribute((noinline)) + backtrackBestPath(const EdgeMap& edgeMap, Score& bestScore, Score& secondBestScore) const + { + bestScore = SCORE_MIN; + secondBestScore = SCORE_MIN; + typename AlignMatrix::const_iterator bestCell + = alignMatrix_.template nextBestAlign(alignMatrix_.alignBegin(), bestScore); + if (alignMatrix_.alignEnd() == bestCell) + { + throw std::logic_error("No best path available"); + } + std::vector ret; + const int t = alignMatrix_.targetOffset(bestCell); + const int q = alignMatrix_.queryOffset(bestCell); + const int softClip = alignMatrix_.queryLen() - 1 - q; + + std::size_t firstNodeId = edgeMap.getNodeId(t); + Cigar start; + start.push_back(Cigar::Operation(Cigar::NODE_START, firstNodeId)); + if (softClip) + { + start.push_back(Cigar::Operation(Cigar::SOFT_CLIP, softClip)); + } + + backtrackPath(edgeMap, start, firstNodeId, q, t, ret); + alignMatrix_.template nextBestAlign(bestCell + 1, secondBestScore); + return ret.front(); + } + + private: + template + Step __attribute((noinline)) stepBack( + const EdgeMap& edgeMap, const Cigar& base, std::size_t lastNodeId, int q, int t, + std::vector& cigars) const + { + Step ret = { Cigar::UNKNOWN, q, t }; + + if (alignMatrix_.isInsertion(q, t)) + { + const Cigar::OpCode code + = (1 == base.length() && Cigar::NODE_START == base.lastOp()) || Cigar::SOFT_CLIP == base.lastOp() + ? Cigar::SOFT_CLIP + : Cigar::INSERT; + ret = Step{ code, q - 1, t }; + } + + EdgeMap::OffsetEdges::const_iterator prevNodeIndexIt = edgeMap.prevNodesBegin(t); + while (prevNodeIndexIt != edgeMap.prevNodesEnd(t)) + { + const int p = *prevNodeIndexIt; + if (alignMatrix_.isDeletion(q, t, p)) + { + if (Cigar::UNKNOWN != ret.operation_ && exploreAllPaths) + { + // recurse if more than one path is possible + if (!backtrackPath(edgeMap, base + Cigar::DELETE, lastNodeId, q, p, cigars)) + { + return Step{ Cigar::UNKNOWN, q, t }; + } + } + else + { + ret = Step{ Cigar::DELETE, q, p }; + } + } + + if (alignMatrix_.isMatch(q, t, p)) + { + if (Cigar::UNKNOWN != ret.operation_ && exploreAllPaths) + { + // recurse if more than one path is possible + if (!backtrackPath(edgeMap, base + Cigar::MATCH, lastNodeId, q - 1, p, cigars)) + { + return Step{ Cigar::UNKNOWN, q, t }; + } + } + else + { + ret = Step{ Cigar::MATCH, q - 1, p }; + } + } + else if (alignMatrix_.isMismatch(q, t, p)) + { + const Cigar::OpCode code = (1 == base.length() && Cigar::NODE_START == base.lastOp()) + || Cigar::SOFT_CLIP == base.lastOp() + ? Cigar::SOFT_CLIP + : Cigar::MISMATCH; + if (Cigar::UNKNOWN != ret.operation_ && exploreAllPaths) + { + // recurse if more than one path is possible + if (!backtrackPath(edgeMap, base + code, lastNodeId, q - 1, p, cigars)) + { + return Step{ Cigar::UNKNOWN, q, t }; + } + } + else + { + ret = Step{ code, q - 1, p }; + } + } + + ++prevNodeIndexIt; + } + + if (Cigar::UNKNOWN == ret.operation_) + { + throw std::logic_error("backtracking failure: no nodes found on best path!"); + } + + // Return the one we have not recursed for. If only one path is possible, no recursion occurs + return ret; + } + + template + bool backtrackPath( + const EdgeMap& edgeMap, const Cigar& base, std::size_t lastNodeId, int q, int t, + std::vector& cigars) const + { + Cigar ret = base; + while (-1 != q && -1 != t) + { + const std::size_t curNodeId = edgeMap.getNodeId(t); + if (lastNodeId != curNodeId) + { + ret.push_back(Cigar::Operation(Cigar::NODE_END, lastNodeId)); + ret.push_back(Cigar::Operation(Cigar::NODE_START, curNodeId)); + lastNodeId = curNodeId; + } + + Step step = stepBack(edgeMap, ret, lastNodeId, q, t, cigars); + if (Cigar::UNKNOWN == step.operation_) + { + // ran out of cigars buffer + return false; + } + + ret.append(step.operation_); + q = step.q_; + t = step.t_; + } + + if (-1 != q) + { + if (Cigar::DELETE == ret.lastOp()) + { + const Cigar::Operation del = ret.pop_back(); + ret.push_back(Cigar::Operation(Cigar::INSERT, q + 1)); + ret.push_back(del); + } + else + { + ret.push_back(Cigar::Operation(Cigar::INSERT, q + 1)); + } + } + else if (-1 != t) // we're either on -1 row or -1 column + { + // count number of bases to the start of the last node + const std::size_t nodeId = edgeMap.getNodeId(t + 1); + int cur = t + 1; + while (cur && edgeMap.getNodeId(cur - 1) == nodeId) + { + --cur; + } + if (t + 1 != cur) + { + ret.push_back(Cigar::Operation(Cigar::DELETE, t + 1 - cur)); + } + } + + if (clipFront && Cigar::INSERT == ret.lastOp()) + { + ret.lastOp() = Cigar::SOFT_CLIP; + } + ret.push_back(Cigar::Operation(Cigar::NODE_END, lastNodeId)); + + ret.collapseLastEmptyNode(); + ret.reverse(); + if (cigars.size() == maxRepeats_ && !removeDuplicateCigars(cigars)) + { + return false; + } + cigars.push_back(ret); + return true; + } + + friend std::ostream& operator<<(std::ostream& os, const Aligner& aligner) + { + return os << "Aligner(" << aligner.alignMatrix_ << ")"; + } + }; + +} // namespace dagAligner + +// template +// class DagAligner +// : public dagAligner::Aligner> +// { +// public: +// DagAligner(const dagAligner::FixedPenaltyMatrix& penaltyMatrix, dagAligner::Score gapOpen, dagAligner::Score gapExt) +// : dagAligner::Aligner>( +// penaltyMatrix, gapOpen, gapExt) +// { +// } +// +// DagAligner(dagAligner::Score match, dagAligner::Score mismatch, dagAligner::Score gapOpen, dagAligner::Score gapExt) +// : dagAligner::Aligner>( +// dagAligner::FixedPenaltyMatrix(match, mismatch), gapOpen, gapExt) +// { +// } +// }; + +template +class DagAligner : public dagAligner::Aligner< + dagAligner::AffineAlignMatrixVectorized< + dagAligner::FixedPenaltyMatrix, penalizeMove>, + clipFront> +{ + typedef dagAligner::FixedPenaltyMatrix PenaltyMatrix; + +public: + DagAligner(const PenaltyMatrix& penaltyMatrix, dagAligner::Score gapOpen, dagAligner::Score gapExt) + : dagAligner::Aligner, clipFront>( + penaltyMatrix, gapOpen, gapExt) + { + } + + DagAligner(dagAligner::Score match, dagAligner::Score mismatch, dagAligner::Score gapOpen, dagAligner::Score gapExt) + : dagAligner::Aligner, clipFront>( + PenaltyMatrix(match, mismatch), gapOpen, gapExt) + { + } +}; + +// template +// class DagAligner +// : public dagAligner::Aligner> +// { +// public: +// DagAligner(const dagAligner::FreePenaltyMatrix& penaltyMatrix, dagAligner::Score gapOpen, dagAligner::Score +// gapExt) +// : dagAligner::Aligner>( +// penaltyMatrix, gapOpen, gapExt) +// { +// } +// +// DagAligner(dagAligner::Score match, dagAligner::Score mismatch, dagAligner::Score gapOpen, dagAligner::Score +// gapExt) +// : dagAligner::Aligner>( +// dagAligner::FreePenaltyMatrix(match, mismatch), gapOpen, gapExt) +// { +// } +// }; + +} // namespace graphalign diff --git a/thirdparty/graph-tools-master/include/graphalign/GaplessAligner.hh b/thirdparty/graph-tools-master/include/graphalign/GaplessAligner.hh new file mode 100755 index 0000000..adfedc9 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/GaplessAligner.hh @@ -0,0 +1,120 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include + +#include "graphalign/GraphAligner.hh" +#include "graphalign/GraphAlignment.hh" +#include "graphalign/KmerIndex.hh" +#include "graphcore/Path.hh" + +namespace graphtools +{ +/** + * Gapless graph aligner + * + * Enables gapless alignment of any sequence to a graph. + */ +class GaplessAligner : public GraphAligner +{ +public: + GaplessAligner(const Graph* graph_ptr, int32_t kmer_len) + : kmer_len_(kmer_len) + , kmer_index_(*graph_ptr, kmer_len) + { + } + std::list align(const std::string& query) const override; + +private: + int32_t kmer_len_; + KmerIndex kmer_index_; +}; + +/** + * Determines orientation of a read relative to the graph + */ +class StrandClassifier +{ +public: + StrandClassifier(const Graph& graph, int32_t kmer_len) + : kmer_len_(kmer_len) + , kmer_index_(graph, kmer_len) + { + } + bool isForwardOriented(const std::string& seq) const; + +private: + int32_t countKmerMatches(const std::string& seq) const; + int32_t kmer_len_; + KmerIndex kmer_index_; +}; + +/** + * Computes a top-scoring gapless alignment of a query sequence to the graph + * that goes through the path starting at the given position on the sequence + * + * @param path: Any path shorter than the query + * @param start_pos: Position on the query corrsponding to the start of the + * path + * @param query: Any sequence + * @return Best gapless alignment with the above properety + */ +std::list getBestAlignmentToShortPath(const Path& path, int32_t start_pos, const std::string& query); + +/** + * Aligns a query sequence to a path of the same length + * + * @param path: Any graph path + * @param query: Any sequence that has the same length as the path + * @return Result of the alignment + */ +GraphAlignment alignWithoutGaps(const Path& path, const std::string& query); + +/** + * Aligns query sequence to the reference sequence starting at the given + * position + * + * @param query: Any sequence + * @param ref_start: Position of the start of the alignment on the reference + * @param reference: Any sequence + * @return Result of the alignment + */ +Alignment alignWithoutGaps(int32_t ref_start, const std::string& reference, const std::string& query); + +/** + * Extracts kmers starting at each position + * + * @param query: Any sequence + * @param kmer_len: Kmer length + * @return List of kmers indexed by start position in the original sequence + */ +std::list extractKmersFromAllPositions(const std::string& query, int32_t kmer_len); +} diff --git a/thirdparty/graph-tools-master/include/graphalign/GappedAligner.hh b/thirdparty/graph-tools-master/include/graphalign/GappedAligner.hh new file mode 100755 index 0000000..e56e0e6 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/GappedAligner.hh @@ -0,0 +1,170 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko , +// Roman Petrovski +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include +#include + +#include "graphalign/GaplessAligner.hh" +#include "graphalign/GraphAligner.hh" +#include "graphalign/GraphAlignment.hh" +#include "graphalign/KmerIndex.hh" +#include "graphalign/LinearAlignment.hh" +#include "graphalign/LinearAlignmentParameters.hh" +#include "graphalign/PinnedDagAligner.hh" +#include "graphalign/PinnedPathAligner.hh" +#include "graphcore/Graph.hh" +#include "graphcore/Path.hh" + +namespace graphtools +{ + +using PathAndAlignment = std::pair; + +/** + * General graph aligner supporting linear gaps. + */ +class GappedGraphAligner : public GraphAligner +{ +public: + /** + * Initializes the aligner + * + * @param graph: A graph possibly containing loops (but no cycles) + * @param kmer_len: Kmer length for kmer index + * @param padding_len: Elongate paths by this much during path kmer extension step to allow for gaps + * @param seed_affix_trim_len: Trim length for the prefix and suffix (=affix) of the path + * @param match_score: Score for matching bases + * @param mismatch_score: Score for mismatching bases + * @param gap_open_score: Score for opeaning a gap (linear) + * @param gap_extend_score: Score for extending an open gap (linear) + */ + GappedGraphAligner( + const Graph* graph_ptr, size_t kmer_len, size_t padding_len, size_t seed_affix_trim_len, + const std::string& alignerName, LinearAlignmentParameters alignerParameters = LinearAlignmentParameters()) + : kmer_len_(kmer_len) + , padding_len_(padding_len) + , seed_affix_trim_len_(seed_affix_trim_len) + , kmer_index_(*graph_ptr, kmer_len) + , aligner_(alignerName, alignerParameters) + { + } + + /** + * Aligns a read to the graph + * + * @param query: Query sequence + * @return List of top-scoring graph alignments + */ + std::list align(const std::string& query) const override; + + /** + * Extends a path matching a kmer in the query sequence to full-length alignments + * + * @param kmer_path: Kmer match path + * @param query: Query sequence + * @param kmer_start_on_query: Position of the left-most base of the kmer on the query sequence + * @return List of top-scoring graph alignments going through the kmer match path + */ + std::list + extendKmerMatchToFullAlignments(Path kmer_path, const std::string& query, size_t kmer_start_on_query) const; + + /** + * Aligns query suffix to all suffix-extensions of a given path + * + * @param query_piece: Query suffix to align + * @param seed_path: Path from whose suffix the alignments should start + * @param extension_len: Length of suffix-extensions + * @return List of top-scoring alignments and their paths; each path is extended to contain the seed path + */ + std::list + extendAlignmentPrefix(const Path& seed_path, const std::string& query_piece, size_t extension_len) const; + + /** + * Aligns query prefix to all prefix-extensions of a given path + * + * @param query_piece: Query prefix to align + * @param seed_path: Path at whose prefix the alignments should end + * @param extension_len: Length of prefix-extensions + * @return List of top-scoring alignments and their paths; each path is extended to contain the seed path + */ + std::list + extendAlignmentSuffix(const Path& seed_path, const std::string& query_piece, size_t extension_len) const; + +private: + const size_t kmer_len_; + const size_t padding_len_; + const int32_t seed_affix_trim_len_; + const KmerIndex kmer_index_; + + class AlignerSelector + { + std::shared_ptr ptrPathAligner_; + mutable std::shared_ptr ptrDagAligner_; + + public: + AlignerSelector(const std::string& alignerName, const LinearAlignmentParameters& alignerParameters) + { + if ("path-aligner" == alignerName) + { + ptrPathAligner_ = std::make_shared( + alignerParameters.matchScore, alignerParameters.mismatchScore, alignerParameters.gapOpenScore); + } + else if ("dag-aligner" == alignerName) + { + ptrDagAligner_ = std::make_shared( + alignerParameters.matchScore, alignerParameters.mismatchScore, alignerParameters.gapOpenScore, + alignerParameters.gapExtendScore); + } + else + { + throw std::invalid_argument("Aligner " + alignerName + " is not available"); + } + } + + std::list + suffixAlign(const Path& seed_path, const std::string& query_piece, size_t extension_len, int& score) const + { + return ptrPathAligner_ ? ptrPathAligner_->suffixAlign(seed_path, query_piece, extension_len, score) + : ptrDagAligner_->suffixAlign(seed_path, query_piece, extension_len, score); + } + + std::list + prefixAlign(const Path& seed_path, const std::string& query_piece, size_t extension_len, int& score) const + { + return ptrPathAligner_ ? ptrPathAligner_->prefixAlign(seed_path, query_piece, extension_len, score) + : ptrDagAligner_->prefixAlign(seed_path, query_piece, extension_len, score); + } + + } aligner_; +}; +} diff --git a/thirdparty/graph-tools-master/include/graphalign/GraphAligner.hh b/thirdparty/graph-tools-master/include/graphalign/GraphAligner.hh new file mode 100755 index 0000000..98b00bf --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/GraphAligner.hh @@ -0,0 +1,47 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include + +#include "graphalign/GraphAlignment.hh" + +namespace graphtools +{ +/** + * Interface class for graph aligners + */ +class GraphAligner +{ +public: + virtual ~GraphAligner(){}; + virtual std::list align(const std::string& query) const = 0; +}; +} \ No newline at end of file diff --git a/thirdparty/graph-tools-master/include/graphalign/GraphAlignment.hh b/thirdparty/graph-tools-master/include/graphalign/GraphAlignment.hh new file mode 100755 index 0000000..bcb3492 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/GraphAlignment.hh @@ -0,0 +1,103 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "graphalign/LinearAlignment.hh" +#include "graphcore/Graph.hh" +#include "graphcore/Path.hh" + +namespace graphtools +{ +/** + * Represents an alignment of a sequence to a graph. Graph alignment consists of a path and linear alignments for each + * node of the path. + */ +class GraphAlignment +{ +public: + typedef size_t size_type; + typedef std::vector NodeAlignments; + typedef NodeAlignments::const_iterator const_iterator; + GraphAlignment(const Path& path, const std::vector& alignments) + : path_(path) + , alignments_(alignments) + { + assertValidity(); + } + + uint32_t queryLength() const; + uint32_t referenceLength() const; + uint32_t numMatches() const; + const Path& path() const { return path_; } + bool overlapsNode(NodeId node_id) const; + NodeId getNodeIdByIndex(int32_t node_index) const { return path_.getNodeIdByIndex(node_index); } + std::list getIndexesOfNode(NodeId node_id) const; + + // Removes the specified number of reference bases from the beginning of the alignment while softclipping as many + // query bases as required + void shrinkStart(int reference_length); + + // Removes the specified number of reference bases from the end of the alignment while softclipping as many query + // bases as required + void shrinkEnd(int reference_length); + + const_iterator begin() const { return alignments_.begin(); } + const_iterator end() const { return alignments_.end(); } + const Alignment& front() const { return alignments_.front(); } + const Alignment& back() const { return alignments_.back(); } + size_type size() const { return alignments_.size(); } + const Alignment& operator[](size_t index) const { return alignments_[index]; } + const std::vector& alignments() const { return alignments_; } + bool operator==(const GraphAlignment& other) const + { + return path_ == other.path_ && alignments_ == other.alignments_; + } + bool operator<(const GraphAlignment& other) const; + std::string generateCigar() const; + + friend std::ostream& operator<<(std::ostream& os, const GraphAlignment& graph_alignment); + +private: + void assertValidity() const; + Path path_; + std::vector alignments_; +}; + +std::ostream& operator<<(std::ostream& os, const GraphAlignment& graph_alignment); +} diff --git a/thirdparty/graph-tools-master/include/graphalign/GraphAlignmentOperations.hh b/thirdparty/graph-tools-master/include/graphalign/GraphAlignmentOperations.hh new file mode 100755 index 0000000..c8b4eaa --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/GraphAlignmentOperations.hh @@ -0,0 +1,75 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include + +#include "graphalign/GraphAlignment.hh" +#include "graphalign/LinearAlignment.hh" +#include "graphcore/Graph.hh" + +namespace graphtools +{ + +// Checks if graph alignment is consistent with the given query sequence +bool checkConsistency(const GraphAlignment& graph_alignment, const std::string& query); + +// A node CIGAR is a string of the form "[linear alignment CIGAR]". This function extracts node_id and +// linear alignment CIGAR from the node CIGAR. +void splitNodeCigar(const std::string& node_cigar, std::string& cigar, NodeId& node_id); + +/** + * Converts graph CIGAR string to a graph alignment + * + * @param first_node_start Start position of the alignment on the first node + * @param graph_cigar Graph CIGAR string + * @param query Query sequence + * @param graph_ptr Pointer to the graph + * @return GraphAlignment + */ +GraphAlignment decodeGraphAlignment(int32_t first_node_start, const std::string& graph_cigar, const Graph* graph_ptr); + +/** + * Convert linear alignment to graph alignment by projecting it onto a compatible path + * + * @param linear_alignment: Linear alignment + * @param path: Path to project the alignment onto; sequence of the path must be equal to alignment's reference sequence + * @return Graph alignment composed of the input linear alignment and the (appropriately shrunk) path + */ +GraphAlignment projectAlignmentOntoGraph(Alignment linear_alignment, Path path); + +// Splits query into pieces corresponding to each node that the alignment spans +std::list getQuerySequencesForEachNode(const GraphAlignment& graph_alignment, const std::string& query); + +// Encodes alignment as a three-row strings where the top corresponds to the query sequence, the bottom to the sequence +// of alignment's path, and the middle contains a "|" for each pair of matching bases; gaps are indicated by "-"; ends +// of nodes are denoted by ":" +std::string prettyPrint(const GraphAlignment& graph_alignment, const std::string& query); +} diff --git a/thirdparty/graph-tools-master/include/graphalign/KmerIndex.hh b/thirdparty/graph-tools-master/include/graphalign/KmerIndex.hh new file mode 100755 index 0000000..564e978 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/KmerIndex.hh @@ -0,0 +1,74 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko , +// Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include +#include +#include + +#include "graphcore/Graph.hh" +#include "graphcore/Path.hh" + +namespace graphtools +{ + +typedef std::unordered_map> StringToPathsMap; + +// Kmer index holds paths that correspond to each kmer that appears in the graph and supports a few standard operations. +class KmerIndex +{ +public: + explicit KmerIndex(const Graph& graph, int32_t kmer_len = 12); + explicit KmerIndex(const StringToPathsMap& kmer_to_paths_map); + KmerIndex(const KmerIndex& other); + KmerIndex(KmerIndex&& other) noexcept; + KmerIndex& operator=(const KmerIndex& other); + KmerIndex& operator=(KmerIndex&& other) noexcept; + ~KmerIndex(); + bool operator==(const KmerIndex& other) const; + std::string encode() const; + const std::list& getPaths(const std::string& kmer) const; + bool contains(const std::string& kmer) const; + size_t numPaths(const std::string& kmer) const; + std::unordered_set kmers() const; + size_t kmerLength() const; + + size_t numUniqueKmersOverlappingNode(NodeId node_id) const; + size_t numUniqueKmersOverlappingEdge(NodeId from, NodeId to) const; + +private: + struct Impl; + std::unique_ptr pimpl_; +}; + +std::ostream& operator<<(std::ostream& os, const KmerIndex& kmer_index); +} diff --git a/thirdparty/graph-tools-master/include/graphalign/KmerIndexOperations.hh b/thirdparty/graph-tools-master/include/graphalign/KmerIndexOperations.hh new file mode 100755 index 0000000..ad5edac --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/KmerIndexOperations.hh @@ -0,0 +1,50 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Peter Krusche , +// Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include + +#include "graphalign/KmerIndex.hh" + +namespace graphtools +{ + +// Returns true if the sequence is in graph's (forward) orientation +bool checkIfForwardOriented(const KmerIndex& kmer_index, const std::string& sequence); + +/** + * Find minimum kmer length that covers each node with a unique kmer + * @param graph a graph + * @param min_unique_kmers_per_edge min number of unique kmers to cover each edge + * @param min_unique_kmers_per_node min number of unique kmers to cover each node + * @return + */ +int findMinCoveringKmerLength(Graph const* graph, size_t min_unique_kmers_per_edge, size_t min_unique_kmers_per_node); +} diff --git a/thirdparty/graph-tools-master/include/graphalign/LinearAlignment.hh b/thirdparty/graph-tools-master/include/graphalign/LinearAlignment.hh new file mode 100755 index 0000000..e200860 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/LinearAlignment.hh @@ -0,0 +1,108 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include + +#include "graphalign/Operation.hh" + +namespace graphtools +{ + +// Represents a linear alignment +class Alignment +{ +public: + using size_type = size_t; + using const_iterator = std::list::const_iterator; + + Alignment(int32_t reference_start, std::list operations) + : reference_start_(reference_start) + , operations_(std::move(operations)) + { + updateCounts(); + } + Alignment(uint32_t reference_start, const std::string& cigar); + const std::list& operations() const { return operations_; } + size_type numOperations() const { return operations_.size(); } + uint32_t queryLength() const; + uint32_t referenceLength() const; + uint32_t referenceStart() const { return reference_start_; } + void setReferenceStart(uint32_t reference_start) { reference_start_ = reference_start; } + + const_iterator begin() const { return operations_.begin(); } + const_iterator end() const { return operations_.end(); } + const Operation& front() const { return operations_.front(); } + const Operation& back() const { return operations_.back(); } + size_type size() const { return operations_.size(); } + bool operator==(const Alignment& other) const + { + return operations_ == other.operations_ && reference_start_ == other.reference_start_; + } + bool operator<(const Alignment& other) const; + + size_t numMatched() const { return matched_; } + size_t numMismatched() const { return mismatched_; } + size_t numClipped() const { return clipped_; } + size_t numInserted() const { return inserted_; } + size_t numDeleted() const { return deleted_; } + std::string generateCigar() const; + /** + * Reverses an alignment + * + * @param reference_length: Total length of the reference sequence + */ + void reverse(size_t reference_length); + + /** + * Splits off a piece of the alignment at the given reference position + * + * @param reference_position: Position at which the alignment is to be split + * @return Suffix alignment + */ + Alignment splitAtReferencePosition(size_t reference_position); + +protected: + void decodeCigar(const std::string& encoding); + void updateCounts(); + +private: + size_t matched_ = 0; + size_t mismatched_ = 0; + size_t clipped_ = 0; + size_t inserted_ = 0; + size_t deleted_ = 0; + size_t missing_ = 0; + int32_t reference_start_ = 0; + std::list operations_; +}; + +std::ostream& operator<<(std::ostream& os, const Alignment& alignment); +} diff --git a/thirdparty/graph-tools-master/include/graphalign/LinearAlignmentOperations.hh b/thirdparty/graph-tools-master/include/graphalign/LinearAlignmentOperations.hh new file mode 100755 index 0000000..2d03728 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/LinearAlignmentOperations.hh @@ -0,0 +1,93 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include +#include + +#include "graphalign/LinearAlignment.hh" +#include "graphutils/SequenceOperations.hh" + +namespace graphtools +{ + +// Checks if a given linear alignment is consistent with the given query and reference sequences +bool checkConsistency(const Alignment& alignment, const std::string& reference, const std::string& query); + +/** + * Splits query and reference into pieces corresponding to operations that the alignment is made of + * + * @param alignment: Any linear alignment + * @param query: Any query sequence + * @param reference: Any reference sequence + * @return: List of pairs of sequences corresponding to each operation in the alignment + */ +std::list +getSequencesForEachOperation(const Alignment& alignment, const std::string& reference, const std::string& query); + +/** + * Checks if two alignments are bookended + * + * Two alignments are considered bookended if + * - Positions of first alignment end and second alignment start are adjacent and + * - First alignment does not end in softclipped bases (unless all of its bases are softclipped) + * - Second alignment does not start in softclipped bases (unless all of its bases are softclipped) + * + * @param first_alignment: Any linear alignment + * @param second_alignment: Any linear alignment + * @return true if alignment are bookended + */ +bool checkIfBookended(const Alignment& first_alignment, const Alignment& second_alignment); + +/** + * Merges two bookended alignments into a longer alignment + * + * @param first_alignment: Any linear alignment + * @param second_alignment: Any linear alignment that is bookeneded with the first + * @return Merged alignment + */ +Alignment mergeAlignments(const Alignment& first_alignment, const Alignment& second_alignment); + +/** + * Calculates alignment score + * + * @param alignment: Any linear alignment + * @param match_score: Score of a match + * @param mismatch_score: Score of a mismatch + * @param gap_score: Score of a gap + * @return Alignment score + */ +int32_t scoreAlignment(const Alignment& alignment, int32_t match_score, int32_t mismatch_score, int32_t gap_score); + +// Encodes alignment as a three-row strings where the top corresponds to the query sequence, the bottom to the reference +// sequence, and the middle contains a "|" for each pair of matching bases; gaps are indicated by "-" +std::string prettyPrint(const Alignment& alignment, const std::string& reference, const std::string& query); +} diff --git a/thirdparty/graph-tools-master/include/graphalign/LinearAlignmentParameters.hh b/thirdparty/graph-tools-master/include/graphalign/LinearAlignmentParameters.hh new file mode 100755 index 0000000..76c38f8 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/LinearAlignmentParameters.hh @@ -0,0 +1,57 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +/** + * Holds scores for linear sequence alignment algorithms + */ +struct LinearAlignmentParameters +{ + LinearAlignmentParameters() { assertThatScoresAreValid(); } + LinearAlignmentParameters(int32_t matchScore, int32_t mismatchScore, int32_t gapOpenScore, int32_t gapExtendScore) + : matchScore(matchScore) + , mismatchScore(mismatchScore) + , gapOpenScore(gapOpenScore) + , gapExtendScore(gapExtendScore) + { + assertThatScoresAreValid(); + } + + void assertThatScoresAreValid() + { + assert(0 <= matchScore && mismatchScore <= 0 && gapOpenScore <= 0 && gapExtendScore <= 0); + } + + const int32_t matchScore = 5; + const int32_t mismatchScore = -4; + const int32_t gapOpenScore = -8; + const int32_t gapExtendScore = -2; +}; diff --git a/thirdparty/graph-tools-master/include/graphalign/Operation.hh b/thirdparty/graph-tools-master/include/graphalign/Operation.hh new file mode 100755 index 0000000..8823cce --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/Operation.hh @@ -0,0 +1,76 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include + +namespace graphtools +{ +enum class OperationType +{ + kMatch, + kMismatch, + kInsertionToRef, + kDeletionFromRef, + kSoftclip, + kMissingBases +}; + +// Represents a single alignment operation +class Operation +{ +public: + Operation(OperationType type, uint32_t length) + : type_(type) + , length_(length) + { + } + explicit Operation(std::string cigar); + + OperationType type() const { return type_; } + uint32_t length() const { return length_; } + uint32_t referenceLength() const; + uint32_t queryLength() const; + + bool operator==(const Operation& other) const { return type_ == other.type_ && length_ == other.length_; } + bool operator<(const Operation& other) const; + + std::string generateCigar() const; + +private: + OperationType type_; + uint32_t length_; +}; + +OperationType decodeOperationType(char type_encoding); + +std::ostream& operator<<(std::ostream& os, OperationType operation_type); +std::ostream& operator<<(std::ostream& os, const Operation& operation); +} diff --git a/thirdparty/graph-tools-master/include/graphalign/OperationOperations.hh b/thirdparty/graph-tools-master/include/graphalign/OperationOperations.hh new file mode 100755 index 0000000..be0e328 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/OperationOperations.hh @@ -0,0 +1,50 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/Operation.hh" + +#include +#include + +namespace graphtools +{ + +// Returns true if a given operation is consistent with the given query and reference sequences +bool checkConsistency(const Operation& operation, const std::string& reference, const std::string& query); + +using OperationPair = std::pair; + +/** + * Splits a given operation by reference length + * + * @param operation: Any operation that spans over one base of the reference + * @param prefix_reference_length: Length of the first piece (prefix) + * @return A pair of operations produced by the split + */ +OperationPair splitByReferenceLength(const Operation& operation, uint32_t prefix_reference_length); +} \ No newline at end of file diff --git a/thirdparty/graph-tools-master/include/graphalign/PinnedAligner.hh b/thirdparty/graph-tools-master/include/graphalign/PinnedAligner.hh new file mode 100755 index 0000000..c2d45f9 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/PinnedAligner.hh @@ -0,0 +1,71 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include + +#include "graphalign/LinearAlignment.hh" +#include "graphalign/TracebackMatrix.hh" + +namespace graphtools +{ + +/** + * Performs local alignment of a pair of sequences that starts at the beginning or the end of both sequences + */ +class PinnedAligner +{ +public: + PinnedAligner(int32_t match_score, int32_t mismatch_score, int32_t gap_score) + : match_score_(match_score) + , mismatch_score_(mismatch_score) + , gap_score_(gap_score) + { + } + TracebackMatrix populateTracebackMatrix(const std::string& reference, const std::string& query); + // Calculates a top-scoring local alignment of a query to the reference that starts at left-most position of both + // sequences + Alignment prefixAlign(const std::string& reference, const std::string& query); + // Calculates a top-scoring local alignment of a query to the reference that starts at right-most position of both + // sequences + Alignment suffixAlign(std::string query, std::string reference); + +private: + void fillTopLeft(TracebackMatrix& matrix); + void fillTopRow(TracebackMatrix& matrix); + void fillLeftColumn(TracebackMatrix& matrix); + void fillBody(const std::string& reference, const std::string& query, TracebackMatrix& matrix); + void fillBodyCell(TracebackMatrix& matrix, size_t row_index, size_t col_index, bool do_bases_match); + + int32_t match_score_; + int32_t mismatch_score_; + int32_t gap_score_; +}; +} diff --git a/thirdparty/graph-tools-master/include/graphalign/PinnedDagAligner.hh b/thirdparty/graph-tools-master/include/graphalign/PinnedDagAligner.hh new file mode 100755 index 0000000..d950e25 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/PinnedDagAligner.hh @@ -0,0 +1,627 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Roman Petrovski +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include + +#include + +#include "graphalign/DagAlignerAffine.hh" +#include "graphalign/GraphAlignment.hh" +#include "graphalign/Operation.hh" +#include "graphalign/dagAligner/BaseMatchingPenaltyMatrix.hh" +#include "graphcore/Graph.hh" +#include "graphcore/Path.hh" + +namespace graphtools +{ + +using PathAndAlignment = std::pair; + +template +class BaseMatchingDagAligner : public graphalign::dagAligner::Aligner< + graphalign::dagAligner::AffineAlignMatrixVectorized< + graphalign::dagAligner::BaseMatchingPenaltyMatrix, penalizeMove>, + clipFront> +{ + typedef graphalign::dagAligner::BaseMatchingPenaltyMatrix PenaltyMatrix; + typedef graphalign::dagAligner::Score Score; + +public: + BaseMatchingDagAligner(const PenaltyMatrix& penaltyMatrix, Score gapOpen, Score gapExt) + : graphalign::dagAligner::Aligner< + graphalign::dagAligner::AffineAlignMatrixVectorized, clipFront>( + penaltyMatrix, gapOpen, gapExt) + { + } + + BaseMatchingDagAligner(graphalign::dagAligner::Score match, Score mismatch, Score gapOpen, Score gapExt) + : graphalign::dagAligner::Aligner< + graphalign::dagAligner::AffineAlignMatrixVectorized, clipFront>( + PenaltyMatrix(match, mismatch), gapOpen, gapExt) + { + } +}; + +/** + * Performs alignment of query pieces that start or end at the seed in the graph + */ +class PinnedDagAligner +{ + typedef std::pair Edge; + typedef std::vector Edges; + BaseMatchingDagAligner aligner_; + + static void appendOperation(OperationType type, uint32_t length, std::list& operations) + { + if (operations.empty() || operations.back().type() != type) + { + operations.push_back(Operation(type, length)); + } + else + { + operations.back() = Operation(type, operations.back().length() + length); + } + } + + template + static void parseGraphCigar( + const GraphT& graph, const graphalign::dagAligner::Cigar& cigar, PathT& path, std::list& operations) + { + using namespace graphalign::dagAligner; + for (const Cigar::Operation& op : cigar) + { + if (Cigar::NODE_START == op.code_) + { + if (path.lastNodeId() != op.value_ + || std::size_t(path.endPosition()) == graph.nodeSeq(op.value_).length()) + { + path.extendEndToNode(op.value_); + } + } + else if (Cigar::NODE_END == op.code_) + { + } + else + switch (op.code_) + { + case Cigar::MATCH: + { + appendOperation(OperationType::kMatch, op.value_, operations); + path.shiftEndAlongNode(op.value_); + break; + } + case Cigar::MISMATCH: + { + appendOperation(OperationType::kMismatch, op.value_, operations); + path.shiftEndAlongNode(op.value_); + break; + } + case Cigar::INSERT: + { + appendOperation(OperationType::kInsertionToRef, op.value_, operations); + break; + } + case Cigar::SOFT_CLIP: + { + appendOperation(OperationType::kSoftclip, op.value_, operations); + break; + } + case Cigar::DELETE: + { + appendOperation(OperationType::kDeletionFromRef, op.value_, operations); + path.shiftEndAlongNode(op.value_); + break; + } + default: + { + throw std::logic_error(std::string("Unexpected graph cigar operation:") + std::to_string(op.code_)); + } + } + } + } + + typedef int MappedId; + + void unmapNodeIds(const std::map& originalIds, graphalign::dagAligner::Cigar& cigar) + { + using namespace graphalign::dagAligner; + for (Cigar::Operation& op : cigar) + { + if (Cigar::NODE_START == op.code_ || Cigar::NODE_END == op.code_) + { + op.value_ = originalIds.at(op.value_); + } + } + } + + /** + * \brief Tests if the cigar first node is a repeat expansion and corrects + * cigar to ensure that fraction of the first expansion is interpreted correctly + */ + template + void fixFirstNodeExpansion( + const std::vector& nodeIds, const std::map& originalIds, const PathT& seedPath, + graphalign::dagAligner::Cigar& cigar) + { + using namespace graphalign::dagAligner; + + if (seedPath.lastNodeId() != originalIds.at(cigar.firstNode()) || MappedId(cigar.firstNode()) != nodeIds[0]) + { + const int skipLen + = seedPath.graphRawPtr()->nodeSeq(seedPath.lastNodeId()).length() - seedPath.endPosition(); + if (0 > skipLen) + { + throw std::logic_error("invalid distance to skip"); + } + if (skipLen) + { + const Cigar::Operation skipFirstNode[] + = { Cigar::Operation( + Cigar::NODE_START, nodeIds[0]), // endPosition is on the base that belongs to the path... + Cigar::Operation(Cigar::DELETE, skipLen), Cigar::Operation(Cigar::NODE_END, nodeIds[0]) }; + + cigar.insert( + cigar.begin(), skipFirstNode, skipFirstNode + sizeof(skipFirstNode) / sizeof(skipFirstNode[0])); + } + } + } + +public: + explicit PinnedDagAligner( + const int32_t matchScore, const int32_t mismatchScore, const int32_t gapOpenScore, const int32_t gapExtendScore) + : aligner_(matchScore, mismatchScore, gapOpenScore, gapExtendScore) + { + } + + std::list + prefixAlign(const Path& seedPath, const std::string& queryPiece, size_t extensionLen, int& score) + { + using namespace graphalign::dagAligner; + std::vector nodeIds; + Edges edges; + std::string target; + + // when repeat expansions are unrolled each copy gets a unique id, so, all + // ids have to be remapped + std::map originalIds; + + bfsDiscoverEdges( + *seedPath.graphRawPtr(), seedPath.nodeIds().back(), seedPath.endPosition(), extensionLen, nodeIds, edges, + target, originalIds); + edges.push_back(Edge(target.length(), target.length())); + + std::list ret; + if (!target.empty()) + { + const EdgeMap alignerEdges(edges, nodeIds); + + aligner_.align(queryPiece.begin(), queryPiece.end(), target.begin(), target.end(), alignerEdges); + + std::vector cigars; + Score secondBestScore = 0; + Score bestScore = aligner_.backtrackAllPaths(alignerEdges, cigars, secondBestScore); + + score = bestScore; + for (Cigar& cigar : cigars) + { + fixFirstNodeExpansion(nodeIds, originalIds, seedPath, cigar); + + unmapNodeIds(originalIds, cigar); + + Path path = seedPath; + std::list operations; + parseGraphCigar(*seedPath.graphRawPtr(), cigar, path, operations); + + ret.push_back(PathAndAlignment(path, Alignment(seedPath.seq().length(), operations))); + } + } + + return ret; + } + + std::list + suffixAlign(const Path& seedPath, std::string queryPiece, size_t extensionLen, int& score) + { + using namespace graphalign::dagAligner; + std::vector nodeIds; + Edges edges; + std::string target; + + // when repeat expansions are unrolled each copy gets a unique id, so, all + // ids have to be remapped + std::map originalIds; + + ReverseGraph rg(*seedPath.graphRawPtr()); + bfsDiscoverEdges( + rg, seedPath.nodeIds().front(), + // endPosition is on the base that belongs to the path... + ConstReversePath(seedPath).endPosition(), extensionLen, nodeIds, edges, target, originalIds); + edges.push_back(Edge(target.length(), target.length())); + + std::list ret; + if (!target.empty()) + { + const EdgeMap alignerEdges(edges, nodeIds); + + std::reverse(queryPiece.begin(), queryPiece.end()); + aligner_.align(queryPiece.begin(), queryPiece.end(), target.begin(), target.end(), alignerEdges); + + std::vector cigars; + Score secondBestScore = 0; + Score bestScore = aligner_.backtrackAllPaths(alignerEdges, cigars, secondBestScore); + + score = bestScore; + for (Cigar& cigar : cigars) + { + fixFirstNodeExpansion(nodeIds, originalIds, ConstReversePath(seedPath), cigar); + + unmapNodeIds(originalIds, cigar); + + Path path = seedPath; + ReversePath rp(path); + std::list operations; + parseGraphCigar(rg, cigar, rp, operations); + operations.reverse(); + + // reversed alignments always start at the beginning of the path because + // the seed path gets start-extended to incorporate them + ret.push_back(PathAndAlignment(path, Alignment(0, operations))); + } + } + + return ret; + } + +private: + template + static std::map extractSubgraph( + const GraphT& graph, const NodeId startNodeId, const std::size_t startNodeOffset, const std::size_t seqLen) + { + std::map nodeStartSeqOffset; + + if (graph.nodeSeq(startNodeId).length() == startNodeOffset) + { + // flag empty start node in a special way + nodeStartSeqOffset[startNodeId] = -1; + } + else + { + // first node start is at the start of the sequence + nodeStartSeqOffset[startNodeId] = 0; + } + + // nodes to be visited by bfs + std::deque shouldVisit(1, startNodeId); + + // extract longest subgraph of nodes such that each node begins within + // seqLen from the start of the start node + while (!shouldVisit.empty()) + { + const NodeId currentNodeId = shouldVisit.front(); + shouldVisit.pop_front(); + + const std::string& currentNodeSeq = graph.nodeSeq(currentNodeId); + const int currentNodeSeqOffset = nodeStartSeqOffset[currentNodeId]; + + // avoid dealing with individual node startNodeOffset (only start node has it) + // by pretending the sequence starts at the start node start + if (seqLen + startNodeOffset - std::max(0, currentNodeSeqOffset) > currentNodeSeq.length()) + { + const int successorSeqOffset + = -1 == currentNodeSeqOffset ? 0 : currentNodeSeqOffset + currentNodeSeq.length(); + // sequence does not terminate at this node, enqueue successors + const std::set& successors = graph.successors(currentNodeId); + for (const NodeId successorId : successors) + { + const auto seenSuccessor = nodeStartSeqOffset.find(successorId); + if (nodeStartSeqOffset.end() == seenSuccessor || seenSuccessor->second > successorSeqOffset) + { + // some successors will end up listed in shouldVisit more than once at a time + shouldVisit.push_back(successorId); + nodeStartSeqOffset[successorId] = successorSeqOffset; + } + } + } + } + + return nodeStartSeqOffset; + } + + typedef std::pair IdEdge; + typedef std::vector IdEdges; + + /** + * \brief expands repeats up to remainder of sequnce length + * \return pairs of mapped node ids indicating an edge between them. + * \postcondition result array is ordered by successor id then by predecessor id + */ + template + static IdEdges unrollRepeats( + const GraphT& graph, const std::size_t seqLen, const std::map& nodeStartSeqOffset, + std::map& originalIds, std::multimap& mappedIds) + { + IdEdges idEdges; + for (const auto& nodeIdOffset : nodeStartSeqOffset) + { + if (int(seqLen) <= nodeIdOffset.second) + { + throw std::logic_error("node should not be in the subgraph"); + } + + const std::set& successors = graph.successors(nodeIdOffset.first); + if (successors.end() != successors.find(nodeIdOffset.first)) + { + const std::size_t nodeSeqLen = graph.nodeSeq(nodeIdOffset.first).length(); + // don't forget empty repeat start node has special sequence offset -1 + for (std::size_t lenLeft = seqLen - std::max(0, nodeIdOffset.second); lenLeft;) + { + // chain unrolled repeat nodes together + IdEdge edge(mappedIds.size(), mappedIds.size() + 1); + idEdges.push_back(edge); + mappedIds.emplace(nodeIdOffset.first, originalIds.size()); + originalIds.emplace(originalIds.size(), nodeIdOffset.first); + lenLeft -= std::min(lenLeft, nodeSeqLen); + } + + // since edges point forward, above loop always produces one more edge than we need + idEdges.pop_back(); + } + else + { + mappedIds.emplace(nodeIdOffset.first, originalIds.size()); + originalIds.emplace(originalIds.size(), nodeIdOffset.first); + } + } + + linkPredecessors(graph, originalIds, mappedIds, idEdges); + + // group edges by successor node + std::sort(idEdges.begin(), idEdges.end(), [](const IdEdge& left, const IdEdge& right) { + return left.second < right.second || (left.second == right.second && left.first < right.first); + }); + + return idEdges; + } + + static void dfsExtractOrderedNodeIds( + const MappedId currentId, const IdEdges& idEdges, const std::vector& idEdgesIndex, + std::vector& seenNodes, std::vector& nodeIds) + { + if (!seenNodes.at(currentId)) + { + seenNodes[currentId] = true; + for (std::size_t predOffset = idEdgesIndex.at(currentId); idEdgesIndex.at(currentId + 1) != predOffset; + ++predOffset) + { + if (idEdges.at(predOffset).second != currentId) + { + throw std::logic_error( + "dfsExtractOrderedNodeIds: Invalid edge for node " + std::to_string(currentId)); + } + + dfsExtractOrderedNodeIds(idEdges[predOffset].first, idEdges, idEdgesIndex, seenNodes, nodeIds); + } + nodeIds.push_back(currentId); + } + } + /** + * \brief dfs in order to produce the topological ordering with start node on top + */ + static std::vector + extractOrderedNodeIds(const IdEdges& idEdges, const std::vector& idEdgesIndex) + { + std::vector nodeIds; + std::vector seenNodes(idEdgesIndex.size(), false); + for (std::size_t mappedId = 0; idEdgesIndex.size() - 1 != mappedId; ++mappedId) + { + dfsExtractOrderedNodeIds(mappedId, idEdges, idEdgesIndex, seenNodes, nodeIds); + } + + return nodeIds; + } + + /** + * \brief self-repeat edges already in idEdges, add all mapped predecessor edges to each first expansion + * and non-repeat nodes + */ + template + static void linkPredecessors( + const GraphT& graph, const std::map& originalIds, + const std::multimap& mappedIds, IdEdges& idEdges) + { + for (MappedId mappedId = 0; MappedId(mappedIds.size()) != mappedId;) + { + const NodeId originalId = originalIds.at(mappedId); + + bool selfRepeat = false; + for (const NodeId& predecessorId : graph.predecessors(originalId)) + { + if (predecessorId != originalId) + { + auto mappedPredIds = mappedIds.equal_range(predecessorId); + + // other nodes, insert edges for each predecessor instance + // empty ranges indicate predecessors that are not part of subgraph + while (mappedPredIds.second != mappedPredIds.first) + { + IdEdge edge(mappedPredIds.first->second, mappedId); + idEdges.push_back(edge); + ++mappedPredIds.first; + } + } + else + { + selfRepeat = true; + } + } + + if (selfRepeat) + { + // skip all instances of self repeat so that only first node gets edges + // from predecessors + auto mappedIdsRange = mappedIds.equal_range(originalId); + mappedId += std::distance(mappedIdsRange.first, mappedIdsRange.second); + } + else + { + ++mappedId; + } + } + } + + static std::vector indexEdges(const IdEdges& idEdges, const std::map& originalIds) + { + std::vector index; + index.reserve(idEdges.size() + 1); + index.push_back(0); + if (!idEdges.size()) + { + // no edges. Single-node graph, just close the index + index.push_back(0); + } + else + { + MappedId lastId = -1; + for (const IdEdge& edge : idEdges) + { + while (lastId != edge.second) + { + index.push_back(index.back()); + ++lastId; + } + ++index.back(); + } + // add nodes without predecessors to index + while (lastId != MappedId(originalIds.rbegin()->first)) + { + index.push_back(index.back()); + ++lastId; + } + } + + return index; + } + + template + static std::string buildTargetSequence( + const GraphT& graph, const std::size_t startNodeOffset, const std::vector& nodeIds, + const std::map& originalIds, const std::map& nodeStartSeqOffset, + const std::vector& idEdgesIndex, const IdEdges& idEdges, std::vector& edges) + { + std::string target; + std::vector mappedIdEndOffset(nodeIds.size(), 0); + // when first node is a repeat expansion fully consumed by seed, just pretend that query start + // at the beginning of the node rather than after the end... + std::size_t startOffset = startNodeOffset; + for (const MappedId mappedId : nodeIds) + { + const NodeId originalId = originalIds.at(mappedId); + const std::string& nodeSeq = graph.nodeSeq(originalId); + if (!startOffset || -1 != nodeStartSeqOffset.at(originalId)) + { + const std::size_t nodePartLen = nodeSeq.length() - startOffset; + if (!nodePartLen) + { + throw std::logic_error("Empty node in expanded subgraph and it is not the first one"); + } + + for (std::size_t predOffset = idEdgesIndex[mappedId]; idEdgesIndex[mappedId + 1] != predOffset; + ++predOffset) + { + if (idEdges[predOffset].second != mappedId) + { + throw std::logic_error("bfsDiscoverEdges: Invalid edge"); + } + Edge edge(mappedIdEndOffset.at(idEdges[predOffset].first), target.length()); + edges.push_back(edge); + } + target += nodeSeq.substr(startOffset, nodePartLen); + } + mappedIdEndOffset[mappedId] = target.length() - 1; + startOffset = 0; + } + + return target; + } + + /** + * \param seqLen extensionLen + offset in the first node. This simplifies + * the process + * by assuming that sequence starts at the node start + */ + template + static void bfsDiscoverEdges( + const GraphT& graph, const NodeId startNodeId, const std::size_t startNodeOffset, const std::size_t seqLen, + std::vector& nodeIds, std::vector& edges, std::string& target, + std::map& originalIds) + { + // length of shortest path to the first node character + const std::map nodeStartSeqOffset = extractSubgraph(graph, startNodeId, startNodeOffset, seqLen); + + // Repeat expansions need to be unrolled, so we create unique id for each unrolled instance and map + // them to original ids + std::multimap mappedIds; + const IdEdges idEdges + = unrollRepeats(graph, startNodeOffset + seqLen, nodeStartSeqOffset, originalIds, mappedIds); + + // index the edge array so that for each mappedId there is a pair of entries index[mappedId], + // index[mappedId+1] which contains offsets of all predecessor edges in the idEdges; + const std::vector idEdgesIndex = indexEdges(idEdges, originalIds); + + nodeIds = extractOrderedNodeIds(idEdges, idEdgesIndex); + if (nodeIds.size() != mappedIds.size()) + { + throw std::logic_error( + "Invalid number of nodeIds entries " + std::to_string(nodeIds.size()) + " expected " + + std::to_string(mappedIds.size())); + } + + if (originalIds.at(nodeIds.at(0)) != startNodeId) + { + // nodeIds must be topologically sorted. Since we're discovering our subgraph from the + // start node, its first expansion must sort to the top. + throw std::logic_error("First expansion of start node must be the first node"); + } + + // extract target sequence in the proper order + target = buildTargetSequence( + graph, startNodeOffset, nodeIds, originalIds, nodeStartSeqOffset, idEdgesIndex, idEdges, edges); + + if (-1 == nodeStartSeqOffset.at(startNodeId)) + { + // if the first node is an empty repeat expansion (consumed by seed), remove it as it has no + // corresponding sequence in the target. + nodeIds.erase(nodeIds.begin()); + } + } +}; +} diff --git a/thirdparty/graph-tools-master/include/graphalign/PinnedPathAligner.hh b/thirdparty/graph-tools-master/include/graphalign/PinnedPathAligner.hh new file mode 100755 index 0000000..441f0e2 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/PinnedPathAligner.hh @@ -0,0 +1,125 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include + +#include "graphalign/GraphAlignment.hh" +#include "graphalign/LinearAlignmentOperations.hh" +#include "graphalign/PinnedAligner.hh" +#include "graphcore/PathOperations.hh" + +namespace graphtools +{ + +using PathAndAlignment = std::pair; + +class PinnedPathAligner +{ + const int32_t matchScore_; + const int32_t mismatchScore_; + const int32_t gapOpenScore_; + + mutable PinnedAligner pinnedAligner_; + +public: + PinnedPathAligner(int32_t matchScore = 5, int32_t mismatchScore = -4, int32_t gapOpenScore = -8) + : matchScore_(matchScore) + , mismatchScore_(mismatchScore) + , gapOpenScore_(gapOpenScore) + , pinnedAligner_(matchScore_, mismatchScore_, gapOpenScore_) + { + } + std::list + suffixAlign(const Path& seed_path, const std::string& query_piece, size_t extension_len, int& score) const; + std::list + prefixAlign(const Path& seed_path, const std::string& query_piece, size_t extension_len, int& score) const; + +private: + int32_t scoreAlignment(const Alignment& alignment) const + { + return graphtools::scoreAlignment(alignment, matchScore_, mismatchScore_, gapOpenScore_); + } +}; + +inline std::list PinnedPathAligner::suffixAlign( + const Path& seed_path, const std::string& query_piece, size_t extension_len, int& top_alignment_score) const +{ + std::list top_paths_and_alignments; + top_alignment_score = INT32_MIN; + + const std::list path_extensions = extendPathStart(seed_path, extension_len); + for (const auto& path : path_extensions) + { + Alignment alignment = pinnedAligner_.suffixAlign(path.seq(), query_piece); + const int32_t alignment_score = scoreAlignment(alignment); + + if (top_alignment_score < alignment_score) + { + top_paths_and_alignments.clear(); + top_alignment_score = alignment_score; + } + + if (top_alignment_score == alignment_score) + { + top_paths_and_alignments.push_back(std::make_pair(path, alignment)); + } + } + + return top_paths_and_alignments; +} + +inline std::list PinnedPathAligner::prefixAlign( + const Path& seed_path, const std::string& query_piece, size_t extension_len, int& top_alignment_score) const +{ + std::list top_paths_and_alignments; + top_alignment_score = INT32_MIN; + + const std::list path_extensions = extendPathEnd(seed_path, extension_len); + for (const auto& path : path_extensions) + { + Alignment alignment = pinnedAligner_.prefixAlign(path.seq(), query_piece); + const int32_t alignment_score = scoreAlignment(alignment); + + if (top_alignment_score < alignment_score) + { + top_paths_and_alignments.clear(); + top_alignment_score = alignment_score; + } + + if (top_alignment_score == alignment_score) + { + top_paths_and_alignments.push_back(std::make_pair(path, alignment)); + } + } + + return top_paths_and_alignments; +} +} diff --git a/thirdparty/graph-tools-master/include/graphalign/TracebackMatrix.hh b/thirdparty/graph-tools-master/include/graphalign/TracebackMatrix.hh new file mode 100755 index 0000000..3377f83 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/TracebackMatrix.hh @@ -0,0 +1,112 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include + +namespace graphtools +{ +enum class TracebackStep +{ + kStop, + kTop, + kLeft, + kDiagonalMatch, + kDiagonalMismatch +}; + +std::ostream& operator<<(std::ostream& out, const TracebackStep& trace_dir); + +struct TracebackMatrixCell +{ + TracebackMatrixCell() + : direction(TracebackStep::kStop) + , score(INT32_MIN) + { + } + TracebackMatrixCell(TracebackStep new_direction, int32_t new_score) + : direction(new_direction) + , score(new_score) + { + } + bool operator==(const TracebackMatrixCell& other) const + { + return direction == other.direction && score == other.score; + } + TracebackStep direction; + int32_t score; +}; + +std::ostream& operator<<(std::ostream& out, const TracebackMatrixCell& cell); + +/** + * Implementation of traceback matrix abstraction; each cell of the matrix contains traceback direction and alignment + * score + */ +class TracebackMatrix +{ +public: + TracebackMatrix(size_t num_rows, size_t num_cols) + : num_rows_(num_rows) + , num_cols_(num_cols) + { + cells_.resize(num_rows_ * num_cols_); + }; + explicit TracebackMatrix(const std::string& encoding); + + size_t numRows() const { return num_rows_; } + size_t numCols() const { return num_cols_; } + int32_t score(size_t row_index, size_t col_index) const; + void setScore(size_t row_index, size_t col_index, int32_t score); + TracebackStep tracebackStep(size_t row_index, size_t col_index) const; + void setTracebackStep(size_t row_index, size_t col_index, TracebackStep direction); + + void locateTopScoringCell(size_t& top_row_index, size_t& top_col_index) const; + + bool operator==(const TracebackMatrix& other) const; + +private: + inline const TracebackMatrixCell& getCell(size_t row_index, size_t col_index) const + { + return cells_[row_index * num_cols_ + col_index]; + } + inline TracebackMatrixCell& getCell(size_t row_index, size_t col_index) + { + return cells_[row_index * num_cols_ + col_index]; + } + + size_t num_rows_; + size_t num_cols_; + std::vector cells_; +}; + +std::ostream& operator<<(std::ostream& out, const TracebackMatrix& matrix); +} diff --git a/thirdparty/graph-tools-master/include/graphalign/TracebackRunner.hh b/thirdparty/graph-tools-master/include/graphalign/TracebackRunner.hh new file mode 100755 index 0000000..1f291cf --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/TracebackRunner.hh @@ -0,0 +1,68 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include + +#include "graphalign/LinearAlignment.hh" +#include "graphalign/TracebackMatrix.hh" + +namespace graphtools +{ +/** + * Implements traceback of dynamic programming matrices generated by local and global alignment algorithms + */ +class TracebackRunner +{ +public: + explicit TracebackRunner(const TracebackMatrix& matrix) + : matrix_(matrix) + { + } + + // Performs traceback starting from a given cell of the matrix + Alignment runTraceback(size_t row_index, size_t col_index); + +private: + void computeTracebackRunForAlignmentOperation(size_t row_index, size_t col_index); + void tracebackPosition(size_t& row_index, size_t& col_index); + void convertCurrentRunToAlignmentOperation(); + void softclipQuerySuffix(size_t& row_index); + void softclipQueryPrefix(size_t& row_index); + + const TracebackMatrix& matrix_; + + std::list operations_; + TracebackStep run_traceback_step; + size_t run_length = 0; + size_t run_last_row_index = 0; + size_t run_last_col_index = 0; +}; +} diff --git a/thirdparty/graph-tools-master/include/graphalign/dagAligner/AffineAlignMatrix.hh b/thirdparty/graph-tools-master/include/graphalign/dagAligner/AffineAlignMatrix.hh new file mode 100755 index 0000000..11fbacf --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/dagAligner/AffineAlignMatrix.hh @@ -0,0 +1,278 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Roman Petrovski +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include + +#include "Details.hh" + +namespace graphalign +{ + +namespace dagAligner +{ + + // the 2-d table of scores filled during the alignment + template class AffineAlignMatrix + { + public: + typedef PenaltyMatrixT PenaltyMatrix; + + private: + const PenaltyMatrix penaltyMatrix_; + const Score gapOpen_; + const Score gapExt_; + + AlignMatrix v_; + AlignMatrix g_; + AlignMatrix f_; + AlignMatrix e_; + + std::vector query_; + std::vector target_; + + public: + AffineAlignMatrix(const PenaltyMatrix& penaltyMatrix, Score gapOpen, Score gapExt) + : penaltyMatrix_(penaltyMatrix) + , gapOpen_(gapOpen) + , gapExt_(gapExt) + { + } + + template + void + init(QueryIt queryBegin, QueryIt queryEnd, TargetIt targetBegin, TargetIt targetEnd, const EdgeMap& edgeMap) + { + if (queryEnd == queryBegin) + { + throw std::logic_error("Empty query is not allowed."); + } + + if (targetEnd == targetBegin) + { + throw std::logic_error("Empty target is not allowed."); + } + + query_.clear(); + penaltyMatrix_.translateQuery(queryBegin, queryEnd, std::back_inserter(query_)); + target_.clear(); + penaltyMatrix_.translateTarget(targetBegin, targetEnd, std::back_inserter(target_)); + + reset(edgeMap); + + fill(edgeMap); + } + + typedef AlignMatrix::const_iterator const_iterator; + template const_iterator nextBestAlign(const_iterator start, Score& bestScore) const + { + return !localAlign ? v_.nextBestAlign(start, queryLen() - 1, bestScore) + : v_.nextBestAlign(start, bestScore); + } + + const_iterator alignBegin() const { return v_.cellOneOne(); } + const_iterator alignEnd() const { return v_.end(); } + int targetOffset(const_iterator cell) const { return std::distance(v_.cellOneOne(), cell) / v_.paddedRowLen(); } + int queryOffset(const_iterator cell) const { return std::distance(v_.cellOneOne(), cell) % v_.paddedRowLen(); } + int queryLen() const { return query_.size(); } + + bool isInsertion(int q, int t) const + { + const Score insExtScore = v_.at(q, t) - f_.at(q - 1, t); + const Score insOpenScore = v_.at(q, t) - v_.at(q - 1, t); + return gapExt_ == insExtScore || gapOpen_ + gapExt_ == insOpenScore; + } + + bool isDeletion(int q, int t, int p) const + { + const Score delExtScore = v_.at(q, t) - e_.at(q, p); + const Score delOpenScore = v_.at(q, t) - v_.at(q, p); + return gapExt_ == delExtScore || gapOpen_ + gapExt_ == delOpenScore; + } + + bool isMatch(int q, int t, int p) const + { + typename PenaltyMatrix::QueryChar queryChar = query_[q]; + typename PenaltyMatrix::TargetChar targetChar = target_[t]; + const Score alnScore = v_.at(q, t) - v_.at(q - 1, p); + return penaltyMatrix_.isMatch(queryChar, targetChar) && penaltyMatrix_(queryChar, targetChar) == alnScore; + } + + bool isMismatch(int q, int t, int p) const + { + typename PenaltyMatrix::QueryChar queryChar = query_[q]; + typename PenaltyMatrix::TargetChar targetChar = target_[t]; + const Score alnScore = v_.at(q, t) - v_.at(q - 1, p); + return !penaltyMatrix_.isMatch(queryChar, targetChar) && penaltyMatrix_(queryChar, targetChar) == alnScore; + } + + private: + void computeAlignPenalties(int q, typename PenaltyMatrix::TargetChar tc, Score penalties[16]) + { + const typename PenaltyMatrix::QueryChar* query = &query_[q]; + for (int i = 0; i < 16; ++i) + { + penalties[i] = penaltyMatrix_(query[i], tc); + } + } + + void reset(const EdgeMap& edgeMap) + { + const int qLen = query_.size(); + const int tLen = target_.size(); + + v_.reset(qLen, tLen); + g_.reset(qLen, tLen); + f_.reset(qLen, tLen); + e_.reset(qLen, tLen); + + // top left must be 0 and never change + if (v_.at(-1, -1)) + { + throw std::logic_error("Incorrectly initialized v_"); + } + if (g_.at(-1, -1)) + { + throw std::logic_error("Incorrectly initialized g_"); + } + if (f_.at(-1, -1)) + { + throw std::logic_error("Incorrectly initialized f_"); + } + if (e_.at(-1, -1)) + { + throw std::logic_error("Incorrectly initialized e_"); + } + + // first column penalises for deletion + for (int t = 0; t < tLen; ++t) + { + if (penalizeMove) + { + for (EdgeMap::OffsetEdges::const_iterator prevNodeIndexIt = edgeMap.prevNodesBegin(t); + prevNodeIndexIt != edgeMap.prevNodesEnd(t); ++prevNodeIndexIt) + { + const int p = *prevNodeIndexIt; + v_.at(-1, t) = std::max(v_.at(-1, t), Score(v_.at(-1, p) + gapOpen_ + gapExt_)); + f_.at(-1, t) = std::max(v_.at(-1, t), Score(f_.at(-1, p) + gapOpen_ + gapExt_)); + } + } + else + { + v_.at(-1, t) = 0; + f_.at(-1, t) = 0; + } + } + + // first row penalises for insertion + for (int q = 0; q < qLen; ++q) + { + v_.at(q, -1) = v_.at(q - 1, -1) + gapOpen_ + gapExt_; + e_.at(q, -1) = e_.at(q - 1, -1) + gapOpen_ + gapExt_; + } + } + + void fill(const EdgeMap& edgeMap) + { + const int qLen = query_.size(); + const int tLen = target_.size(); + + for (int t = 0; t < tLen; ++t) + { + for (EdgeMap::OffsetEdges::const_iterator prevNodeIndexIt = edgeMap.prevNodesBegin(t); + edgeMap.prevNodesEnd(t) != prevNodeIndexIt; ++prevNodeIndexIt) + { + const int p = *prevNodeIndexIt; + recomputeForDeletion(qLen, t, p); + recomputeForAlign(qLen, t, p); + } + + consolidate(qLen, t); + recomputeForInsertion(qLen, t); + } + } + + void recomputeForDeletion(int qLen, int t, int p) + { + Score* ep = e_.row(0, p); + Score* v = v_.row(0, p); + Score* et = e_.row(0, t); + for (int i = 0; i < qLen; ++i) + { + et[i] = std::max(et[i], std::max(ep[i] + gapExt_, v[i] + gapOpen_ + gapExt_)); + } + } + + void recomputeForAlign(int qLen, int t, int p) + { + const typename PenaltyMatrix::QueryChar* query = &query_[0]; + + Score* g = g_.row(0, t); + Score* v = v_.row(-1, p); + for (int i = 0; i < qLen; ++i) + { + g[i] = std::max(g[i], v[i] + penaltyMatrix_(query[i], target_[t])); + } + } + + void consolidate(int qLen, int t) + { + Score* e = e_.row(0, t); + Score* g = g_.row(0, t); + Score* v = v_.row(0, t); + for (int i = 0; i < qLen; ++i) + { + v[i] = std::max(v[i], std::max(g[i], e[i])); + } + } + + void recomputeForInsertion(int qLen, int t) + { + Score* v = v_.row(0, t); + Score* f = f_.row(0, t); + Score* fp = f_.row(-1, t); + Score* vp = v_.row(-1, t); + + for (int i = 0; i < qLen; ++i) + { + f[i] = std::max(f[i], std::max((fp[i] + gapExt_), (vp[i] + gapOpen_ + gapExt_))); + v[i] = std::max(v[i], f[i]); + } + } + + friend std::ostream& operator<<(std::ostream& os, const AffineAlignMatrix& matrix) + { + return os << "AffineAlignMatrix(" << matrix.v_ << ")"; + } + }; + +} // namespace dagAligner + +} // namespace graphalign diff --git a/thirdparty/graph-tools-master/include/graphalign/dagAligner/AffineAlignMatrixVectorized.hh b/thirdparty/graph-tools-master/include/graphalign/dagAligner/AffineAlignMatrixVectorized.hh new file mode 100755 index 0000000..7309910 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/dagAligner/AffineAlignMatrixVectorized.hh @@ -0,0 +1,333 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Roman Petrovski +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include + +#include "Details.hh" + +namespace graphalign +{ + +namespace dagAligner +{ + + // the 2-d table of scores filled during the alignment + template class AffineAlignMatrixVectorized + { + public: + typedef PenaltyMatrixT PenaltyMatrix; + + private: + const PenaltyMatrix penaltyMatrix_; + const Score gapOpen_; + const Score gapExt_; + + PaddedAlignMatrix v_; + PaddedAlignMatrix g_; + PaddedAlignMatrix f_; + PaddedAlignMatrix e_; + + std::vector query_; + std::vector target_; + std::vector alignmentPenalties_[PenaltyMatrix::TARGET_CHAR_MAX_ + 1]; + + public: + AffineAlignMatrixVectorized(const PenaltyMatrix& penaltyMatrix, Score gapOpen, Score gapExt) + : penaltyMatrix_(penaltyMatrix) + , gapOpen_(gapOpen) + , gapExt_(gapExt) + { + } + + template + void + init(QueryIt queryBegin, QueryIt queryEnd, TargetIt targetBegin, TargetIt targetEnd, const EdgeMap& edgeMap) + { + if (queryEnd == queryBegin) + { + throw std::logic_error("Empty query is not allowed."); + } + + if (targetEnd == targetBegin) + { + throw std::logic_error("Empty target is not allowed."); + } + + // avoid "uninitialized read" complaints from valgrind + query_.resize((std::distance(queryBegin, queryEnd) + step - 1) / step * step); + query_.clear(); + penaltyMatrix_.translateQuery(queryBegin, queryEnd, std::back_inserter(query_)); + target_.clear(); + penaltyMatrix_.translateTarget(targetBegin, targetEnd, std::back_inserter(target_)); + + reset(edgeMap); + + fill(edgeMap); + } + + typedef AlignMatrix::const_iterator const_iterator; + template const_iterator nextBestAlign(const_iterator start, Score& bestScore) const + { + return !localAlign ? v_.nextBestAlign(start, queryLen() - 1, bestScore) + : v_.nextBestAlign(start, bestScore); + } + const_iterator alignBegin() const { return v_.cellOneOne(); } + const_iterator alignEnd() const { return v_.end(); } + int targetOffset(const_iterator cell) const { return std::distance(v_.cellOneOne(), cell) / v_.paddedRowLen(); } + int queryOffset(const_iterator cell) const { return std::distance(v_.cellOneOne(), cell) % v_.paddedRowLen(); } + int queryLen() const { return query_.size(); } + + bool isInsertion(int q, int t) const + { + const Score insExtScore = v_.at(q, t) - f_.at(q - 1, t); + const Score insOpenScore = v_.at(q, t) - v_.at(q - 1, t); + return gapExt_ == insExtScore || gapOpen_ + gapExt_ == insOpenScore; + } + + bool isDeletion(int q, int t, int p) const + { + const Score delExtScore = v_.at(q, t) - e_.at(q, p); + const Score delOpenScore = v_.at(q, t) - v_.at(q, p); + return gapExt_ == delExtScore || gapOpen_ + gapExt_ == delOpenScore; + } + + bool isMatch(int q, int t, int p) const + { + typename PenaltyMatrix::QueryChar queryChar = query_[q]; + typename PenaltyMatrix::TargetChar targetChar = target_[t]; + const Score alnScore = v_.at(q, t) - v_.at(q - 1, p); + return penaltyMatrix_.isMatch(queryChar, targetChar) && penaltyMatrix_(queryChar, targetChar) == alnScore; + } + + bool isMismatch(int q, int t, int p) const + { + typename PenaltyMatrix::QueryChar queryChar = query_[q]; + typename PenaltyMatrix::TargetChar targetChar = target_[t]; + const Score alnScore = v_.at(q, t) - v_.at(q - 1, p); + return !penaltyMatrix_.isMatch(queryChar, targetChar) && penaltyMatrix_(queryChar, targetChar) == alnScore; + } + + private: + // __attribute((noinline)) + void reset(const EdgeMap& edgeMap) + { + const int qLen = query_.size(); + const int tLen = target_.size(); + + for (typename PenaltyMatrix::TargetChar tc = 0; tc <= PenaltyMatrix::TARGET_CHAR_MAX_; ++tc) + { + alignmentPenalties_[tc].resize((qLen + step - 1) / step * step, 0); + for (int q = 0; q < qLen; q += step) + { + computeAlignPenalties(q, tc, &alignmentPenalties_[tc].front() + q); + } + } + + v_.reset(qLen, tLen); + g_.reset(qLen, tLen); + f_.reset(qLen, tLen); + e_.reset(qLen, tLen); + + // top left must be 0 and never change + if (v_.at(-1, -1)) + { + throw std::logic_error("Incorrectly initialized v_"); + } + if (g_.at(-1, -1)) + { + throw std::logic_error("Incorrectly initialized g_"); + } + if (f_.at(-1, -1)) + { + throw std::logic_error("Incorrectly initialized f_"); + } + if (e_.at(-1, -1)) + { + throw std::logic_error("Incorrectly initialized e_"); + } + + // first column penalises for deletion + for (int t = 0; t < tLen; ++t) + { + if (penalizeMove) + { + for (EdgeMap::OffsetEdges::const_iterator prevNodeIndexIt = edgeMap.prevNodesBegin(t); + prevNodeIndexIt != edgeMap.prevNodesEnd(t); ++prevNodeIndexIt) + { + const int p = *prevNodeIndexIt; + v_.at(-1, t) = std::max(v_.at(-1, t), Score(v_.at(-1, p) + gapOpen_ + gapExt_)); + f_.at(-1, t) = std::max(v_.at(-1, t), Score(f_.at(-1, p) + gapOpen_ + gapExt_)); + } + } + else + { + v_.at(-1, t) = 0; + f_.at(-1, t) = 0; + } + } + + // first row penalizes for insertion + for (int q = 0; q < queryLen(); ++q) + { + v_.at(q, -1) = v_.at(q - 1, -1) + gapOpen_ + gapExt_; + e_.at(q, -1) = e_.at(q - 1, -1) + gapOpen_ + gapExt_; + } + } + + // __attribute((noinline)) + void fill(const EdgeMap& edgeMap) + { + const int qLen = query_.size(); + const int tLen = target_.size(); + + for (int t = 0; t < tLen; ++t) + { + const typename PenaltyMatrix::TargetChar tc = target_[t]; + const Score* const penalties = &alignmentPenalties_[tc].front(); + for (EdgeMap::OffsetEdges::const_iterator prevNodeIndexIt = edgeMap.prevNodesBegin(t); + edgeMap.prevNodesEnd(t) != prevNodeIndexIt; ++prevNodeIndexIt) + { + const int p = *prevNodeIndexIt; + for (int q = 0; q < qLen; q += step) + { + recomputeForDeletion(q, t, p); + recomputeForAlign(q, t, p, penalties + q); + } + } + + for (int q = 0; q < qLen; q += step) + { + consolidate(q, t); + } + recomputeForInsertion(qLen, t); + } + } + + // __attribute((noinline)) + void computeAlignPenalties(int q, typename PenaltyMatrix::TargetChar tc, Score penalties[step]) + { + const typename PenaltyMatrix::QueryChar* query = &query_[q]; + for (int i = 0; i < step; ++i) + { + penalties[i] = penaltyMatrix_(query[i], tc); + } + } + + // __attribute((noinline)) + void recomputeForAlign(int q, int t, int p, const Score penalties[step]) + { + Score tmp[step]; + Score* v = v_.row(q - 1, p); + for (int i = 0; i < step; ++i) + { + tmp[i] = v[i] + penalties[i]; + } + + Score* g = g_.row(q, t); + for (int i = 0; i < step; ++i) + { + g[i] = g[i] > tmp[i] ? g[i] : tmp[i]; + } + } + + // __attribute((noinline)) + void recomputeForDeletion(int q, int t, int p) + { + Score* ep = e_.row(q, p); + Score tmpEp[step]; + for (int i = 0; i < step; ++i) + { + tmpEp[i] = ep[i] + gapExt_; + } + + Score* vp = v_.row(q, p); + Score tmpVp[step]; + for (int i = 0; i < step; ++i) + { + tmpVp[i] = vp[i] + gapOpen_ + gapExt_; + } + + Score tmp[step]; + for (int i = 0; i < step; ++i) + { + tmp[i] = tmpEp[i] > tmpVp[i] ? tmpEp[i] : tmpVp[i]; + } + + Score* e = e_.row(q, t); + for (int i = 0; i < step; ++i) + { + e[i] = e[i] > tmp[i] ? e[i] : tmp[i]; + } + } + + // __attribute((noinline)) + void recomputeForInsertion(int qLen, int t) + { + Score* v = v_.row(0, t); + Score* f = f_.row(0, t); + Score* fp = f_.row(-1, t); + Score* vp = v_.row(-1, t); + + // cannot be vectorized since insertion is computed horisontally + for (int i = 0; i < qLen; ++i) + { + f[i] = std::max(f[i], std::max((fp[i] + gapExt_), (vp[i] + gapOpen_ + gapExt_))); + v[i] = std::max(v[i], f[i]); + } + } + + // __attribute((noinline)) + void consolidate(int q, int t) + { + Score tmp[step]; + Score* e = e_.row(q, t); + Score* g = g_.row(q, t); + for (int i = 0; i < step; ++i) + { + tmp[i] = g[i] > e[i] ? g[i] : e[i]; + } + + Score* v = v_.row(q, t); + for (int i = 0; i < step; ++i) + { + v[i] = v[i] > tmp[i] ? v[i] : tmp[i]; + } + } + + friend std::ostream& operator<<(std::ostream& os, const AffineAlignMatrixVectorized& matrix) + { + return os << "AffineAlignMatrix(" << matrix.v_ << ")"; + } + }; + +} // namespace dagAligner + +} // namespace graphalign diff --git a/thirdparty/graph-tools-master/include/graphalign/dagAligner/BaseMatchingPenaltyMatrix.hh b/thirdparty/graph-tools-master/include/graphalign/dagAligner/BaseMatchingPenaltyMatrix.hh new file mode 100755 index 0000000..a9a7eac --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/dagAligner/BaseMatchingPenaltyMatrix.hh @@ -0,0 +1,127 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Roman Petrovski +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include + +#include "graphalign/dagAligner/Details.hh" +#include "graphutils/BaseMatching.hh" + +namespace graphalign +{ + +namespace dagAligner +{ + + class BaseMatchingPenaltyMatrix + { + typedef graphtools::codes::BaseCode Oligo; + typedef graphalign::dagAligner::Score Score; + + public: + typedef Oligo QueryChar; + typedef Oligo TargetChar; + + // test constructor, since this is a free-form penalty matrix, the one + // that accepts an actual matrix is expected + BaseMatchingPenaltyMatrix(const Score match = 2, const Score mismatch = -2) + { + int row = 0; + for (const auto& r : graphtools::codes::kReferenceQueryCodeMatchLookupTable) + { + int col = 0; + for (const auto& c : r) + { + matrix_[row][col] = c ? match : mismatch; + ++col; + } + ++row; + } + } + + Score operator()(QueryChar q, TargetChar t) const + { + if (t >= ROWS_) + { + throw std::logic_error("Invalid row request from BaseMatchingPenaltyMatrix"); + } + if (q >= COLUMNS_) + { + throw std::logic_error("Invalid column request from BaseMatchingPenaltyMatrix"); + } + + return matrix_[t][q]; + } + + bool isMatch(QueryChar q, TargetChar t) const + { + return graphtools::codes::kReferenceQueryCodeMatchLookupTable[t][q]; + } + + template + static void translateTarget(InputIterator targetBegin, InputIterator targetEnd, OutputIterator output) + { + std::transform(targetBegin, targetEnd, output, [](unsigned char tc) { + return graphtools::codes::kReferenceBaseEncodingTable[tc]; + }); + } + + template + static void translateQuery(InputIterator queryBegin, InputIterator queryEnd, OutputIterator output) + { + std::transform(queryBegin, queryEnd, output, [](unsigned char tc) { + return graphtools::codes::kQueryBaseEncodingTable[tc]; + }); + } + + friend std::ostream& operator<<(std::ostream& os, const BaseMatchingPenaltyMatrix& matrix) + { + os << "BaseMatchingPenaltyMatrix(\n"; + for (int r = 0; r < ROWS_; ++r) + { + for (int c = 0; c < COLUMNS_; ++c) + { + os << (c ? "\t" : "[") << int(matrix.matrix_[r][c]); + } + os << "]\n"; + } + return os << ")"; + } + + static const int ROWS_ = graphtools::codes::kMaxReferenceBaseCode + 1; + static const int COLUMNS_ = graphtools::codes::kMaxQueryBaseCode + 1; + static const Oligo TARGET_CHAR_MAX_ = graphtools::codes::kMaxReferenceBaseCode; + + private: + graphalign::dagAligner::Score matrix_[ROWS_][COLUMNS_]; + }; + +} // namespace dagAligner + +} // namespace graphalign diff --git a/thirdparty/graph-tools-master/include/graphalign/dagAligner/Details.hh b/thirdparty/graph-tools-master/include/graphalign/dagAligner/Details.hh new file mode 100755 index 0000000..3183985 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/dagAligner/Details.hh @@ -0,0 +1,506 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Roman Petrovski +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include + +#include +#include +#include +#include + +#include +#include + +// avoid boost lambda issues with boost::adaptors::transformed on boost 1.53 +#define BOOST_RESULT_OF_USE_DECLTYPE + +#include + +namespace graphalign +{ + +namespace dagAligner +{ + + typedef signed short Score; + static const Score SCORE_MIN = std::numeric_limits::min(); + + /** + * \brief Contains information about graph edges between the target sequence characters + */ + class EdgeMap + { + public: + typedef int NodeId; + typedef std::vector OffsetNodeIds; + typedef std::vector OffsetEdges; + + private: + // for target character, an id of the node to which the character belongs + OffsetNodeIds offsetNodeIds_; + + // [index_[t], index_[t+1]) contains range in prevOffsets_ that point to base preceding t in the target graph + std::vector index_; + + OffsetEdges prevOffsets_; + + public: + EdgeMap( + const OffsetNodeIds& offsetNodeIds, const std::vector& index, + const std::vector& prevOffsets) + : offsetNodeIds_(offsetNodeIds) + , index_(index) + , prevOffsets_(prevOffsets) + { + if (index.back() != prevOffsets_.size()) + { + throw std::out_of_range("index.back() != edges.size()"); + } + } + + /** + * \brief construct EdgeMap from list of unique node identifiers and edges as pairs of offsets + * \param edges offset pairs in form of {from,to} describing the connectivity in the graph + * \param nodeIds unique identifier of nodes in the same order as offsets appear in the edges + * Edges have to be sorted by 'to' position and cannot create cycles ('from' < 'to') + * Edges starting from offset -1 are ways to enter the graph. An edge from {-1,0} is implied, + * i.e. alignments can always start at position 0. + * For a (graph) sequence of length n, a dummy sequence of form {n,n} must be present as a marker + * of sequence length. + */ + EdgeMap(const std::vector>& edges, const std::vector& nodeIds) + { + typedef std::vector> Edges; + if (edges.back().second != edges.back().first) + { + throw std::invalid_argument( + "last pair of offsets must point to itself and last character in the graph"); + } + std::vector::const_iterator nodeIdIt = nodeIds.begin(); + + index_.push_back(0); + // root node offset is -1 + prevOffsets_.push_back(-1); + index_.push_back(prevOffsets_.size()); + + Edges::const_iterator edge = edges.begin(); + if (!edges.front().second) + { + if (-1 == edges.front().first) + { + // first node edge from -1 is optional + ++edge; + } + } + + for (; edges.end() != edge;) + { + // fill regular offsets to previous character within the node + while (int(offsetNodeIds_.size()) < edge->second - 1) + { + prevOffsets_.push_back(int(offsetNodeIds_.size())); + index_.push_back(prevOffsets_.size()); + offsetNodeIds_.push_back(*nodeIdIt); + } + + offsetNodeIds_.push_back(*nodeIdIt); + + // insert graph edges, all incoming edges for the same target offset + const int lastEdge = edge->second; + do + { + prevOffsets_.push_back(edge->first); + ++edge; + } while (edges.end() != edge && lastEdge == edge->second); + + index_.push_back(prevOffsets_.size()); + + // Now we're on a new node + ++nodeIdIt; + } + // remove closing offset reference to itself + prevOffsets_.pop_back(); + index_.pop_back(); + } + + /* + * \param t offset of character in target character sequence + * \return iterator to the first offset of the predecessor character. Could be on the same or a different node + */ + OffsetEdges::const_iterator prevNodesBegin(std::size_t t) const + { + assert(t < index_.size()); + return prevOffsets_.begin() + index_[t]; + } + + /* + * \param t offset of character in target character sequence + * \return iterator to the one after last offset of the predecessor character. Could be on the same or a + * different node + */ + OffsetEdges::const_iterator prevNodesEnd(std::size_t t) const + { + return prevOffsets_.begin() + index_.at(t + 1); + } + + std::size_t getNodeId(std::size_t t) const { return offsetNodeIds_.at(t); } + + friend std::ostream& operator<<(std::ostream& os, const EdgeMap& edgeMap) + { + return os << "EdgeMap(" + << "nodeIds(" + << boost::algorithm::join( + edgeMap.offsetNodeIds_ + | boost::adaptors::transformed([](std::size_t d) { return std::to_string(d); }), + ",") + << ")," + << "index(" + << boost::algorithm::join( + edgeMap.index_ + | boost::adaptors::transformed([](std::size_t d) { return std::to_string(d); }), + ",") + << ")," + << "prevOffsets(" + << boost::algorithm::join( + edgeMap.prevOffsets_ + | boost::adaptors::transformed([](int d) { return std::to_string(d); }), + ",") + << "))"; + } + }; + + // the 2-d table of scores filled during the alignment + template class PaddedAlignMatrix + { + std::size_t rowLen_; + typedef std::vector Matrix; + Matrix matrix_; + + public: + PaddedAlignMatrix() + : rowLen_(1) + , matrix_(rowLen_, 0) + { + } + + void reset(std::size_t qLen, std::size_t tLen) + { + // + 1 for gap row and gap column + rowLen_ = qLen + 1; + matrix_.resize(paddedRowLen() * (tLen + 1)); + std::fill(matrix_.begin() + 1, matrix_.end(), SCORE_MIN); + } + + int paddedRowLen() const { return 1 + (rowLen_ - 1 + pad - 1) / pad * pad; } + int rowLen() const { return rowLen_; } + Score at(int q, int t) const { return *(cellOneOne() + t * paddedRowLen() + q); } + Score& at(int q, int t) { return *(cellOneOne() + t * paddedRowLen() + q); } + Score* row(int q, int t) { return &(*(cellOneOne() + t * paddedRowLen() + q)); } + + typedef Matrix::iterator iterator; + iterator cellZeroZero() { return matrix_.begin(); } + iterator cellOneOne() { return matrix_.begin() + 1 + paddedRowLen(); } + iterator cellOneZero() { return matrix_.begin() + 1; } + iterator cellZeroOne() { return matrix_.begin() + paddedRowLen(); } + typedef Matrix::const_iterator const_iterator; + const_iterator cellOneOne() const { return matrix_.begin() + 1 + paddedRowLen(); } + const_iterator begin() const { return matrix_.begin(); } + const_iterator end() const { return matrix_.end() - paddedRowLen() + rowLen(); } + const_iterator last(int q) const { return matrix_.end() - paddedRowLen() + q + 1; } + // find best alignment after the line indicated by start iterator + const_iterator nextBestAlign(const_iterator start, int q, Score& bestScore) const + { + const_iterator rowStart = begin() + + (std::distance(begin(), start) + paddedRowLen() - 1 - q - 1) / paddedRowLen() * paddedRowLen(); + if (matrix_.end() == rowStart) + { + // current line is the end, no more good scores + return end(); + } + const_iterator rowEnd = rowStart + q + 1; + bestScore = *rowEnd; + const_iterator ret = rowEnd; + + while (last(q) != rowEnd) + { + rowEnd += paddedRowLen(); + if (bestScore < *rowEnd) + { + bestScore = *rowEnd; + ret = rowEnd; + } + } + return ret; + } + + const_iterator nextBestAlign(const_iterator start, Score& bestScore) const + { + const_iterator it = start; + if (end() == it) + { + // next line is the end, no more good scores + return end(); + } + if (!(std::distance(begin(), it) % paddedRowLen())) + { + ++it; + } + + if (rowLen() == (std::distance(begin(), it) % paddedRowLen())) + { + it += paddedRowLen() - rowLen() + 1; + } + const_iterator ret = it; + bestScore = *(it++); + + // + while (end() != it) + { + if (rowLen() == (std::distance(begin(), it) % paddedRowLen())) + { + it += paddedRowLen() - rowLen() + 1; + } + if (bestScore < *it) + { + bestScore = *it; + ret = it; + } + ++it; + } + return ret; + } + + int targetOffset(const_iterator cell) const { return std::distance(cellOneOne(), cell) / paddedRowLen(); } + int queryOffset(const_iterator cell) const { return std::distance(cellOneOne(), cell) % paddedRowLen(); } + + friend std::ostream& operator<<(std::ostream& os, const PaddedAlignMatrix& matrix) + { + os << "AlignMatrix(\n"; + for (const_iterator it = matrix.begin(); matrix.end() > it; it += matrix.paddedRowLen()) + { + for (std::size_t i = 0; i < matrix.rowLen_; ++i) + { + os << (i ? "\t" : "[") << int(*(it + i)); + } + os << "]\n"; + } + return os << ")"; + } + }; + + typedef PaddedAlignMatrix<1> AlignMatrix; + + class Cigar + { + public: + enum OpCode + { + ALIGN = 0, // 'M' + INSERT = 1, // 'I' + DELETE = 2, // 'D' + SKIP = 3, // 'N' Essentially same as 'D' but not treated as a deletion. + // Can be used for intron when aligning RNA sample against whole genome reference + SOFT_CLIP = 4, // 'S' + HARD_CLIP = 5, // 'H' + PAD = 6, // 'P' + MATCH = 7, // '=' + MISMATCH = 8, // 'X' + UNKNOWN, // '?' + NODE_START, // Non-standard. Indicates change of the node in graph cigar + NODE_END // Non-standard. Indicates change of the node in graph cigar + }; + + struct Operation + { + typedef std::size_t ValueType; + Operation(OpCode code, ValueType value) + : code_(code) + , value_(value) + { + } + OpCode code_; + ValueType value_; // normally the operation length, but for NODE contains nodeId + + char getCharCode(const char matchChar = '=', const char mismatchChar = 'X') const + { + const std::vector CIGAR_CHARS + = { 'M', 'I', 'D', 'N', 'S', 'H', 'P', matchChar, mismatchChar, '?' }; + if (CIGAR_CHARS.size() > code_) + { + return CIGAR_CHARS[code_]; + } + throw std::out_of_range(std::string("invalid code '") + std::to_string(code_) + "'"); + } + + bool operator<(const Operation& that) const + { + return code_ < that.code_ || (code_ == that.code_ && value_ < that.value_); + } + + bool operator==(const Operation& that) const { return code_ == that.code_ && value_ == that.value_; } + }; + + typedef std::vector Operations; + + typedef Operations::iterator iterator; + iterator begin() { return cigar_.begin(); } + iterator end() { return cigar_.end(); } + + typedef Operations::const_iterator const_iterator; + const_iterator begin() const { return cigar_.begin(); } + const_iterator end() const { return cigar_.end(); } + void push_back(const Operation& op) { cigar_.push_back(op); } + Operation pop_back() + { + const Operation ret = cigar_.back(); + cigar_.pop_back(); + return ret; + } + iterator erase(const_iterator from, const_iterator to) { return cigar_.erase(from, to); } + + template void insert(const_iterator before, IteratorT b, IteratorT e) + { + cigar_.insert(before, b, e); + } + + std::size_t firstNode() const + { + if (Cigar::NODE_START != cigar_.front().code_) + { + throw std::logic_error("First CIGAR op is expected to be a node start"); + } + return cigar_.front().value_; + } + + bool empty() const { return cigar_.empty(); } + std::size_t length() const { return cigar_.size(); } + + OpCode lastOp() const { return cigar_.back().code_; } + OpCode& lastOp() { return cigar_.back().code_; } + std::size_t& lastValue() { return cigar_.back().value_; } + void append(OpCode op) + { + if (lastOp() != op) + { + push_back(Cigar::Operation(op, 1)); + } + else + { + ++lastValue(); + } + } + + Cigar operator+(OpCode op) const + { + Cigar ret = *this; + ret.append(op); + return ret; + } + + void reverse() + { + std::reverse(cigar_.begin(), cigar_.end()); + for (Operation& op : cigar_) + { + if (NODE_START == op.code_) + { + op.code_ = NODE_END; + } + else if (NODE_END == op.code_) + { + op.code_ = NODE_START; + } + } + } + + void collapseLastEmptyNode() + { + if (4 < cigar_.size() && cigar_[cigar_.size() - 1].code_ == Cigar::NODE_END + && cigar_[cigar_.size() - 4].code_ == Cigar::NODE_START + && Cigar::SOFT_CLIP == cigar_[cigar_.size() - 3].code_ + && Cigar::DELETE == cigar_[cigar_.size() - 2].code_) + { + Cigar::Operation op = cigar_[cigar_.size() - 3]; + cigar_.pop_back(); + cigar_.pop_back(); + cigar_.pop_back(); + cigar_.pop_back(); + std::swap(cigar_.back(), op); + cigar_.push_back(op); + } + else if ( + 3 < cigar_.size() && cigar_[cigar_.size() - 1].code_ == Cigar::NODE_END + && cigar_[cigar_.size() - 3].code_ == Cigar::NODE_START + && Cigar::SOFT_CLIP == cigar_[cigar_.size() - 2].code_) + { + Cigar::Operation op = cigar_[cigar_.size() - 2]; + cigar_.pop_back(); + cigar_.pop_back(); + cigar_.pop_back(); + std::swap(cigar_.back(), op); + cigar_.push_back(op); + } + } + + friend std::ostream& operator<<(std::ostream& os, const Cigar& cigar) + { + for (const Operation& op : cigar.cigar_) + { + if (NODE_START == op.code_) + { + os << op.value_ << '['; + } + else if (NODE_END == op.code_) + { + os << ']'; + } + else + { + os << op.value_ << op.getCharCode(); + } + } + return os; + } + + bool operator<(const Cigar& that) const + { + return std::lexicographical_compare(begin(), end(), that.begin(), that.end()); + } + + bool operator==(const Cigar& that) const { return cigar_ == that.cigar_; } + + private: + Operations cigar_; + }; + +} // namespace dagAligner + +} // namespace graphalign diff --git a/thirdparty/graph-tools-master/include/graphalign/dagAligner/PenaltyMatrix.hh b/thirdparty/graph-tools-master/include/graphalign/dagAligner/PenaltyMatrix.hh new file mode 100755 index 0000000..2eeda76 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphalign/dagAligner/PenaltyMatrix.hh @@ -0,0 +1,236 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Roman Petrovski +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include + +#include +#include + +#include + +#include "graphalign/dagAligner/Details.hh" + +namespace graphalign +{ + +namespace dagAligner +{ + + class FreePenaltyMatrix + { + typedef unsigned char Oligo; + + public: + typedef Oligo QueryChar; + typedef Oligo TargetChar; + + // test constructor, since this is a free-form penalty matrix, the one + // that accepts an actual matrix is expected + FreePenaltyMatrix(const Score match = 2, const Score mismatch = -2) + : matrix_{ + // // a c g t n + // {match, mismatch, mismatch, mismatch, 0}, // a + // {mismatch, match, mismatch, mismatch, 0}, // c + // {mismatch, mismatch, match, mismatch, 0}, // g + // {mismatch, mismatch, mismatch, match, 0}, // t + // {0, 0, 0, 0, 0} // n + + // // a c g t n + // {match, mismatch, mismatch, mismatch, mismatch}, // a + // {mismatch, match, mismatch, mismatch, mismatch}, // c + // {mismatch, mismatch, match, mismatch, mismatch}, // g + // {mismatch, mismatch, mismatch, match, mismatch}, // t + // {mismatch, mismatch, mismatch, mismatch, match} // n + + // // a c g t n + // { match, mismatch, mismatch, mismatch, match }, // a + // { mismatch, match, mismatch, mismatch, match }, // c + // { mismatch, mismatch, match, mismatch, match }, // g + // { mismatch, mismatch, mismatch, match, match }, // t + // + + // a c g t n + { match, mismatch, mismatch, mismatch, match }, // a + { mismatch, match, mismatch, mismatch, match }, // c + { mismatch, mismatch, match, mismatch, match }, // g + { mismatch, mismatch, mismatch, match, match }, // t + { match, match, match, match, match } // n + + } + { + } + //__attribute__ ((noinline)) + Score operator()(QueryChar q, TargetChar t) const + { + if (q >= ROWS_) + { + throw std::logic_error("Invalid row request from FreePenaltyMatrix"); + } + if (t >= COLUMNS_) + { + throw std::logic_error("Invalid column request from FreePenaltyMatrix"); + } + + return matrix_[q][t]; + } + + bool isMatch(QueryChar q, TargetChar t) const { return (*this)(q, q) == (*this)(q, t); } + + template + static void translateTarget(InputIterator targetBegin, InputIterator targetEnd, OutputIterator output) + { + std::transform(targetBegin, targetEnd, output, &translateOligo); + } + + template + static void translateQuery(InputIterator queryBegin, InputIterator queryEnd, OutputIterator output) + { + std::transform(queryBegin, queryEnd, output, &translateOligo); + } + + friend std::ostream& operator<<(std::ostream& os, const FreePenaltyMatrix& matrix) + { + os << "FreePenaltyMatrix(\n"; + for (int r = 0; r < ROWS_; ++r) + { + for (int c = 0; c < COLUMNS_; ++c) + { + os << (c ? "\t" : "[") << int(matrix.matrix_[r][c]); + } + os << "]\n"; + } + return os << ")"; + } + + static const Oligo A = 0; + static const Oligo C = 1; + static const Oligo G = 2; + static const Oligo T = 3; + static const Oligo N = 4; + static const Oligo TARGET_CHAR_MAX_ = N; + + static const int ROWS_ = 5; + static const int COLUMNS_ = 5; + + private: + const Score matrix_[ROWS_][COLUMNS_]; + static const std::size_t OLIGO_MAX_CHAR = 255; + static const Oligo TRANSLATION_TABLE_[OLIGO_MAX_CHAR + 1]; + + static Oligo translateOligo(unsigned char tc) { return TRANSLATION_TABLE_[tc]; } + }; + + template class FixedPenaltyMatrix + { + typedef unsigned char Oligo; + + public: + typedef Oligo QueryChar; + typedef Oligo TargetChar; + + FixedPenaltyMatrix(const Score match = 2, const Score mismatch = -2) + : match_(match) + , mismatch_(mismatch) + { + } + //__attribute__ ((noinline)) + Score operator()(QueryChar q, TargetChar t) const { return isMatch(q, t) ? match_ : mismatch_; } + + bool isMatch(QueryChar q, TargetChar t) const + { + return q == t || (matchQueryN && N == q) || (matchTargetN && N == t); + } + + template + static void translateTarget(InputIterator targetBegin, InputIterator targetEnd, OutputIterator output) + { + std::transform(targetBegin, targetEnd, output, &translateOligo); + } + + template + static void translateQuery(InputIterator queryBegin, InputIterator queryEnd, OutputIterator output) + { + std::transform(queryBegin, queryEnd, output, &translateOligo); + } + + friend std::ostream& operator<<(std::ostream& os, const FixedPenaltyMatrix& matrix) + { + return os << "FixedPenaltyMatrix(" << matrix.match_ << "," << matrix.mismatch_ << ")"; + } + static const Oligo A = 0; + static const Oligo C = 1; + static const Oligo G = 2; + static const Oligo T = 3; + static const Oligo N = 4; + static const Oligo X = 5; + static const Oligo TARGET_CHAR_MAX_ = 5; + + private: + const Score match_; + const Score mismatch_; + static const std::size_t OLIGO_MAX_CHAR = 255; + static const Oligo TRANSLATION_TABLE_[OLIGO_MAX_CHAR + 1]; + static Oligo translateOligo(unsigned char tc) { return TRANSLATION_TABLE_[tc]; } + }; + // clang-format off + template + const typename FixedPenaltyMatrix::Oligo + FixedPenaltyMatrix::TRANSLATION_TABLE_[OLIGO_MAX_CHAR + 1] + = { + N, N, N, N, N, N, N, N, N, N, + N, N, N, N, N, N, N, N, N, N, + N, N, N, N, N, N, N, N, N, N, + N, N, N, N, N, N, N, N, N, N, + N, N, N, N, N, N, N, N, N, N, + N, N, N, N, N, N, N, N, N, N, + N, N, N, N, N, + // capitals + A, N, C, N, N, N, G, N, N, N, + N, N, N, N, N, N, N, N, N, T, + N, N, N, X, N, N, + // rubbish + N, N, N, N, N, N, + // lowercase + A, N, C, N, N, N, G, N, N, N, + N, N, N, N, N, N, N, N, N, T, + N, N, N, X, N, N, + // padding + N, N, N, N, + N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, + N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, + N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, + N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, + }; + // clang-format on + +} // namespace dagAligner + +} // namespace graphalign diff --git a/thirdparty/graph-tools-master/include/graphcore/Graph.hh b/thirdparty/graph-tools-master/include/graphcore/Graph.hh new file mode 100755 index 0000000..ec8c156 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphcore/Graph.hh @@ -0,0 +1,149 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko , +// Peter Krusche , +// Felix Schlesinger + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "graphutils/PairHashing.hh" + +namespace graphtools +{ + +using NodeId = uint32_t; +using NodeIdPair = std::pair; +using SortedLabels = std::set; +using Labels = std::unordered_set; +using AdjacencyList = std::vector>; + +struct Node +{ + std::string name; + std::string sequence; + std::vector sequence_expansion; +}; + +/** + * Sequence graph that can hold degenerate nucleotide sequences + */ +class Graph +{ +public: + explicit Graph(size_t num_nodes = 0, std::string const& id = "", bool is_sequence_expansion_required = true) + : graphId(id) + { + init(num_nodes); + is_sequence_expansion_required_ = is_sequence_expansion_required; + } + + virtual ~Graph() = default; + + size_t numNodes() const { return nodes_.size(); } + size_t numEdges() const { return edge_labels_.size(); } + bool isSequenceExpansionRequired() const { return is_sequence_expansion_required_; } + const std::string& nodeName(NodeId node_id) const; + void setNodeName(NodeId node_id, const std::string& node_name); + const std::string& nodeSeq(NodeId node_id) const; + void setNodeSeq(NodeId node_id, const std::string& sequence); + const std::vector& nodeSeqExpansion(NodeId node_id) const; + void addEdge(NodeId source_node_id, NodeId sink_node_id); + bool hasEdge(NodeId source_node_id, NodeId sink_node_id) const; + + SortedLabels allLabels() const; + const Labels& edgeLabels(NodeId source_node_id, NodeId sink_node_id) const; + // All edges in the graph with this label + std::set edgesWithLabel(const std::string& label) const; + /** + * All label to an existing edge + * @throws if source -> sink is not an edge in the graph + */ + void addLabelToEdge(NodeId source_node_id, NodeId sink_node_id, const std::string& label); + // Remove this label from all edges + void eraseLabel(const std::string& label); + + // this cannot be const if graphs are to be assigned. Currently this happens in unit tests. + std::string graphId; + const std::set& successors(NodeId node_id) const; + const std::set& predecessors(NodeId node_id) const; + +private: + void init(size_t num_nodes); + void assertNodeExists(NodeId node_id) const; + void assertEdgeExists(NodeIdPair edge) const; + std::vector nodes_; + bool is_sequence_expansion_required_; + std::unordered_map edge_labels_; + AdjacencyList adjacency_list_; + AdjacencyList reverse_adjacency_list_; +}; + +class ReverseGraph +{ + const Graph& graph_; + +public: + explicit ReverseGraph(const Graph& graph) + : graph_(graph) + { + } + + size_t numNodes() const { return graph_.numNodes(); } + size_t numEdges() const { return graph_.numEdges(); } + bool isSequenceExpansionRequired() const { return graph_.isSequenceExpansionRequired(); } + const std::string& nodeName(NodeId nodeId) const { return graph_.nodeName(nodeId); } + const std::string nodeSeq(NodeId nodeId) const + { + std::string ret = graph_.nodeSeq(nodeId); + std::reverse(ret.begin(), ret.end()); + return ret; + } + bool hasEdge(NodeId sourceNodeId, NodeId sinkNodeId) const { return graph_.hasEdge(sourceNodeId, sinkNodeId); } + + SortedLabels allLabels() const { return graph_.allLabels(); } + const Labels& edgeLabels(NodeId sourceNodeId, NodeId sinkNodeId) const + { + return graph_.edgeLabels(sourceNodeId, sinkNodeId); + } + + // All edges in the graph with this label + std::set edgesWithLabel(const std::string& label) const { return graph_.edgesWithLabel(label); } + + const std::set& successors(NodeId nodeId) const { return graph_.predecessors(nodeId); } + const std::set& predecessors(NodeId nodeId) const { return graph_.successors(nodeId); } +}; + +} diff --git a/thirdparty/graph-tools-master/include/graphcore/GraphBuilders.hh b/thirdparty/graph-tools-master/include/graphcore/GraphBuilders.hh new file mode 100755 index 0000000..5dc1abf --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphcore/GraphBuilders.hh @@ -0,0 +1,112 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko , +// Peter Krusche + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include + +#include "graphcore/Graph.hh" + +namespace graphtools +{ + +/** + * Construct a graph representing deletion of a sequence from a reference + * + * The node ids are assigned in order specified by the function paramters. + * + * @param left_flank Sequence of the left flank + * @param deletion Sequence deleted from the reference + * @param right_flank Sequence of the right flank + * @return Deletion graph + */ +Graph makeDeletionGraph(const std::string& left_flank, const std::string& deletion, const std::string& right_flank); + +/** + * Construct a graph representing replacement of a piece of a reference by another sequence + * + * The node ids are assigned in order specified by the function paramters. + * + * @param left_flank Sequence of the left flank + * @param deletion Sequence deleted from the reference + * @param insertion Sequence inserted into the reference + * @param right_flank Sequence of the right flank + * @return Swap graph + */ +Graph makeSwapGraph( + const std::string& left_flank, const std::string& deletion, const std::string& insertion, + const std::string& right_flank); + +/** + * Construct a graph representing two sequence swaps separated by another sequence + * + * The node ids are assigned in order specified by the function paramters. + * + * @param left_flank Sequence of the left flank + * @param deletion1 Deleted sequence of the first swap + * @param insertion1 Inserted sequence of the first swap + * @param middle Sequence separating the swaps + * @param deletion2 Deleted sequence of the second swap + * @param insertion2 Inserted sequence of the second swap + * @param right_flank Sequence of the right flank + * @return Double-swap graph + */ +Graph makeDoubleSwapGraph( + const std::string& left_flank, const std::string& deletion1, const std::string& insertion1, + const std::string& middle, const std::string& deletion2, const std::string& insertion2, + const std::string& right_flank); + +/** + * Construct a graph representing an STR repeat with the loop separated into multiple nodes to keep the graph acyclic + * + * The first and the last nodes correspond to the left and the right flanks respectively. The internal nodes correspond + * to the repeat unit. The number of repeat unit nodes is given by ceiling(read length/unit length). + * + * @param read_len Length of the sequenced reads + * @param left_flank Sequence of the left flank + * @param repeat_unit Repeat unit + * @param right_flank Sequence of the right flank + * @return Loopless STR graph + */ +Graph makeLooplessStrGraph( + size_t read_len, const std::string& left_flank, const std::string& repeat_unit, const std::string& right_flank); + +/** + * Construct a graph representing an STR repeat + * + * The graph consists of the repeat flanks separated by the loop corresponding to multiple repetitions of the repeat + * unit. The node ids are assigned in order specified by the function paramters. + * + * @param left_flank Sequence of the left flank + * @param repeat_unit Repeat unit + * @param right_flank Sequence of the right flank + * @return STR graph + */ +Graph makeStrGraph(const std::string& left_flank, const std::string& repeat_unit, const std::string& right_flank); +} diff --git a/thirdparty/graph-tools-master/include/graphcore/GraphCoordinates.hh b/thirdparty/graph-tools-master/include/graphcore/GraphCoordinates.hh new file mode 100755 index 0000000..edc2879 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphcore/GraphCoordinates.hh @@ -0,0 +1,100 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Peter Krusche + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "graphalign/GraphAlignment.hh" + +#include + +namespace graphtools +{ + +/** + * Class to translate between different ways to index nucleotides on the graph + */ +class GraphCoordinates +{ +public: + explicit GraphCoordinates(graphtools::Graph const* g); + virtual ~GraphCoordinates(); + GraphCoordinates(GraphCoordinates const&) = delete; + GraphCoordinates& operator=(GraphCoordinates const&) = delete; + + GraphCoordinates(GraphCoordinates&& rhs) noexcept; + GraphCoordinates& operator=(GraphCoordinates&& rhs) noexcept; + + /** + * Get a "canonical" / linearized position -- every base on the graph has such a position + * Positions within the same node are guaranteed to be consecutive + * @param node node name + * @param offset offset relative to start of node + * @return canonical position + */ + uint64_t canonicalPos(std::string const& node, uint64_t offset = 0) const; + + /** + * Calculated canonical start and end positions for a graph mapping + * @param mapping + * @return start and end + */ + std::pair canonicalStartAndEnd(graphtools::Path const& mapping) const; + + /** + * Reverse lookup : get node and offset from a canonical pos + * @param canonical_pos canonical position + * @param node output variable for node name + * @param offset output variable for offset + */ + void nodeAndOffset(uint64_t canonical_pos, std::string& node, uint64_t& offset) const; + + /** + * Calculate the minimum distance in bp between two canonical positions + * @param pos1 start pos + * @param pos2 end pos + * @return basepairs between pos1 and pos2 + */ + uint64_t distance(uint64_t pos1, uint64_t pos2) const; + + /** + * Return the node id for a node name + * @param node_name name of node + * @return node id for the node + */ + NodeId nodeId(const std::string& node_name) const; + + /** + * @return the graph for these coordinates + */ + Graph const& getGraph() const; + +private: + struct GraphCoordinatesImpl; + std::unique_ptr _impl; +}; +} diff --git a/thirdparty/graph-tools-master/include/graphcore/GraphOperations.hh b/thirdparty/graph-tools-master/include/graphcore/GraphOperations.hh new file mode 100755 index 0000000..984d14c --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphcore/GraphOperations.hh @@ -0,0 +1,42 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko , +// Peter Krusche + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "Graph.hh" + +namespace graphtools +{ +/** + * Reverse (and optionally complement) a graph + * @param graph the graph to reverse + * @param complement true to also reverse-complement all sequences + */ +Graph reverseGraph(Graph const& graph, bool complement = false); +} diff --git a/thirdparty/graph-tools-master/include/graphcore/GraphReferenceMapping.hh b/thirdparty/graph-tools-master/include/graphcore/GraphReferenceMapping.hh new file mode 100755 index 0000000..f045bcc --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphcore/GraphReferenceMapping.hh @@ -0,0 +1,136 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Felix Schlesinger + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include + +#include "Graph.hh" +#include "Path.hh" + +namespace graphtools +{ +typedef std::string ContigId; // Identifier of a contig (chromosome) within a reference +typedef int32_t Position; // 0-based position in a reference sequence + +/** + * Defines an interval on a (genomic) reference sequence + */ +class ReferenceInterval +{ +public: + ReferenceInterval(ContigId contig, Position start, Position end); + /** + * Create a 0-length interval with start=stop=pos. + * Represents the position right before base 'pos' (0-based) + */ + static ReferenceInterval makePosition(ContigId const& contig, Position pos); + /** + * Create a region by parsing it from a region string + * @param region string: :-. 0-based Half-open interval + * @return ReferenceInterval matching the region + * @throws If not a valid region string + */ + static ReferenceInterval parseRegion(std::string const& regionString); + friend bool operator<(ReferenceInterval const&, ReferenceInterval const&); + friend bool operator==(ReferenceInterval const&, ReferenceInterval const&); + friend std::ostream& operator<<(std::ostream&, ReferenceInterval const&); + + /** + * Length (number of bases covered) of the interval + */ + int32_t length() const; + + // Reference sequence (chromosome) name + ContigId const contig; + // Start 0-based closed (i.e. included) + Position const start; + // End 0-based open (i.e. excluded) + Position const end; +}; + +/** + * Map a node to a single piece of reference sequence + * Very simple 1-1 mapping for now + */ +class NodeReferenceMapping +{ +public: + /** + * Create a mapping from a node to a reference interval + * The reference interval must have the same length as the sequence of the node + */ + NodeReferenceMapping(Graph const&, NodeId, ReferenceInterval const&); + + /** + * Map a position within a node to a reference position using the NodeReferenceMapping + * @param offset Position within the node. Must be < node length + * @return 0-length interval at mapped position + */ + ReferenceInterval map(int32_t offset) const; + +private: + int32_t const nodeLength_; + ReferenceInterval const ref_; +}; + +/** + * Mapping of (a subset of) nodes in a graph to a reference sequence + * At most one mapping per node for now + */ +class GraphReferenceMapping +{ +public: + explicit GraphReferenceMapping(Graph const*); + + /** + * Map a node to a reference interval + * ReferenceInterval must have the same length as node sequence + */ + void addMapping(NodeId, ReferenceInterval const&); + /** + * Map a position within a node to a reference position + * @param node in the graph this GraphReferenceMapping is based on + * @param offset Position within the node. Must be < node length + * @return 0-length interval at mapped position if the node is mapped, nothing otherwise + */ + boost::optional map(NodeId node, int32_t offset) const; + /** + * Map the first mappable position in a path to a reference position + * I.e. the start position of the path in the first node that has a reference mapping + * @return 0-length interval at mapped position if the node is mapped, nothing otherwise + */ + boost::optional map(Path const&) const; + +private: + Graph const* const graph_; + std::unordered_map mappings_; +}; +} diff --git a/thirdparty/graph-tools-master/include/graphcore/Path.hh b/thirdparty/graph-tools-master/include/graphcore/Path.hh new file mode 100755 index 0000000..6887cd9 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphcore/Path.hh @@ -0,0 +1,196 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "graphcore/Graph.hh" + +namespace graphtools +{ + +/** + * A path in a sequence graph is given by (1) a sequence of nodes and (2) start/end position on the first/last node. The + * start/end positions are 0-based and form a half-open interval. + */ +class Path +{ +public: + typedef std::vector::const_iterator const_iterator; + // The constructor checks if the inputs define a well-formed path. + Path(const Graph* graph_raw_ptr, int32_t start_position, const std::vector& nodes, int32_t end_position); + ~Path(); + Path(const Path& other); + Path(Path&& other) noexcept; + Path& operator=(const Path& other); + Path& operator=(Path&& other) noexcept; + bool operator==(const Path& other) const; + bool operator<(const Path& path) const; + + const_iterator begin() const; + const_iterator end() const; + + // Ids of nodes overlapped by the path + std::vector const& nodeIds() const; + size_t numNodes() const; + // Sequence of the entire path + std::string seq() const; + // Piece of node sequence that the path overlaps + std::string getNodeSeq(size_t node_index) const; + const Graph* graphRawPtr() const; + std::string encode() const; + int32_t startPosition() const; + int32_t endPosition() const; + size_t length() const; + NodeId getNodeIdByIndex(size_t node_index) const; + int32_t getStartPositionOnNodeByIndex(size_t node_index) const; + int32_t getEndPositionOnNodeByIndex(size_t node_index) const; + size_t getNodeOverlapLengthByIndex(size_t node_index) const; + + bool checkOverlapWithNode(NodeId node_id) const; + int32_t getDistanceFromPathStart(NodeId node, int32_t offset) const; + + // Path modifiers + // Moves start position by a specified length; the path gets longer/shorter if shift_len is positive/negative + // respectively. + void shiftStartAlongNode(int32_t shift_len); + // Moves end position by a specified length; the path gets longer/shorter if shift_len is positive/negative + // respectively. + void shiftEndAlongNode(int32_t shift_len); + // Moves path start to the end of the specified node. The new node must be a predecessor of the previous start node. + void extendStartToNode(NodeId node_id); + // Moves path start to the start of the specified node. The new node must be a predecessor of the previous start + // node. + void extendStartToIncludeNode(NodeId node_id); + // Moves path end to the start of the specified node. The new node must be a successor of the previous start node. + void extendEndToNode(NodeId node_id); + // Moves path end to the end of the specified node. The new node must be a successor of the previous start node. + void extendEndToIncludeNode(NodeId node_id); + // Moves path start to the start of the next node in the path. + void removeStartNode(); + // Moves path end to the end of the previous node in the path. + void removeEndNode(); + // Moves path start to the start of the next node if the start has zero-length overlap with the corresponding node; + // does nothing if path spans only one node. + void removeZeroLengthStart(); + // Moves path end to the end of the end of the previous node if the end of the path has zero-length overlap with the + // corresponding node; does nothing if path spans only one node. + void removeZeroLengthEnd(); + // Shortens the start of the path by a specified length. + void shrinkStartBy(int32_t shrink_len); + // Shortens the end of the path by a specified length. + void shrinkEndBy(int32_t shrink_len); + // Shortens the path by the specified lengths from each direction. + void shrinkBy(int32_t start_shrink_len, int32_t end_shrink_len); + + NodeId firstNodeId() const { return nodeIds().front(); } + NodeId lastNodeId() const { return nodeIds().back(); } + +private: + struct Impl; + std::unique_ptr pimpl_; +}; + +std::ostream& operator<<(std::ostream& os, const Path& path); + +class ReversePath +{ + Path& path_; + +public: + explicit ReversePath(Path& path) + : path_(path) + { + } + + // TODO: add methods as needed + + NodeId firstNodeId() const { return path_.lastNodeId(); } + NodeId lastNodeId() const { return path_.firstNodeId(); } + + int32_t startPosition() const + { + return path_.graphRawPtr()->nodeSeq(path_.lastNodeId()).length() - path_.endPosition(); + } + + int32_t endPosition() const + { + return path_.graphRawPtr()->nodeSeq(path_.firstNodeId()).length() - path_.startPosition(); + } + + std::string seq() const + { + std::string ret = path_.seq(); + std::reverse(ret.begin(), ret.end()); + return ret; + } + + void shiftEndAlongNode(int32_t shift_len) { path_.shiftStartAlongNode(shift_len); } + void extendEndToNode(NodeId node_id) { path_.extendStartToNode(node_id); } + + friend std::ostream& operator<<(std::ostream& os, const ReversePath& path) + { + return os << "reverse path of: " << path.path_; + } +}; + +class ConstReversePath +{ + const Path& path_; + +public: + explicit ConstReversePath(const Path& path) + : path_(path) + { + } + + // TODO: add methods as needed + + NodeId firstNodeId() const { return path_.lastNodeId(); } + NodeId lastNodeId() const { return path_.firstNodeId(); } + + int32_t startPosition() const + { + return path_.graphRawPtr()->nodeSeq(path_.lastNodeId()).length() - path_.endPosition(); + } + + int32_t endPosition() const + { + return path_.graphRawPtr()->nodeSeq(path_.firstNodeId()).length() - path_.startPosition(); + } + + const Graph* graphRawPtr() const { return path_.graphRawPtr(); } +}; + +} diff --git a/thirdparty/graph-tools-master/include/graphcore/PathFamily.hh b/thirdparty/graph-tools-master/include/graphcore/PathFamily.hh new file mode 100755 index 0000000..29beb07 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphcore/PathFamily.hh @@ -0,0 +1,85 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Felix Schlesinger , +// Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "graphcore/Graph.hh" +#include "graphcore/Path.hh" + +namespace graphtools +{ + +/** + * Defines a Path Family (a set of paths) from a set of edges. + * Briefly, a path is part of a path family F if + * - it uses at least one edge in F + * - it uses an edge in F into or out-of any node where such an edge is present + */ +class PathFamily +{ +public: + explicit PathFamily(Graph*); + // Path family from all edges with the given label + PathFamily(Graph* graph, const std::string& label); + PathFamily(const PathFamily&); + PathFamily(PathFamily&&) noexcept; + ~PathFamily() noexcept; + + bool operator==(const PathFamily&) const; + bool operator!=(const PathFamily&) const; + + std::unordered_set const& edges() const; + const Graph& graph() const; + + // Check if a path in contained in (consistent with) F + bool containsPath(const Path&) const; + // Check if another path family contains a subset of edges in F + bool includes(const PathFamily&) const; + + void addEdge(NodeId first, NodeId second); + // Apply the given label on the graph to on all edges in F (and delete from others) + void setLabel(const std::string&); + +private: + struct Impl; + std::unique_ptr pimpl_; +}; + +std::ostream& operator<<(std::ostream& os, const PathFamily& path); +} diff --git a/thirdparty/graph-tools-master/include/graphcore/PathFamilyOperations.hh b/thirdparty/graph-tools-master/include/graphcore/PathFamilyOperations.hh new file mode 100755 index 0000000..53de601 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphcore/PathFamilyOperations.hh @@ -0,0 +1,100 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Felix Schlesinger , +// Peter Krusche , +// Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "Path.hh" +#include "PathFamily.hh" + +#include +#include +#include + +namespace graphtools +{ + +/** + * Generate path segments in a family which can be combined into longer paths. + * + * These segments are built by concatenating family edges into paths whenever + * this is possible uniquely, ignoring repeat / self-loop edges. + * + * @param family the path family + * @return a list of path segments + */ +std::list getPathSegmentsForFamily(graphtools::PathFamily const& family); + +/** + * Enumerate path segment combinations in family. + * + * Two path segments can be combined if they overlap or are adjacent and + * (if adjacent on different nodes) their linking edge is in the family. + * + * @param family a path family + * @param segments a set of path segments + * @param[out] paths output list of paths + * @param maxPaths maximum number of paths to generate + * @return true if all paths were generated, false if maxPaths was used to limit the number of paths + */ +bool enumeratePathCombinationsInFamily( + PathFamily const& family, std::list const& segments, std::list* paths, size_t maxPaths); + +/** + * Get all maximal paths in a path family, exhaustively + * + * Note that this function can generate an number of paths that is + * exponential in the number of nodes. + * + * @param family the path family + * @param[out] paths output list of paths + * @param maxPaths limit the number of paths + * @return true if all paths were enumerated, false if additional paths can be generated + * + * Note this will ignore self-edges / loops. + * + */ +bool getMaximalPathsForFamily( + graphtools::PathFamily const& family, std::list* paths, size_t maxPaths = std::numeric_limits::max()); + +/** + * Convert path to path family + * @param graph must match the graph used by path + * @param path a path on graph + * @return a path family containing all edges in path + */ +graphtools::PathFamily pathToPathFamily(graphtools::Graph& graph, graphtools::Path const& path); + +/** + * Get all path families from edge labels on a graph + * @param graph a graph (not const because path families are constructed from a non-const graph) + * @return mapping of label -> path family + */ +std::map getPathFamiliesFromGraph(graphtools::Graph& graph); +} diff --git a/thirdparty/graph-tools-master/include/graphcore/PathOperations.hh b/thirdparty/graph-tools-master/include/graphcore/PathOperations.hh new file mode 100755 index 0000000..565ca32 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphcore/PathOperations.hh @@ -0,0 +1,186 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko , +// Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include + +#include "graphcore/Path.hh" + +namespace graphtools +{ + +/** + * Computes all possible extensions of the path by the specified length in both directions + * + * @param start_extension_len Extension length of the start posision + * @param end_extension_len Extension length of the end position + * @return List of extended paths + */ +std::list extendPath(const Path& path, int32_t start_extension_len, int32_t end_extension_len); + +// Computes all possible extensions of the start of the path by the specified length +std::list extendPathStart(const Path& path, int32_t extension_len); + +// Computes all possible extensions of the end of the path by the specified length +std::list extendPathEnd(const Path& path, int32_t extension_len); + +/** + * Extend a path matching a query sequence to produce maximum exact + unique matches + * + * The path is extended at the end while the query string matches uniquely. + * + * @param path the path. We must have path.length() <= query.size() - qpos + * @param query query string. + * @param qpos position where path starts in query + * @return extended path + */ +Path extendPathEndMatching(Path path, const std::string& query, size_t qpos); + +/** + * Extend a path matching a query sequence to produce maximum exact + unique matches + * + * The path is extended at the start while the query string matches uniquely. + * + * @param path the path. We must have path.length() <= query.size() - qpos + * @param query query string. + * @param[out] qpos position where path starts in query -- updated as path is extended at start + * @return extended path + */ +Path extendPathStartMatching(Path path, const std::string& query, size_t& pos_in_query); + +/** + * Extend a path matching a query sequence to produce maximum exact + unique matches + * + * The path is extended at the start and end while the query string matches uniquely. + * + * @param path the path. We must have path.length() <= query.size() - qpos + * @param query query string. + * @param[out] qpos position where path starts in query -- updated as path is extended at start + * @return extended path + */ +Path extendPathMatching(Path path, const std::string& query, size_t& pos_in_query); + +/** + * Splits sequence into segments corresponding to the path + * + * @param path Any path + * @param sequence A string having the same length as the path + * @return Segments of the sequence corresponding to nodes spanned by the path + */ +std::vector splitSequenceByPath(const Path& path, const std::string& sequence); + +/** + * Return true if two paths are exactly adjacent + * (i.e. p1 starts just before p2, or the other way around) + * + * @param p1 first path + * @param p2 second path + * @return true if the paths are adjacent + */ +bool checkIfPathsAdjacent(Path const& p1, Path const& p2); + +/** + * Return true if two paths overlap either prefix - suffix or suffix-prefix + * @param p1 first path + * @param p2 second path + * @return true if the paths overlap + */ +bool checkPathPrefixSuffixOverlap(Path const& p1, Path const& p2); + +/** + * Paths can be merged if they overlap prefix-suffix / suffix-prefix. + * + * @param p1 first path + * @param p2 second path + * @return merged path + */ +Path mergePaths(Path const& p1, Path const& p2); + +/** + * Merge a set of paths + * + * This will merge paths iteratively until none of the resulting paths overlap + * + * @param paths a list of paths + */ +void greedyMerge(std::list& paths); + +/** + * Merge a set of paths + * + * This will merge paths exhaustively, each path is merged with all + * paths it overlaps until we cannot merge anymore + * + * @param paths a list of paths + */ +void exhaustiveMerge(std::list& paths); + +/** + * Return the intersection(s) between two paths + * + * Multiple paths may be returned when paths diverge and re-join. + * + * @param p1 first path + * @param p2 second path + * @return merged path + */ +std::list intersectPaths(Path const& p1, Path const& p2); + +/** + * Returns subpaths corresponding to overlap of the input path with each node it passes through + * + * @param path: any path + * @return single-node subpaths + */ +std::list generateSubpathForEachNode(const Path& path); + +/** + * Checks if two paths are bookended + * + * Two paths are considered bookended if positions of first path end and second path start are adjacent on the graph + * + * @param first_alignment: Any path + * @param second_alignment: Any path on same graph + * @return true if paths are bookended + */ +bool checkIfBookended(const Path& first_path, const Path& second_path); + +/** + * Concatenates two bookended paths into a longer path + * + * @param first_alignment: Any path + * @param second_alignment: Any path that is bookended with the first + * @return Concatenated path + */ +Path concatenatePaths(const Path& first_path, const Path& second_path); +} diff --git a/thirdparty/graph-tools-master/include/graphutils/BaseMatching.hh b/thirdparty/graph-tools-master/include/graphutils/BaseMatching.hh new file mode 100755 index 0000000..df9e9b7 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphutils/BaseMatching.hh @@ -0,0 +1,172 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include + +namespace graphtools +{ + +namespace codes +{ + using BaseCode = unsigned char; + const int kMaxBaseCode = 15; + + // Core base codes + const BaseCode A = 0; + const BaseCode C = 1; + const BaseCode G = 2; + const BaseCode T = 3; + const BaseCode X = 4; + + // Degenerate base codes + const BaseCode B = 5; + const BaseCode D = 6; + const BaseCode H = 7; + const BaseCode K = 8; + const BaseCode M = 9; + const BaseCode N = 10; + const BaseCode R = 11; + const BaseCode S = 12; + const BaseCode V = 13; + const BaseCode W = 14; + const BaseCode Y = 15; + + const int kMaxQueryBaseCode = 4; + const int kMaxReferenceBaseCode = 15; + + const int maxBaseAscii = 255; + + // Currently, low-quality (lower case) bases get the same encoding as their high-quality counterparts. We should + // extend the coding scheme when we are ready to deal with base-quality in the alignment. + + // Core bases A, C, G, T and degenerate bases B, D, H, K, M, N, S, R, V, W, Y all receive distinct codes. All other + // base symbols are coded as X, which is the code intended to mismatch everything. + const std::array kReferenceBaseEncodingTable + = { X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, A, B, C, D, X, X, G, H, X, X, K, X, M, N, X, X, X, R, S, T, X, V, W, X, Y, X, X, X, X, X, X, + X, A, X, C, X, X, X, G, X, X, X, X, X, X, X, X, X, X, X, X, T, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X }; + + // Core bases A, C, G, T all recieve distinct codes. All other base symbols are coded as X. + const std::array kQueryBaseEncodingTable + = { X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, A, X, C, X, X, X, G, X, X, X, X, X, X, X, X, X, X, X, X, T, X, X, X, X, X, X, X, X, X, X, X, + X, A, X, C, X, X, X, G, X, X, X, X, X, X, X, X, X, X, X, X, T, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X }; + + // We use the standard matching rules for degenerate bases. The X symbol corresponds to a mismatch + const std::array, codes::kMaxReferenceBaseCode + 1> + kReferenceQueryCodeMatchLookupTable = { + // clang-format off + // A C G T X + { { true, false, false, false, false }, // A + { false, true, false, false, false }, // C + { false, false, true, false, false }, // G + { false, false, false, true, false }, // T + { false, false, false, false, false }, // X + { false, true, true, true, false }, // B + { true, false, true, true, false }, // D + { true, true, false, true, false }, // H + { false, false, true, true, false }, // K + { true, true, false, false, false }, // M + { true, true, true, true, false }, // N + { true, false, true, false, false }, // R + { false, true, true, false, false }, // S + { true, true, true, false, false }, // V + { true, false, false, true, false }, // W + { false, true, false, true, false } // Y + } + // clang-format on + }; +} + +inline codes::BaseCode encodeReferenceBase(char base) { return codes::kReferenceBaseEncodingTable[base]; } + +inline codes::BaseCode encodeQueryBase(char base) { return codes::kQueryBaseEncodingTable[base]; } + +/** + * Checks if a pair of reference and query base codes corresponds to matching bases + * + * Examples: + * checkIfReferenceBaseCodeMatchesQueryBaseCode(encodeReferenceBase('C'), encodeQueryBase('c')) == true + * checkIfReferenceBaseCodeMatchesQueryBaseCode(encodeReferenceBase('T'), encodeQueryBase('Y')) == true + * checkIfReferenceBaseCodeMatchesQueryBaseCode(encodeReferenceBase('a'), encodeQueryBase('W')) == true + * checkIfReferenceBaseCodeMatchesQueryBaseCode(encodeReferenceBase('C'), encodeQueryBase('G')) == false + * + */ +inline bool checkIfReferenceBaseCodeMatchesQueryBaseCode(codes::BaseCode referenceCode, codes::BaseCode queryCode) +{ + // Temporary asserts until we add validation code for reference/query strings. + assert(referenceCode <= codes::kMaxReferenceBaseCode); + assert(queryCode <= codes::kMaxQueryBaseCode); + + return codes::kReferenceQueryCodeMatchLookupTable[referenceCode][queryCode]; +} + +inline bool checkIfReferenceBaseMatchesQueryBase(char referenceBase, char queryBase) +{ + return checkIfReferenceBaseCodeMatchesQueryBaseCode(encodeReferenceBase(referenceBase), encodeQueryBase(queryBase)); +} + +/** + * Checks if a reference sequence matches a query sequence + * + * @param reference And reference sequence + * @param query Any query sequence + * @return True if the sequences match + */ +inline bool checkIfReferenceAndQuerySequencesMatch(const std::string& reference, const std::string& query) +{ + if (reference.length() != query.length()) + { + return false; + } + + for (size_t index = 0; index != reference.length(); ++index) + { + if (!checkIfReferenceBaseMatchesQueryBase(reference[index], query[index])) + { + return false; + } + } + + return true; +} +} diff --git a/thirdparty/graph-tools-master/include/graphutils/DepthTest.hh b/thirdparty/graph-tools-master/include/graphutils/DepthTest.hh new file mode 100755 index 0000000..d3cc37a --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphutils/DepthTest.hh @@ -0,0 +1,62 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Sai Chen , +// Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include + +using boost::math::normal_distribution; + +class DepthTest +{ +public: + /** + * Set up depth test + * @param expectedNumReads The mean number of reads when the coverage is as expected + * @param stdDeviation The standard deviation of the number of reads + * @param lowerSignificanceThreshold P value cutoff at the lower tail of the distribution + * @param upperSignificanceThreshold P value cutoff at the upper tail of the distribution + */ + DepthTest( + int32_t expectedNumReads, double stdDeviation, double lowerSignificanceThreshold, + double upperSignificanceThreshold); + + /** + * Given observed number of reads, return true if pass depth test + */ + bool testReadCount(int32_t observedNumReads); + +private: + int32_t expectedNumReads_; + double stdDeviation_; + double lowerSignificanceThreshold_; + double upperSignificanceThreshold_; + const normal_distribution<> coverageDistribution_; +}; diff --git a/thirdparty/graph-tools-master/include/graphutils/IntervalBuffer.hh b/thirdparty/graph-tools-master/include/graphutils/IntervalBuffer.hh new file mode 100755 index 0000000..7c9fbe7 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphutils/IntervalBuffer.hh @@ -0,0 +1,85 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include +#include +#include + +namespace intervals +{ + +class IntervalBuffer +{ +public: + /** tracks intervals over a number of lanes */ + IntervalBuffer(); + IntervalBuffer(IntervalBuffer const& rhs); + virtual ~IntervalBuffer(); + IntervalBuffer& operator=(IntervalBuffer const& rhs); + + /** + * @brief Add an interval to a lane + * + * @param start interval coordinates + * @param end interval coordinates + * @param lane lane to add to + */ + void addInterval(int64_t start, int64_t end, size_t lane); + + /** + * @brief Advance buffer, discarding all intervals with end < to + * + * @param to interval minimum end position; pass -1 to clear buffer + */ + void advance(int64_t to); + + /** + * @brief Check if interval is fully covered in a given lane + */ + bool isCovered(int64_t start, int64_t end, size_t lane) const; + + /** + * @brief Check if interval is partially covered in a given lane + */ + bool hasOverlap(int64_t start, int64_t end, size_t lane) const; + + /** + * Get the intervals for a particular lane + * @return intervals for a particular lane + */ + std::list> getIntervals(size_t lane) const; + +private: + struct Impl; + std::unique_ptr pimpl_; +}; +} diff --git a/thirdparty/graph-tools-master/include/graphutils/IntervalList.hh b/thirdparty/graph-tools-master/include/graphutils/IntervalList.hh new file mode 100755 index 0000000..fa41902 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphutils/IntervalList.hh @@ -0,0 +1,286 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/** + * \brief Store a list of non-intersecting intervals + * + * + * \file IntervalList.hh + * \author Peter Krusche + * \email pkrusche@illumina.com + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace intervals +{ + +/** interval interface */ +struct interval +{ + explicit interval(int64_t _start = -1, int64_t _end = -1) + : start(_start) + , end(_end) + { + } + virtual ~interval() = default; + + // merge two intervals + virtual void merge(interval const& rhs) + { + if (start < 0) + { + start = rhs.start; + } + else + { + start = std::min(rhs.start, start); + } + if (end < 0) + { + end = rhs.end; + } + else + { + end = std::max(rhs.end, end); + } + } + + virtual void resize(int64_t _start, int64_t _end) + { + if (_start >= 0) + { + start = _start; + } + if (_end >= 0) + { + end = _end; + } + } + + int64_t start, end; +}; + +template class IntervalList +{ +public: + virtual ~IntervalList() = default; + + void add(interval_t const& iv) + { + if (iv.start > iv.end) + { + return; + } + + // find first interval that ends after start - 1 (so we join adjacent intervals) + auto ivr = intervals.lower_bound(iv.start - 1); + if (ivr == intervals.end()) + { + // no interval ends after start => prepend + intervals[iv.end] = iv; + } + else + { + // first interval that ends after start + // check overlap + + // do they overlap + // we know ivr->first >= start + if (ivr->second.start <= iv.end) + { + // overlap => merge + interval_t tmp = ivr->second; + tmp.merge(iv); + intervals.erase(ivr); + add(tmp); + } + else + { + // no overlap: + // x y s f + //-[---]--------[-------]------- + // |----| + // start end + // (y must be < start because otherwise we'd have found it with lower_bound) + // => insert interval + intervals[iv.end] = iv; + } + } + } + + /** get all intervals that overlap a range */ + interval_t query(int64_t start, int64_t end) const + { + interval_t ivs; + + if (end < start) + { + return ivs; + } + + // find first interval that ends after start + auto it = intervals.cbegin(); + + int x = 0; + while (it != intervals.cend() && it->second.end < start && x < 3) + { + ++it; + ++x; + } + + // if using advance, we don't need to search -- the first interval will + // already be the one we're looking for + if (it != intervals.cend() && it->second.end < start) + { + it = intervals.lower_bound(start); + } + + // overlap if it->second.start <= end + while (it != intervals.cend() && + // check if overlap + it->second.start <= end) + { + ivs.merge(it->second); + ++it; + } + return ivs; + } + + /** get all intervals that overlap a range */ + template > void get(int64_t start, int64_t end, container_t& output) + { + if (end < start) + { + return; + } + + // find first interval that ends after start + auto it = intervals.lower_bound(start); + + // overlap if it->second.start <= end + while (it != intervals.end() && + // check if overlap + it->second.start <= end) + { + output.push_back(it->second); + ++it; + } + } + + /** reset / remove interval range */ + void remove_from(int64_t start) + { + // find first interval that ends after start + auto it = intervals.lower_bound(start); + + // keep all intervals that end before start + // know: + // it->second.start <= it->second.end && + // start <= it->second.end && + // start <= end + // region to delete starts inside interval? + // (if interval starts exactly at start, we can remove it) + if (it != intervals.end() && it->second.start < start) + { + interval_t tmp = it->second; + intervals.erase(it, intervals.end()); + // start is > end if end == -1 + tmp.resize(tmp.start, start - 1); + intervals[tmp.end] = tmp; + } + else + { + // remove stuff that ends afterwards, if any + intervals.erase(it, intervals.end()); + } + } + + /** reset / remove interval range */ + void remove_to(int64_t end) + { + if (end < 0) + { + intervals.clear(); + return; + } + + // find first interval that ends after end + auto it = intervals.lower_bound(end); + if (it != intervals.end() && it->second.start <= end) + { + if (it->first > end) + { + it->second.resize(end + 1, it->first); + } + else // fully contained => also remove + { + ++it; + } + } + intervals.erase(intervals.begin(), it); + } + + /** remove values outside region */ + void keep_only(int64_t _start, int64_t _end) + { + remove_to(_start - 1); + remove_from(_end + 1); + } + + /** return intervals in arbitrary container */ + template > container_t getIntervals() const + { + container_t t; + for (auto const& iv : intervals) + { + t.push_back(iv.second); + } + return t; + } + +protected: + std::map intervals; + + void ensure_is_interval() + { + // make sure interval_t is derived from interval + interval* iv = new interval_t(); + delete iv; + } +}; +} diff --git a/thirdparty/graph-tools-master/include/graphutils/PairHashing.hh b/thirdparty/graph-tools-master/include/graphutils/PairHashing.hh new file mode 100755 index 0000000..7ca38aa --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphutils/PairHashing.hh @@ -0,0 +1,57 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include + +namespace graphtools +{ + +static inline void hash_combine(std::size_t& /*seed*/) {} + +template inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) +{ + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + hash_combine(seed, rest...); +} +} + +namespace std +{ +template struct hash> +{ + std::size_t operator()(const std::pair& t) const + { + std::size_t result = 0; + graphtools::hash_combine(result, t.first, t.second); + return result; + } +}; +} \ No newline at end of file diff --git a/thirdparty/graph-tools-master/include/graphutils/SequenceOperations.hh b/thirdparty/graph-tools-master/include/graphutils/SequenceOperations.hh new file mode 100755 index 0000000..33695e0 --- /dev/null +++ b/thirdparty/graph-tools-master/include/graphutils/SequenceOperations.hh @@ -0,0 +1,90 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include + +namespace graphtools +{ + +using StringPair = std::pair; + +/** + * Splits a string by the specified delimiter + */ +std::vector splitStringByDelimiter(const std::string& str, char sep = ' '); + +/** + * Splits a string by whitespace + */ +std::vector splitStringByWhitespace(const std::string& str); + +/** + * Return reversed sequence + * @param seq sequence to reverse + * @return reversed sequence + */ +static inline std::string reverseString(std::string seq) +{ + std::reverse(seq.begin(), seq.end()); + return seq; +} + +/** + * Return reverse complemented sequence + * @param seq sequence to reverse-complement + * @return reverse complemented sequence + */ +std::string reverseComplement(std::string seq); + +/** + * Returns true if sequence consists of uppercase symbols over extended nucleotide alphabet + */ +bool checkIfReferenceSequence(const std::string& sequence); + +/** + * Checks if sequence consists of uppercase As, Ts, Cs, and Gs + */ +bool checkIfNucleotideReferenceSequence(const std::string& sequence); + +/** + * Expands reference symbol into strings made up of matching nucleotides + */ +std::string const& expandReferenceSymbol(char reference_symbol); + +/** + * Expands reference sequence by expanding each degenerate symbol + * + * @param sequence Reference sequence to expand + * @return All nucleotide sequences matching the input sequence + */ +void expandReferenceSequence(const std::string& sequence, std::vector& target); +} \ No newline at end of file diff --git a/thirdparty/graph-tools-master/src/graphIO/BamWriter.cpp b/thirdparty/graph-tools-master/src/graphIO/BamWriter.cpp new file mode 100755 index 0000000..dbefbca --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphIO/BamWriter.cpp @@ -0,0 +1,181 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Felix Schlesinger +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphIO/BamWriter.hh" + +#include +#include +#include + +// cppcheck-suppress missingInclude +#include "htslib/bgzf.h" +// cppcheck-suppress missingInclude +#include "htslib/kseq.h" +// cppcheck-suppress missingInclude +#include "htslib/sam.h" + +using std::string; + +// Set a base in the bit-backed read sequence representation of a BAM record. (4 bits per base). +// Sets position i to base c in sequence s. +#define bam1_seq_seti(s, i, c) ((s)[(i) >> 1] = ((s)[(i) >> 1] & 0xf << (((i)&1) << 2)) | (c) << ((~(i)&1) << 2)) + +namespace graphIO +{ + +string const BamWriter::initHeader = "@HD\tVN:1.4\tSO:unknown\n"; +string const BamWriter::graphCigarBamTag = "XG"; + +BamWriter::BamWriter(string const& bamPath, ReferenceContigs& contigs) + : filePtr_(hts_open(bamPath.c_str(), "wb"), hts_close) + , bamHeader_(bam_hdr_init(), bam_hdr_destroy) +{ + if (writeHeader(initHeader, contigs) != 0) + { + throw std::logic_error("Failed to write header"); + } +} + +int BamWriter::writeHeader(std::string const& initHeader, ReferenceContigs& contigs) +{ + bamHeader_->l_text = strlen(initHeader.c_str()); + bamHeader_->text = strdup(initHeader.c_str()); + bamHeader_->n_targets = contigs.size(); + + // All this memory gets freed by the header (bam_hdr_destroy) + bamHeader_->target_name = new char*[contigs.size()]; + bamHeader_->target_len = new uint32_t[contigs.size()]; + for (size_t i = 0; i != contigs.size(); ++i) + { + bamHeader_->target_name[i] = new char[contigs[i].first.length() + 1]; + memcpy(bamHeader_->target_name[i], contigs[i].first.c_str(), contigs[i].first.length() + 1); + bamHeader_->target_len[i] = contigs[i].second; + } + return bam_hdr_write(filePtr_->fp.bgzf, bamHeader_.get()); +} + +void BamWriter::writeAlignment(BamAlignment& align) +{ + bam1_t* q = bam_init1(); + + if (align.chromName.empty()) + { + q->core.tid = -1; + } + else + { + q->core.tid = bam_name2id(bamHeader_.get(), align.chromName.c_str()); + if (q->core.tid == -1) + { + throw std::logic_error("Unknown contig name " + align.chromName); + } + } + q->core.pos = align.pos; + q->core.mtid = -1; + q->core.mpos = -1; + q->core.flag = BAM_FUNMAP; + if (align.isPaired) + { + q->core.flag += BAM_FPAIRED + BAM_FMUNMAP; + q->core.flag += align.isMate1 ? BAM_FREAD1 : BAM_FREAD2; + } + q->core.l_qname = align.fragmentName.length() + 1; // +1 includes the tailing '\0' + q->core.l_qseq = align.sequence.length(); + q->core.n_cigar = 0; // we have no cigar sequence + + //`q->data` structure: qname-cigar-seq-qual-aux + int seqQualLength = (int)(1.5 * align.sequence.length() + (align.sequence.length() % 2 != 0)); + q->l_data = q->core.l_qname + seqQualLength; + q->m_data = q->l_data; + kroundup32(q->m_data); + q->data = (uint8_t*)realloc(q->data, q->m_data); + memcpy(q->data, align.fragmentName.c_str(), q->core.l_qname); // first set qname + uint8_t* s = bam_get_seq(q); + for (int i = 0; i < q->core.l_qseq; ++i) + { + bam1_seq_seti(s, i, seq_nt16_table[(unsigned char)align.sequence[i]]); + } + s = bam_get_qual(q); + if (!align.BaseQualities.empty() && align.BaseQualities.size() != align.sequence.length()) + { + throw std::logic_error("Mismatched sequence and quality lengths"); + } + for (unsigned i = 0; i < align.sequence.length(); ++i) + { + s[i] = align.BaseQualities.empty() ? 0xFF : align.BaseQualities[i]; + } + if (!align.graphCigar.empty()) + { + string data(align.graphCigar); + bam_aux_append( + q, BamWriter::graphCigarBamTag.c_str(), 'Z', align.graphCigar.length() + 1, + reinterpret_cast(&data[0])); + } + if (bam_write1(filePtr_->fp.bgzf, q) == 0) + { + throw std::logic_error("Cannot write alignment"); + } + bam_destroy1(q); +} + +BamAlignment BamWriter::makeAlignment( + string const& fragmentName, Sequence const& sequence, std::vector const& qualities, + BamWriter::PairingInfo pairing, string const& graphAlign) const +{ + BamAlignment align; + align.fragmentName = fragmentName; + align.sequence = sequence; + align.BaseQualities == qualities; + align.graphCigar = graphAlign; + align.isPaired = (pairing != BamWriter::PairingInfo::Unpaired); + align.isMate1 = (pairing == BamWriter::PairingInfo::FirstMate); + return align; +} + +BamAlignment BamWriter::makeAlignment( + GraphReferenceMapping const& refMap, string const& fragmentName, Sequence const& sequence, + std::vector const& qualities, BamWriter::PairingInfo pairing, GraphAlignment const& graphAlign) const +{ + BamAlignment bamAlign; + bamAlign.fragmentName = fragmentName; + bamAlign.sequence = sequence; + bamAlign.BaseQualities = qualities; + bamAlign.isPaired = (pairing != BamWriter::PairingInfo::Unpaired); + bamAlign.isMate1 = (pairing == BamWriter::PairingInfo::FirstMate); + auto refPos = refMap.map(graphAlign.path()); + if (refPos) + { + bamAlign.chromName = refPos->contig; + bamAlign.pos = refPos->start; + } + std::stringstream graphCigar; + graphCigar << graphAlign; + bamAlign.graphCigar = graphCigar.str(); + return bamAlign; +} +} \ No newline at end of file diff --git a/thirdparty/graph-tools-master/src/graphIO/CMakeLists.txt b/thirdparty/graph-tools-master/src/graphIO/CMakeLists.txt new file mode 100755 index 0000000..e020330 --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphIO/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.5.0) + +include(GetHtslib) + +set(SOURCES "GraphJson.cpp" "ReferenceGenome.cpp" BamWriter.cpp) +add_library(graphIO ${SOURCES}) +target_include_directories(graphIO PUBLIC "../../include") +target_include_directories(graphIO PUBLIC "../../external/include") + +target_link_libraries(graphIO Boost::filesystem htslib) \ No newline at end of file diff --git a/thirdparty/graph-tools-master/src/graphIO/GraphJson.cpp b/thirdparty/graph-tools-master/src/graphIO/GraphJson.cpp new file mode 100755 index 0000000..eded140 --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphIO/GraphJson.cpp @@ -0,0 +1,160 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Felix Schlesinger +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphIO/GraphJson.hh" + +#include +#include + +#include "graphIO/ReferenceGenome.hh" + +using std::string; + +namespace graphIO +{ + +Graph loadGraph(string const& jsonPath) +{ + std::ifstream jsonFile(jsonPath); + assert(jsonFile.good()); + Json json; + jsonFile >> json; + return parseGraph((json.count("graph") == 1) ? json["graph"] : json); +} + +Graph parseGraph(Json const& jsonGraph) +{ + boost::optional genome; + const auto refFasta = jsonGraph.find("reference_genome"); + if (refFasta != jsonGraph.end()) + { + genome.emplace(refFasta->get()); + } + + Json::array_t nodes = jsonGraph.value("nodes", Json::array()); + auto const nNodes = nodes.size(); + string const& graphId = jsonGraph.value("graph_id", ""); + Graph graph(nNodes, graphId); + + std::unordered_map nodeIds; // NodeName -> NameID + NodeId nodeIndex = 0; + for (auto const& jsonNode : nodes) + { + string const name = jsonNode.at("name"); + graph.setNodeName(nodeIndex, name); + auto const seq = jsonNode.find("sequence"); + if (seq != jsonNode.end()) + { + graph.setNodeSeq(nodeIndex, *seq); + } + else + { + auto const refRegion = jsonNode.find("reference"); + if (refRegion == jsonNode.end()) + { + throw std::runtime_error("Node has no sequence: " + graph.nodeName(nodeIndex)); + } + if (!genome) + { + throw std::runtime_error("Need 'referenceGenome' FASTA file to use reference nodes"); + } + auto const interval = ReferenceInterval::parseRegion(*refRegion); + graph.setNodeSeq(nodeIndex, genome->extractSeq(interval)); + } + assert(nodeIds.count(name) == 0); + nodeIds[name] = nodeIndex; + nodeIndex++; + } + for (auto const& jsonEdge : jsonGraph.value("edges", Json::array())) + { + NodeId const sourceNode = nodeIds.at(jsonEdge.at("from")); + NodeId const sinkNode = nodeIds.at(jsonEdge.at("to")); + graph.addEdge(sourceNode, sinkNode); + const auto labels = jsonEdge.find("labels"); + if (labels != jsonEdge.end()) + { + for (string const& label : *labels) + { + graph.addLabelToEdge(sourceNode, sinkNode, label); + } + } + } + return graph; +} + +Json graphToJson(Graph const& graph) +{ + Json json; + if (!graph.graphId.empty()) + { + json["graph_id"] = graph.graphId; + } + json["nodes"] = Json::array(); + for (size_t i = 0; i != graph.numNodes(); ++i) + { + json["nodes"].push_back({ { "name", graph.nodeName(i) }, { "sequence", graph.nodeSeq(i) } }); + } + json["edges"] = Json::array(); + for (size_t n1 = 0; n1 < graph.numNodes(); ++n1) + { + for (NodeId n2 : graph.successors(n1)) + { + Json edge = { { "from", graph.nodeName(n1) }, { "to", graph.nodeName(n2) } }; + auto const& labels = graph.edgeLabels(n1, n2); + if (labels.size() > 0) + { + edge["labels"] = labels; + } + + json["edges"].push_back(std::move(edge)); + } + } + return json; +} + +GraphReferenceMapping parseReferenceMapping(Json const& jRefmap, Graph const& graph) +{ + const string refFasta = jRefmap.at("reference_genome"); + RefGenome genome(refFasta); + Json::array_t nodes = jRefmap.value("nodes", Json::array()); + + GraphReferenceMapping refmap(&graph); + NodeId nodeIndex = 0; + for (auto const& jNode : nodes) + { + const auto refInterval = jNode.find("reference"); + if (refInterval != jNode.end()) + { + auto const region = ReferenceInterval::parseRegion(*refInterval); + refmap.addMapping(nodeIndex, region); + } + ++nodeIndex; + } + return refmap; +} +} \ No newline at end of file diff --git a/thirdparty/graph-tools-master/src/graphIO/ReferenceGenome.cpp b/thirdparty/graph-tools-master/src/graphIO/ReferenceGenome.cpp new file mode 100755 index 0000000..302bfba --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphIO/ReferenceGenome.cpp @@ -0,0 +1,64 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Felix Schlesinger +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphIO/ReferenceGenome.hh" + +#include +#include +#include +#include +#include +#include +#include + +using std::string; + +namespace graphIO +{ + +RefGenome::RefGenome(string const& fastaPath) + : fastaPath_(fastaPath) + , fai_(fai_load(fastaPath.c_str()), fai_destroy) +{ +} + +string RefGenome::extractSeq(ReferenceInterval const& interval) const +{ + int len; + // pass end - 1 since htslib includes the last base, but our interval object excludes it + std::unique_ptr refTmp( + faidx_fetch_seq(fai_.get(), interval.contig.c_str(), interval.start, interval.end - 1, &len)); + if (!refTmp || len == -1 || len == -2 || len != interval.length()) + { + throw std::runtime_error((boost::format("ERROR: can't extract %1% from %2%") % interval % fastaPath_).str()); + } + string seq(refTmp.get(), len); + std::transform(seq.begin(), seq.end(), seq.begin(), ::toupper); + return seq; +} +} \ No newline at end of file diff --git a/thirdparty/graph-tools-master/src/graphalign/GaplessAligner.cpp b/thirdparty/graph-tools-master/src/graphalign/GaplessAligner.cpp new file mode 100755 index 0000000..d0c7ad4 --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphalign/GaplessAligner.cpp @@ -0,0 +1,155 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/GaplessAligner.hh" + +#include +#include +#include + +#include "graphcore/PathOperations.hh" +#include "graphutils/BaseMatching.hh" +#include "graphutils/SequenceOperations.hh" + +using std::list; +using std::string; +using std::vector; + +namespace graphtools +{ +list GaplessAligner::align(const string& query) const +{ + const list kmers = extractKmersFromAllPositions(query, kmer_len_); + + int32_t pos = 0; + for (const string& kmer : kmers) + { + // Initiate alignment from a unique kmer. + if (kmer_index_.numPaths(kmer) == 1) + { + const Path kmer_path = kmer_index_.getPaths(kmer).front(); + return getBestAlignmentToShortPath(kmer_path, pos, query); + } + ++pos; + } + return {}; +} + +list getBestAlignmentToShortPath(const Path& path, int32_t start_pos, const string& query) +{ + const int32_t start_extension = start_pos; + const auto end_extension = static_cast(query.length() - start_pos - path.length()); + const list full_paths = extendPath(path, start_extension, end_extension); + + list best_alignments; + int32_t max_matches = -1; + + for (const Path& full_path : full_paths) + { + GraphAlignment alignment = alignWithoutGaps(full_path, query); + if (static_cast(alignment.numMatches()) > max_matches) + { + max_matches = alignment.numMatches(); + best_alignments = { alignment }; + } + else if (static_cast(alignment.numMatches()) == max_matches) + { + best_alignments.push_back(alignment); + } + } + + return best_alignments; +} + +GraphAlignment alignWithoutGaps(const Path& path, const string& query) +{ + vector query_pieces = splitSequenceByPath(path, query); + vector alignments; + + const Graph* graph_ptr = path.graphRawPtr(); + size_t index = 0; + for (auto node_id : path.nodeIds()) + { + const string& node_seq = graph_ptr->nodeSeq(node_id); + const string query_piece = query_pieces[index]; + const int32_t ref_start = index == 0 ? path.startPosition() : 0; + alignments.push_back(alignWithoutGaps(ref_start, node_seq, query_piece)); + ++index; + } + + return GraphAlignment(path, alignments); +} + +Alignment alignWithoutGaps(int32_t ref_start, const string& reference, const string& query) +{ + if (reference.length() < ref_start + query.length()) + { + throw std::logic_error( + "Gapless alignment requires that sequences " + query + " and " + reference + " have same length."); + } + + if (query.empty() || reference.empty()) + { + throw std::logic_error("Cannot align empty sequences"); + } + + list operations; + size_t previous_run_end = 0; + size_t run_length = 0; + char run_operation = '\0'; + for (size_t index = 0; index != query.length(); ++index) + { + char cur_operation = 'X'; + if (checkIfReferenceBaseMatchesQueryBase(reference[ref_start + index], query[index])) + { + cur_operation = 'M'; + } + + if (cur_operation == run_operation) + { + ++run_length; + } + else + { + if (run_operation != '\0') + { + OperationType operation_type = decodeOperationType(run_operation); + operations.emplace_back(operation_type, run_length); + } + previous_run_end += run_length; + run_length = 1; + run_operation = cur_operation; + } + } + + OperationType operation_type = decodeOperationType(run_operation); + operations.emplace_back(operation_type, run_length); + + return Alignment(ref_start, operations); +} +} diff --git a/thirdparty/graph-tools-master/src/graphalign/GappedAligner.cpp b/thirdparty/graph-tools-master/src/graphalign/GappedAligner.cpp new file mode 100755 index 0000000..e77d0ee --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphalign/GappedAligner.cpp @@ -0,0 +1,256 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko , +// Roman Petrovski +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/GappedAligner.hh" + +#include "graphalign/GraphAlignmentOperations.hh" +#include "graphalign/LinearAlignmentOperations.hh" +#include "graphcore/PathOperations.hh" + +using std::list; +using std::make_pair; +using std::string; +using std::to_string; + +namespace graphtools +{ + +/** + * Remove prefix of a path if it overlaps multiple nodes + * + * @param prefixLength: length of the prefix of the path to evaluate + * @param[in|out] path: any path + * @return: the length by which the path's beginning was trimmed + */ +static int32_t removePrefixThatOverlapsMultipleNodes(int32_t prefixLength, Path& path) +{ + if (path.numNodes() == 1) + { + return 0; + } + + const int32_t overlapLengthWithFirstNode = path.getNodeOverlapLengthByIndex(0); + + if (overlapLengthWithFirstNode <= prefixLength) + { + if ((int32_t)path.length() >= prefixLength) + { + path.shrinkStartBy(prefixLength); + return prefixLength; + } + } + + return 0; +} + +/** + * Remove suffix of a path if it overlaps multiple nodes + * + * @param suffixLength: length of the suffix of the path to evaluate + * @param[in|out] path: any path + * @return: the length by which the path's end was trimmed + */ +static int32_t removeSuffixThatOverlapsMultipleNodes(int32_t suffixLength, Path& path) +{ + if (path.numNodes() == 1) + { + return 0; + } + + const int32_t overlapLengthWithLastNode = path.getNodeOverlapLengthByIndex(path.numNodes() - 1); + + if (overlapLengthWithLastNode <= suffixLength) + { + if ((int32_t)path.length() >= suffixLength) + { + path.shrinkEndBy(suffixLength); + return suffixLength; + } + } + + return 0; +} + +list GappedGraphAligner::align(const string& query) const +{ + const list kmers = extractKmersFromAllPositions(query, kmer_len_); + + size_t kmer_start_on_query = 0; + for (const string& kmer : kmers) + { + // Initiate alignment from a unique kmer. + if (kmer_index_.numPaths(kmer) == 1) + { + Path kmer_path = kmer_index_.getPaths(kmer).front(); + removeSuffixThatOverlapsMultipleNodes(seed_affix_trim_len_, kmer_path); + const int32_t num_prefix_bases_trimmed + = removePrefixThatOverlapsMultipleNodes(seed_affix_trim_len_, kmer_path); + return extendKmerMatchToFullAlignments(kmer_path, query, kmer_start_on_query + num_prefix_bases_trimmed); + } + ++kmer_start_on_query; + } + + return {}; +} + +list GappedGraphAligner::extendKmerMatchToFullAlignments( + Path kmer_path, const string& query, size_t kmer_start_on_query) const +{ + assert(kmer_path.length() > 1); + + // Generate prefix extensions + list prefix_extensions; + size_t query_prefix_len = kmer_start_on_query; + if (query_prefix_len != 0) + { + const string query_prefix = query.substr(0, query_prefix_len); + Path prefix_seed_path = kmer_path; + prefix_seed_path.shrinkEndBy(kmer_path.length()); + prefix_extensions = extendAlignmentPrefix(prefix_seed_path, query_prefix, query_prefix_len + padding_len_); + } + else + { + // Because (a) empty alignments are currently disallowed and (b) we don't want to deal with an empty list of + // prefix_extensions we create a 1bp prefix artificially. + query_prefix_len = 1; + Path prefix_path = kmer_path; + prefix_path.shrinkEndBy(prefix_path.length() - 1); + prefix_extensions = { make_pair(prefix_path, Alignment(0, "1M")) }; + kmer_path.shrinkStartBy(1); + } + + // Generate suffix extensions + list suffix_extensions; + size_t query_suffix_len = query.length() - kmer_path.length() - query_prefix_len; + if (query_suffix_len != 0) + { + const string query_suffix = query.substr(query_prefix_len + kmer_path.length(), query_suffix_len); + Path suffix_seed_path = kmer_path; + suffix_seed_path.shrinkStartBy(kmer_path.length()); + suffix_extensions = extendAlignmentSuffix(suffix_seed_path, query_suffix, query_suffix_len + padding_len_); + } + else + { + // Because (a) empty alignments are currently disallowed and (b) we don't want to deal with an empty list of + // suffix_extensions we create a 1bp suffix artificially. + Path suffix_path = kmer_path; + suffix_path.shrinkStartBy(suffix_path.length() - 1); + suffix_extensions = { make_pair(suffix_path, Alignment(0, "1M")) }; + kmer_path.shrinkEndBy(1); + } + + // Merge alignments together + list top_paths_and_alignments; + for (PathAndAlignment& prefix_path_and_alignment : prefix_extensions) + { + Path& prefix_path = prefix_path_and_alignment.first; + Path prefix_plus_kmer_path = concatenatePaths(prefix_path, kmer_path); + + Alignment& prefix_alignment = prefix_path_and_alignment.second; + Alignment kmer_alignment(prefix_alignment.referenceLength(), to_string(kmer_path.length()) + "M"); + Alignment prefix_plus_kmer_alignment = mergeAlignments(prefix_alignment, kmer_alignment); + + for (PathAndAlignment& suffix_path_and_alignment : suffix_extensions) + { + Path& suffix_path = suffix_path_and_alignment.first; + Alignment& suffix_alignment = suffix_path_and_alignment.second; + Path full_path = concatenatePaths(prefix_plus_kmer_path, suffix_path); + + suffix_alignment.setReferenceStart(prefix_plus_kmer_path.length()); + Alignment full_alignment = mergeAlignments(prefix_plus_kmer_alignment, suffix_alignment); + top_paths_and_alignments.push_back(make_pair(full_path, full_alignment)); + } + } + + list top_graph_alignments; + for (PathAndAlignment& path_and_alignment : top_paths_and_alignments) + { + Path& path = path_and_alignment.first; + Alignment& alignment = path_and_alignment.second; + top_graph_alignments.push_back(projectAlignmentOntoGraph(alignment, path)); + } + + top_graph_alignments.sort(); + top_graph_alignments.unique(); + return top_graph_alignments; +} + +list +GappedGraphAligner::extendAlignmentPrefix(const Path& seed_path, const string& query_piece, size_t extension_len) const +{ + assert(seed_path.length() == 0); + + int32_t top_alignment_score = INT32_MIN; + list top_paths_and_alignments + = aligner_.suffixAlign(seed_path, query_piece, extension_len, top_alignment_score); + + for (PathAndAlignment& path_and_alignment : top_paths_and_alignments) + { + Path& path = path_and_alignment.first; + Alignment& alignment = path_and_alignment.second; + alignment.setReferenceStart(0); + + const int32_t overhang = path.length() - alignment.referenceLength(); + path.shrinkStartBy(overhang); + + if (!checkConsistency(path_and_alignment.second, path.seq(), query_piece)) + { + throw std::logic_error("Inconsistent prefix"); + } + } + + return top_paths_and_alignments; +} + +list +GappedGraphAligner::extendAlignmentSuffix(const Path& seed_path, const string& query_piece, size_t extension_len) const +{ + assert(seed_path.length() == 0); + + int32_t top_alignment_score = INT32_MIN; + list top_paths_and_alignments + = aligner_.prefixAlign(seed_path, query_piece, extension_len, top_alignment_score); + + for (PathAndAlignment& path_and_alignment : top_paths_and_alignments) + { + if (!checkConsistency(path_and_alignment.second, path_and_alignment.first.seq(), query_piece)) + { + throw std::logic_error("Inconsistent suffix"); + } + + Path& path = path_and_alignment.first; + Alignment& alignment = path_and_alignment.second; + + const int32_t overhang = path.length() - alignment.referenceLength(); + path.shrinkEndBy(overhang); + } + + return top_paths_and_alignments; +} +} diff --git a/thirdparty/graph-tools-master/src/graphalign/GraphAlignment.cpp b/thirdparty/graph-tools-master/src/graphalign/GraphAlignment.cpp new file mode 100755 index 0000000..bee7e5d --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphalign/GraphAlignment.cpp @@ -0,0 +1,229 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include + +#include "graphalign/GraphAlignment.hh" +#include "graphalign/LinearAlignmentOperations.hh" +#include "graphcore/PathOperations.hh" + +using std::list; +using std::map; +using std::string; +using std::to_string; + +namespace graphtools +{ +void GraphAlignment::assertValidity() const +{ + for (size_t node_index = 0; node_index != path_.numNodes(); ++node_index) + { + const Alignment& node_alignment = alignments_[node_index]; + + const bool is_start_wrong + = path_.getStartPositionOnNodeByIndex(node_index) != (int32_t)node_alignment.referenceStart(); + + const int32_t node_alignment_end = node_alignment.referenceLength() + node_alignment.referenceStart(); + const bool is_end_wrong = path_.getEndPositionOnNodeByIndex(node_index) != node_alignment_end; + + if (is_start_wrong || is_end_wrong) + { + std::ostringstream graph_alignment_encoding; + graph_alignment_encoding << *this; + throw std::logic_error( + "Path " + path_.encode() + " is not compatible with graph alignment " + graph_alignment_encoding.str()); + } + } +} + +uint32_t GraphAlignment::queryLength() const +{ + uint32_t query_span = 0; + for (const auto& alignment : alignments_) + { + query_span += alignment.queryLength(); + } + return query_span; +} + +uint32_t GraphAlignment::referenceLength() const +{ + uint32_t reference_span = 0; + for (const auto& alignment : alignments_) + { + reference_span += alignment.referenceLength(); + } + return reference_span; +} + +uint32_t GraphAlignment::numMatches() const +{ + uint32_t num_matches = 0; + for (const auto& alignment : alignments_) + { + num_matches += alignment.numMatched(); + } + return num_matches; +} + +bool GraphAlignment::overlapsNode(NodeId node_id) const +{ + return path_.checkOverlapWithNode(static_cast(node_id)); +} + +list GraphAlignment::getIndexesOfNode(NodeId node_id) const +{ + list indexes; + const auto num_alignments = static_cast(alignments_.size()); + for (int32_t node_index = 0; node_index != num_alignments; ++node_index) + { + const NodeId cur_node_id = path_.getNodeIdByIndex(static_cast(node_index)); + if (cur_node_id == node_id) + { + indexes.push_back(node_index); + } + } + return indexes; +} + +string GraphAlignment::generateCigar() const +{ + string graph_cigar; + for (int32_t index = 0; index != (int32_t)size(); ++index) + { + const int32_t node_id = path_.getNodeIdByIndex(static_cast(index)); + graph_cigar += std::to_string(node_id); + const Alignment& alignment = alignments_[index]; + graph_cigar += "[" + alignment.generateCigar() + "]"; + } + return graph_cigar; +} + +bool GraphAlignment::operator<(const GraphAlignment& other) const +{ + if (!(path_ == other.path_)) + { + return path_ < other.path_; + } + + return alignments_ < other.alignments_; +} + +void GraphAlignment::shrinkStart(int reference_length) +{ + int prefix_query_length = 0; + + if (reference_length >= (int)referenceLength()) + { + std::stringstream string_stream; + string_stream << "Cannot shrink start of " << *this << " by " << reference_length; + throw std::logic_error(string_stream.str()); + } + + path_.shrinkStartBy(reference_length); + + size_t leftover_reference_length = reference_length; + + auto first_suffix_alignment_iter = alignments_.begin(); + while (leftover_reference_length >= first_suffix_alignment_iter->referenceLength()) + { + leftover_reference_length -= first_suffix_alignment_iter->referenceLength(); + prefix_query_length += first_suffix_alignment_iter->queryLength(); + ++first_suffix_alignment_iter; + } + + if (leftover_reference_length != 0) + { + const int split_position = first_suffix_alignment_iter->referenceStart() + leftover_reference_length; + Alignment suffix = first_suffix_alignment_iter->splitAtReferencePosition(split_position); + prefix_query_length += first_suffix_alignment_iter->queryLength(); + *first_suffix_alignment_iter = suffix; + } + + Alignment softclipAlignment(first_suffix_alignment_iter->referenceStart(), to_string(prefix_query_length) + "S"); + *first_suffix_alignment_iter = mergeAlignments(softclipAlignment, *first_suffix_alignment_iter); + + alignments_.erase(alignments_.begin(), first_suffix_alignment_iter); + + assertValidity(); +} + +void GraphAlignment::shrinkEnd(int reference_length) +{ + int suffix_query_length = 0; + + if (reference_length >= (int)referenceLength()) + { + std::stringstream string_stream; + string_stream << "Cannot shrink start of " << *this << " by " << reference_length; + throw std::logic_error(string_stream.str()); + } + + path_.shrinkEndBy(reference_length); + + size_t leftover_reference_length = reference_length; + + auto last_prefix_alignment_iter = alignments_.end() - 1; + while (leftover_reference_length >= last_prefix_alignment_iter->referenceLength()) + { + leftover_reference_length -= last_prefix_alignment_iter->referenceLength(); + suffix_query_length += last_prefix_alignment_iter->queryLength(); + --last_prefix_alignment_iter; + } + + if (leftover_reference_length != 0) + { + const int split_position = last_prefix_alignment_iter->referenceStart() + + last_prefix_alignment_iter->referenceLength() - leftover_reference_length; + Alignment suffix = last_prefix_alignment_iter->splitAtReferencePosition(split_position); + suffix_query_length += suffix.queryLength(); + } + + auto last_prefix_alignment_reference_end + = last_prefix_alignment_iter->referenceStart() + last_prefix_alignment_iter->referenceLength(); + Alignment softclip_alignment(last_prefix_alignment_reference_end, to_string(suffix_query_length) + "S"); + *last_prefix_alignment_iter = mergeAlignments(*last_prefix_alignment_iter, softclip_alignment); + + alignments_.erase(last_prefix_alignment_iter + 1, alignments_.end()); + + assertValidity(); +} + +std::ostream& operator<<(std::ostream& out, const GraphAlignment& graph_alignment) +{ + for (int32_t node_index = 0; node_index != (int32_t)graph_alignment.size(); ++node_index) + { + const int32_t node_id = graph_alignment.getNodeIdByIndex(node_index); + out << node_id << "[" << graph_alignment[node_index] << "]"; + } + return out; +} + +} diff --git a/thirdparty/graph-tools-master/src/graphalign/GraphAlignmentOperations.cpp b/thirdparty/graph-tools-master/src/graphalign/GraphAlignmentOperations.cpp new file mode 100755 index 0000000..7c77086 --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphalign/GraphAlignmentOperations.cpp @@ -0,0 +1,236 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/GraphAlignmentOperations.hh" + +#include + +#include "graphalign/LinearAlignmentOperations.hh" + +using std::list; +using std::string; +using std::vector; + +namespace graphtools +{ + +bool checkConsistency(const GraphAlignment& graph_alignment, const string& query) +{ + NodeId node_index = 0; + uint32_t query_pos = 0; + const Graph* graph_ptr = graph_alignment.path().graphRawPtr(); + for (const auto& linear_alignment : graph_alignment) + { + if (query.length() < query_pos + linear_alignment.queryLength()) + { + return false; + } + const string query_piece = query.substr(query_pos, linear_alignment.queryLength()); + query_pos += linear_alignment.queryLength(); + + NodeId node_id = graph_alignment.getNodeIdByIndex(node_index); + const string& node_seq = graph_ptr->nodeSeq(node_id); + if (!checkConsistency(linear_alignment, node_seq, query_piece)) + { + return false; + } + + const bool query_or_reference_length_is_zero + = linear_alignment.referenceLength() == 0 || linear_alignment.queryLength() == 0; + + if (graph_alignment.path().numNodes() != 1 && query_or_reference_length_is_zero) + { + return false; + } + + ++node_index; + } + + return true; +} + +static vector splitGraphCigar(const string& graph_cigar) +{ + vector node_cigars; + string node_cigar; + for (size_t index = 0; index != graph_cigar.length(); ++index) + { + node_cigar += graph_cigar[index]; + if (node_cigar.back() == ']') + { + node_cigars.push_back(node_cigar); + node_cigar.clear(); + } + } + + return node_cigars; +} + +GraphAlignment decodeGraphAlignment(int32_t first_node_start, const string& graph_cigar, const Graph* graph_ptr) +{ + vector node_ids; + vector alignments; + vector node_cigars = splitGraphCigar(graph_cigar); + for (const string& node_cigar : node_cigars) + { + int32_t ref_pos = alignments.empty() ? first_node_start : 0; + + string cigar; + NodeId node_id; + splitNodeCigar(node_cigar, cigar, node_id); + node_ids.push_back(node_id); + + Alignment alignment(ref_pos, cigar); + alignments.push_back(alignment); + } + + // Convert to 0-based coordinates + int32_t last_node_end = alignments.back().referenceStart() + alignments.back().referenceLength(); + Path path(graph_ptr, first_node_start, node_ids, last_node_end); + return GraphAlignment(path, alignments); +} + +void splitNodeCigar(const string& node_cigar, string& cigar, NodeId& node_id) +{ + node_id = static_cast(-1); + string nodeid_encoding; + for (size_t index = 0; index != node_cigar.length(); ++index) + { + if (node_cigar[index] == '[') + { + node_id = static_cast(std::stoull(nodeid_encoding)); + cigar = node_cigar.substr(index + 1); + cigar.pop_back(); + return; + } + if (isdigit(node_cigar[index]) == 0) + { + throw std::logic_error(node_cigar + " is a malformed node CIGAR"); + } + nodeid_encoding += node_cigar[index]; + } +} + +// Implementation note: Unless stated otherwise, all calculations are performed in path coordinates. +GraphAlignment projectAlignmentOntoGraph(Alignment linear_alignment, Path path) +{ + vector alignments; + + path.shrinkBy( + linear_alignment.referenceStart(), + path.length() - linear_alignment.referenceStart() - linear_alignment.referenceLength()); + linear_alignment.setReferenceStart(0); + + for (size_t node_index = 0; node_index != path.numNodes(); ++node_index) + { + const size_t last_position_of_path_on_this_node = path.getNodeOverlapLengthByIndex(node_index); + + const size_t linear_alignment_last_position + = linear_alignment.referenceStart() + linear_alignment.referenceLength(); + + if (linear_alignment_last_position <= last_position_of_path_on_this_node) + { + alignments.push_back(linear_alignment); + break; + } + else + { + Alignment suffix = linear_alignment.splitAtReferencePosition(last_position_of_path_on_this_node); + alignments.push_back(linear_alignment); + linear_alignment = suffix; + linear_alignment.setReferenceStart(0); + } + } + + Alignment& first_alignment = alignments.front(); + first_alignment.setReferenceStart(path.startPosition()); + + return GraphAlignment(path, alignments); +} + +list getQuerySequencesForEachNode(const GraphAlignment& graph_alignment, const string& query) +{ + list sequence_pieces; + + uint32_t query_pos = 0; + for (const auto& linear_alignment : graph_alignment) + { + const string query_piece = query.substr(query_pos, linear_alignment.queryLength()); + sequence_pieces.push_back(query_piece); + query_pos += linear_alignment.queryLength(); + } + + return sequence_pieces; +} + +static string joinLinearAlignmentEncodings(const vector& linear_alignment_encodings) +{ + string graph_query_encoding, graph_match_pattern_encoding, graph_reference_encoding; + + for (const string& linear_alignment_encoding : linear_alignment_encodings) + { + if (!graph_query_encoding.empty()) + { + graph_query_encoding += ':'; + graph_match_pattern_encoding += ':'; + graph_reference_encoding += ':'; + } + + const vector query_pattern_reference_encodings + = splitStringByDelimiter(linear_alignment_encoding, '\n'); + + graph_query_encoding += query_pattern_reference_encodings[0]; + graph_match_pattern_encoding += query_pattern_reference_encodings[1]; + graph_reference_encoding += query_pattern_reference_encodings[2]; + } + + return graph_query_encoding + '\n' + graph_match_pattern_encoding + '\n' + graph_reference_encoding; +} + +string prettyPrint(const GraphAlignment& graph_alignment, const string& query) +{ + const list node_queries = getQuerySequencesForEachNode(graph_alignment, query); + + const Graph* graph_ptr = graph_alignment.path().graphRawPtr(); + + vector linear_alignment_encodings; + size_t node_index = 0; + for (const string& node_query : node_queries) + { + NodeId node_id = graph_alignment.getNodeIdByIndex(node_index); + const string& node_seq = graph_ptr->nodeSeq(node_id); + + const auto& linear_alignment = graph_alignment[node_index]; + linear_alignment_encodings.push_back(prettyPrint(linear_alignment, node_seq, node_query)); + + ++node_index; + } + + return joinLinearAlignmentEncodings(linear_alignment_encodings); +} +} diff --git a/thirdparty/graph-tools-master/src/graphalign/KmerIndex.cpp b/thirdparty/graph-tools-master/src/graphalign/KmerIndex.cpp new file mode 100755 index 0000000..d714b6f --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphalign/KmerIndex.cpp @@ -0,0 +1,263 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko , +// Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/KmerIndex.hh" + +#include +#include +#include +#include +#include +#include + +#include "graphcore/PathOperations.hh" +#include "graphutils/PairHashing.hh" +#include "graphutils/SequenceOperations.hh" + +#include + +using std::list; +using std::string; +using std::unordered_set; +using std::vector; + +namespace graphtools +{ + +struct KmerIndex::Impl +{ + explicit Impl(StringToPathsMap kmer_to_paths_map_); + explicit Impl(const Graph& graph, size_t kmer_len_); + void addKmerPathsStartingAtNode(const Graph& graph, NodeId node_id); + void addKmerPaths(const std::list& kmer_paths); + void updateKmerCounts(); + size_t kmer_len; + StringToPathsMap kmer_to_paths_map; + std::unordered_map node_kmer_counts; + std::unordered_map edge_kmer_counts; +}; + +KmerIndex::Impl::Impl(StringToPathsMap kmer_to_paths_map_) + : kmer_to_paths_map(std::move(kmer_to_paths_map_)) +{ + kmer_len = 0; + for (const auto& kv : kmer_to_paths_map) + { + const string& kmer = kv.first; + kmer_len = kmer.length(); + break; + } + updateKmerCounts(); +} + +KmerIndex::Impl::Impl(const Graph& graph, size_t kmer_len_) + : kmer_len(kmer_len_) +{ + for (NodeId node_id = 0; node_id != graph.numNodes(); ++node_id) + { + addKmerPathsStartingAtNode(graph, node_id); + } + updateKmerCounts(); +} + +void KmerIndex::Impl::addKmerPathsStartingAtNode(const Graph& graph, NodeId node_id) +{ + const string node_seq = graph.nodeSeq(node_id); + vector node_list; + node_list.push_back(node_id); + for (size_t pos = 0; pos != node_seq.length(); ++pos) + { + Path path(&graph, static_cast(pos), node_list, static_cast(pos)); + addKmerPaths(extendPath(path, 0, static_cast(kmer_len))); + } +} + +void KmerIndex::Impl::addKmerPaths(const list& kmer_paths) +{ + for (const Path& kmer_path : kmer_paths) + { + vector expanded_sequences; + if (kmer_path.graphRawPtr()->isSequenceExpansionRequired()) + { + expandReferenceSequence(kmer_path.seq(), expanded_sequences); + } + else + { + expanded_sequences = { kmer_path.seq() }; + } + for (const auto& expanded_kmer_seq : expanded_sequences) + { + kmer_to_paths_map[expanded_kmer_seq].push_back(kmer_path); + } + } +} + +void KmerIndex::Impl::updateKmerCounts() +{ + node_kmer_counts.clear(); + edge_kmer_counts.clear(); + for (const auto& kmer_and_paths : kmer_to_paths_map) + { + // kmer is unique + if (kmer_and_paths.second.size() == 1) + { + bool has_previous = false; + NodeId previous_node = 0; + for (auto const& path_node_id : kmer_and_paths.second.front().nodeIds()) + { + node_kmer_counts[path_node_id] += 1; + if (has_previous) + { + edge_kmer_counts[std::make_pair(previous_node, path_node_id)] += 1; + } + has_previous = true; + previous_node = path_node_id; + } + } + } +} + +KmerIndex::KmerIndex(const StringToPathsMap& kmer_to_paths_map) + : pimpl_(new Impl(kmer_to_paths_map)) +{ +} + +KmerIndex::KmerIndex(const Graph& graph, int32_t kmer_len) + : pimpl_(new Impl(graph, static_cast(kmer_len))) +{ +} + +KmerIndex::KmerIndex(const KmerIndex& other) + : pimpl_(new Impl(*other.pimpl_)) +{ +} + +KmerIndex::KmerIndex(KmerIndex&& other) noexcept + : pimpl_(std::move(other.pimpl_)) +{ +} + +KmerIndex& KmerIndex::operator=(const KmerIndex& other) +{ + if (this != &other) + { + pimpl_.reset(new Impl(*other.pimpl_)); + } + return *this; +} + +KmerIndex& KmerIndex::operator=(KmerIndex&& other) noexcept +{ + pimpl_ = std::move(other.pimpl_); + return *this; +} + +KmerIndex::~KmerIndex() = default; + +bool KmerIndex::operator==(const KmerIndex& other) const +{ + return (pimpl_->kmer_to_paths_map == other.pimpl_->kmer_to_paths_map && pimpl_->kmer_len == other.pimpl_->kmer_len); +} + +static string encodePaths(const list& paths) +{ + list path_encodings; + for (const auto& path : paths) + { + path_encodings.push_back(path.encode()); + } + return boost::algorithm::join(path_encodings, ","); +} + +size_t KmerIndex::kmerLength() const { return pimpl_->kmer_len; } + +string KmerIndex::encode() const +{ + list kv_encodings; + for (const auto& kv : pimpl_->kmer_to_paths_map) + { + const string encoding_of_paths = encodePaths(kv.second); + const string kv_encoding = "{" + kv.first + "->" + encoding_of_paths + "}"; + kv_encodings.push_back(kv_encoding); + } + return boost::algorithm::join(kv_encodings, ","); +} + +bool KmerIndex::contains(const std::string& kmer) const +{ + return pimpl_->kmer_to_paths_map.find(kmer) != pimpl_->kmer_to_paths_map.end(); +} + +size_t KmerIndex::numPaths(const std::string& kmer) const +{ + if (!contains(kmer)) + { + return 0; + } + return pimpl_->kmer_to_paths_map.at(kmer).size(); +} + +const list& KmerIndex::getPaths(const std::string& kmer) const { return pimpl_->kmer_to_paths_map.at(kmer); } + +unordered_set KmerIndex::kmers() const +{ + unordered_set kmers; + for (const auto& kv : pimpl_->kmer_to_paths_map) + { + kmers.insert(kv.first); + } + return kmers; +} + +size_t KmerIndex::numUniqueKmersOverlappingNode(NodeId node_id) const +{ + auto node_it = pimpl_->node_kmer_counts.find(node_id); + if (node_it != pimpl_->node_kmer_counts.end()) + { + return node_it->second; + } + return 0; +} + +size_t KmerIndex::numUniqueKmersOverlappingEdge(NodeId from, NodeId to) const +{ + auto edge_it = pimpl_->edge_kmer_counts.find(std::make_pair(from, to)); + if (edge_it != pimpl_->edge_kmer_counts.end()) + { + return edge_it->second; + } + return 0; +} + +std::ostream& operator<<(std::ostream& os, const KmerIndex& kmer_index) +{ + os << kmer_index.encode(); + return os; +} +} diff --git a/thirdparty/graph-tools-master/src/graphalign/KmerIndexOperations.cpp b/thirdparty/graph-tools-master/src/graphalign/KmerIndexOperations.cpp new file mode 100755 index 0000000..56ab379 --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphalign/KmerIndexOperations.cpp @@ -0,0 +1,121 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko , +// Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/KmerIndexOperations.hh" + +#include + +#include + +#include "graphutils/SequenceOperations.hh" + +using std::list; +using std::string; + +namespace graphtools +{ +list extractKmersFromAllPositions(const string& sequence, int32_t kmer_len) +{ + list kmers; + for (size_t pos = 0; pos + kmer_len <= sequence.length(); ++pos) + { + string kmer = sequence.substr(pos, static_cast(kmer_len)); + boost::to_upper(kmer); + kmers.push_back(kmer); + } + return kmers; +} + +int32_t countKmerMatches(const KmerIndex& kmer_index, const std::string& seq) +{ + const list kmers = extractKmersFromAllPositions(seq, kmer_index.kmerLength()); + int32_t num_kmer_matches = 0; + + for (const string& kmer : kmers) + { + if (kmer_index.numPaths(kmer) != 0) + { + ++num_kmer_matches; + } + } + return num_kmer_matches; +} + +bool checkIfForwardOriented(const KmerIndex& kmer_index, const std::string& sequence) +{ + const int32_t num_forward_matches = countKmerMatches(kmer_index, sequence); + const int32_t num_revcomp_matches = countKmerMatches(kmer_index, reverseComplement(sequence)); + return num_forward_matches >= num_revcomp_matches; +} + +/** + * Find minimum kmer length that covers each node with a unique kmer + * @param graph a graph + * @param min_unique_kmers_per_edge min number of unique kmers to cover each edge + * @param min_unique_kmers_per_node min number of unique kmers to cover each node + * @return + */ +int findMinCoveringKmerLength(Graph const* graph, size_t min_unique_kmers_per_edge, size_t min_unique_kmers_per_node) +{ + for (int32_t k = 10; k < 64; ++k) + { + KmerIndex index(*graph, k); + + bool any_below = false; + for (NodeId node_id = 0; node_id != graph->numNodes(); ++node_id) + { + if (index.numUniqueKmersOverlappingNode(node_id) < min_unique_kmers_per_node) + { + any_below = true; + break; + } + // this will enumerate all edges + for (const auto succ : graph->successors(node_id)) + { + if (index.numUniqueKmersOverlappingEdge(node_id, succ) < min_unique_kmers_per_edge) + { + any_below = true; + break; + } + } + if (any_below) + { + break; + } + } + if (any_below) + { + continue; + } + + return k; + } + return -1; +} +} diff --git a/thirdparty/graph-tools-master/src/graphalign/LinearAlignment.cpp b/thirdparty/graph-tools-master/src/graphalign/LinearAlignment.cpp new file mode 100755 index 0000000..85ac107 --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphalign/LinearAlignment.cpp @@ -0,0 +1,230 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/LinearAlignment.hh" + +#include +#include +#include +#include +#include +#include + +#include "graphalign/OperationOperations.hh" +#include "graphutils/BaseMatching.hh" + +using std::list; +using std::logic_error; +using std::map; +using std::string; +using std::to_string; +using std::vector; + +namespace graphtools +{ + +Alignment::Alignment(uint32_t reference_start, const string& cigar) + : reference_start_(reference_start) +{ + decodeCigar(cigar); + updateCounts(); +} + +void Alignment::updateCounts() +{ + clipped_ = 0; + matched_ = 0; + mismatched_ = 0; + missing_ = 0; + inserted_ = 0; + deleted_ = 0; + for (const Operation& operation : operations_) + { + switch (operation.type()) + { + case OperationType::kSoftclip: + clipped_ += operation.length(); + break; + case OperationType::kMatch: + matched_ += operation.length(); + break; + case OperationType::kMismatch: + mismatched_ += operation.length(); + break; + case OperationType::kMissingBases: + missing_ += operation.length(); + break; + case OperationType::kInsertionToRef: + inserted_ += operation.length(); + break; + case OperationType::kDeletionFromRef: + deleted_ += operation.length(); + break; + } + } +} + +void Alignment::decodeCigar(const string& cigar) +{ + string length_encoding; + for (char c : cigar) + { + if (isalpha(c) != 0) + { + uint32_t operation_length = std::stoi(length_encoding); + OperationType operation_type = decodeOperationType(c); + operations_.emplace_back(operation_type, operation_length); + length_encoding.clear(); + } + else + { + if (isdigit(c) == 0) + { + throw logic_error(cigar + " is malformed CIGAR string"); + } + length_encoding += c; + } + } +} + +uint32_t Alignment::queryLength() const +{ + int32_t query_span = 0; + for (const auto& operation : operations_) + { + query_span += operation.queryLength(); + } + return query_span; +} + +uint32_t Alignment::referenceLength() const +{ + int32_t reference_span = 0; + for (const auto& operation : operations_) + { + reference_span += operation.referenceLength(); + } + return reference_span; +} + +string Alignment::generateCigar() const +{ + string cigar_string; + for (const auto& operation : operations_) + { + cigar_string += operation.generateCigar(); + } + return cigar_string; +} + +Alignment Alignment::splitAtReferencePosition(size_t reference_position) +{ + const size_t end_of_reference_positions = referenceStart() + referenceLength(); + if (reference_position == 0 || end_of_reference_positions <= reference_position) + { + std::ostringstream os; + os << *this; + throw logic_error("Cannot split " + os.str() + " at reference position " + to_string(reference_position)); + } + + size_t first_unused_position = reference_start_; + + list::const_iterator operation_it = operations_.begin(); + + while (operation_it != operations_.end()) + { + const size_t first_unused_position_after_applying_operation + = first_unused_position + operation_it->referenceLength(); + if (first_unused_position_after_applying_operation <= reference_position) + { + ++operation_it; + first_unused_position = first_unused_position_after_applying_operation; + } + else + { + break; + } + } + + if (operation_it == operations_.end()) + { + // Throw error (test first) + } + + if (first_unused_position == reference_position) + { + list suffix_operations; + suffix_operations.splice(suffix_operations.begin(), operations_, operation_it, operations_.end()); + + updateCounts(); + return Alignment(first_unused_position, suffix_operations); + } + else + { + const size_t first_piece_reference_length = reference_position - first_unused_position; + OperationPair prefix_suffix = splitByReferenceLength(*operation_it, first_piece_reference_length); + + list suffix_operations; + suffix_operations.splice(suffix_operations.begin(), operations_, ++operation_it, operations_.end()); + suffix_operations.push_front(prefix_suffix.second); + + operations_.pop_back(); + operations_.push_back(prefix_suffix.first); + + updateCounts(); + return Alignment(reference_position, suffix_operations); + } +} + +void Alignment::reverse(size_t reference_length) +{ + reference_start_ = reference_length - reference_start_ - referenceLength(); + std::reverse(operations_.begin(), operations_.end()); +} + +bool Alignment::operator<(const Alignment& other) const +{ + if (reference_start_ != other.reference_start_) + { + return reference_start_ < other.reference_start_; + } + + return operations_ < other.operations_; +} + +std::ostream& operator<<(std::ostream& os, const Alignment& alignment) +{ + os << "Ref start: " << alignment.referenceStart() << ", "; + for (const Operation& operation : alignment) + { + os << operation; + } + + return os; +} +} diff --git a/thirdparty/graph-tools-master/src/graphalign/LinearAlignmentOperations.cpp b/thirdparty/graph-tools-master/src/graphalign/LinearAlignmentOperations.cpp new file mode 100755 index 0000000..268ca0a --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphalign/LinearAlignmentOperations.cpp @@ -0,0 +1,234 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/LinearAlignmentOperations.hh" + +#include +#include +#include +#include +#include + +#include "graphalign/OperationOperations.hh" + +using std::list; +using std::logic_error; +using std::string; + +namespace graphtools +{ + +bool checkConsistency(const Alignment& alignment, const string& reference, const string& query) +{ + const bool does_alignment_span_whole_query = alignment.queryLength() == query.length(); + const bool is_alignment_within_reference + = alignment.referenceStart() + alignment.referenceLength() <= reference.length(); + + if (!does_alignment_span_whole_query || !is_alignment_within_reference) + { + return false; + } + + uint32_t query_pos = 0; + uint32_t ref_pos = alignment.referenceStart(); + + for (const auto& operation : alignment) + { + const string query_piece = query.substr(query_pos, operation.queryLength()); + const string reference_piece = reference.substr(ref_pos, operation.referenceLength()); + + if (!checkConsistency(operation, reference_piece, query_piece)) + { + return false; + } + + query_pos += operation.queryLength(); + ref_pos += operation.referenceLength(); + } + + return true; +} + +list getSequencesForEachOperation(const Alignment& alignment, const string& reference, const string& query) +{ + list sequence_pieces; + + uint32_t query_pos = 0; + uint32_t ref_pos = alignment.referenceStart(); + + for (const auto& operation : alignment) + { + const string reference_piece = reference.substr(ref_pos, operation.referenceLength()); + const string query_piece = query.substr(query_pos, operation.queryLength()); + sequence_pieces.emplace_back(reference_piece, query_piece); + + query_pos += operation.queryLength(); + ref_pos += operation.referenceLength(); + } + + return sequence_pieces; +} + +bool checkIfBookended(const Alignment& first_alignment, const Alignment& second_alignment) +{ + const size_t position_after_first_alignment_end + = first_alignment.referenceStart() + first_alignment.referenceLength(); + const bool are_adjacent = position_after_first_alignment_end == (size_t)second_alignment.referenceStart(); + + if (!are_adjacent) + { + return false; + } + + const bool is_first_alignment_ends_with_softclip = first_alignment.back().type() == OperationType::kSoftclip; + const bool is_second_alignment_starts_with_softclip = second_alignment.front().type() == OperationType::kSoftclip; + + if (is_first_alignment_ends_with_softclip || is_second_alignment_starts_with_softclip) + { + const bool is_first_alignment_fully_softclipped + = first_alignment.numOperations() == 1 && is_first_alignment_ends_with_softclip; + const bool is_second_alignment_fully_softclipped + = second_alignment.numOperations() == 1 && is_second_alignment_starts_with_softclip; + + return is_first_alignment_fully_softclipped || is_second_alignment_fully_softclipped; + } + + return true; +} + +Alignment mergeAlignments(const Alignment& first_alignment, const Alignment& second_alignment) +{ + if (!checkIfBookended(first_alignment, second_alignment)) + { + std::ostringstream msg; + msg << "Alignments " << first_alignment << " and " << second_alignment << " are not bookended"; + throw std::logic_error(msg.str()); + } + + list first_alignment_operations = first_alignment.operations(); + list second_alignment_operations = second_alignment.operations(); + + if (first_alignment_operations.back().type() == second_alignment_operations.front().type()) + { + const Operation last_operation_of_first_alignment = first_alignment_operations.back(); + first_alignment_operations.pop_back(); + + const Operation first_operations_of_second_alignment = second_alignment_operations.front(); + second_alignment_operations.pop_front(); + + uint32_t merged_operation_length + = last_operation_of_first_alignment.length() + first_operations_of_second_alignment.length(); + + first_alignment_operations.emplace_back(last_operation_of_first_alignment.type(), merged_operation_length); + } + + first_alignment_operations.splice(first_alignment_operations.end(), second_alignment_operations); + + return Alignment(first_alignment.referenceStart(), first_alignment_operations); +} + +int32_t scoreAlignment(const Alignment& alignment, int32_t match_score, int32_t mismatch_score, int32_t gap_score) +{ + int32_t score = 0; + + for (const Operation& operation : alignment) + { + switch (operation.type()) + { + case OperationType::kMatch: + score += match_score * operation.referenceLength(); + break; + case OperationType::kMismatch: + score += mismatch_score * operation.referenceLength(); + break; + case OperationType::kInsertionToRef: + score += gap_score * operation.queryLength(); + break; + case OperationType::kDeletionFromRef: + score += gap_score * operation.referenceLength(); + break; + default: + break; + } + } + + return score; +} + +string prettyPrint(const Alignment& alignment, const string& reference, const string& query) +{ + string reference_encoding, match_patten, query_encoding; + + const list sequence_pieces = getSequencesForEachOperation(alignment, reference, query); + auto matched_sequences_it = sequence_pieces.begin(); + + for (const auto& operation : alignment) + { + const string& operation_reference = matched_sequences_it->first; + const string& operation_query = matched_sequences_it->second; + + switch (operation.type()) + { + case OperationType::kDeletionFromRef: + reference_encoding += operation_reference; + query_encoding += string(operation.referenceLength(), '-'); + match_patten += string(operation.referenceLength(), ' '); + break; + + case OperationType::kInsertionToRef: + reference_encoding += string(operation.queryLength(), '-'); + match_patten += string(operation.queryLength(), ' '); + query_encoding += operation_query; + break; + + case OperationType::kMatch: + reference_encoding += operation_reference; + match_patten += string(operation.queryLength(), '|'); + query_encoding += operation_query; + break; + + case OperationType::kMismatch: + case OperationType::kMissingBases: + reference_encoding += operation_reference; + match_patten += string(operation.queryLength(), ' '); + query_encoding += operation_query; + break; + + case OperationType::kSoftclip: + reference_encoding += string(operation.queryLength(), '-'); + match_patten += string(operation.queryLength(), ' '); + query_encoding += operation_query; + break; + } + + ++matched_sequences_it; + } + + return reference_encoding + '\n' + match_patten + '\n' + query_encoding; +} +} diff --git a/thirdparty/graph-tools-master/src/graphalign/Operation.cpp b/thirdparty/graph-tools-master/src/graphalign/Operation.cpp new file mode 100755 index 0000000..dc79728 --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphalign/Operation.cpp @@ -0,0 +1,155 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/Operation.hh" + +#include +#include +#include +#include +#include +#include + +using std::logic_error; +using std::map; +using std::string; +using std::to_string; + +namespace graphtools +{ + +string Operation::generateCigar() const +{ + string cigar_string = to_string(length_); + + std::ostringstream os; + os << type_; + cigar_string += os.str(); + + return cigar_string; +} + +OperationType decodeOperationType(char type_encoding) +{ + switch (type_encoding) + { + case 'M': + return OperationType::kMatch; + case 'N': + return OperationType::kMissingBases; + case 'X': + return OperationType::kMismatch; + case 'I': + return OperationType::kInsertionToRef; + case 'D': + return OperationType::kDeletionFromRef; + case 'S': + return OperationType::kSoftclip; + default: + throw logic_error(to_string(type_encoding) + " is unknown CIGAR operation"); + } +} + +Operation::Operation(string cigar) +{ + type_ = decodeOperationType(cigar.back()); + cigar.pop_back(); + length_ = std::stoi(cigar); +} + +uint32_t Operation::referenceLength() const +{ + switch (type_) + { + case OperationType::kMatch: + case OperationType::kMismatch: + case OperationType::kMissingBases: + case OperationType::kDeletionFromRef: + return length_; + default: + return 0; + } +} + +uint32_t Operation::queryLength() const +{ + switch (type_) + { + case OperationType::kMatch: + case OperationType::kMismatch: + case OperationType::kMissingBases: + case OperationType::kInsertionToRef: + case OperationType::kSoftclip: + return length_; + default: + return 0; + } +} + +bool Operation::operator<(const Operation& other) const +{ + if (type_ != other.type_) + { + return type_ < other.type_; + } + + return length_ < other.length_; +} + +std::ostream& operator<<(std::ostream& os, OperationType operation_type) +{ + switch (operation_type) + { + case OperationType::kMatch: + os << 'M'; + break; + case OperationType::kMismatch: + os << 'X'; + break; + case OperationType::kInsertionToRef: + os << 'I'; + break; + case OperationType::kDeletionFromRef: + os << 'D'; + break; + case OperationType::kSoftclip: + os << 'S'; + break; + case OperationType::kMissingBases: + os << 'N'; + } + + return os; +} + +std::ostream& operator<<(std::ostream& os, const Operation& operation) +{ + os << operation.length() << operation.type(); + return os; +} +} diff --git a/thirdparty/graph-tools-master/src/graphalign/OperationOperations.cpp b/thirdparty/graph-tools-master/src/graphalign/OperationOperations.cpp new file mode 100755 index 0000000..e8c057b --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphalign/OperationOperations.cpp @@ -0,0 +1,123 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/OperationOperations.hh" + +#include +#include + +#include "graphutils/BaseMatching.hh" + +using std::string; + +namespace graphtools +{ +bool checkConsistency(const Operation& operation, const std::string& reference, const std::string& query) +{ + const bool is_query_full_length = query.length() == operation.length(); + const bool is_ref_full_length = reference.length() == operation.length(); + + switch (operation.type()) + { + case OperationType::kMatch: + if (is_query_full_length && checkIfReferenceAndQuerySequencesMatch(reference, query)) + return true; + return false; + + case OperationType::kMismatch: + if (is_query_full_length && query.length() == reference.length()) + { + bool found_matching_base = false; + for (size_t index = 0; index != query.length(); ++index) + { + if (checkIfReferenceBaseMatchesQueryBase(reference[index], query[index])) + { + found_matching_base = true; + } + } + if (!found_matching_base) + { + return true; + } + } + return false; + + case OperationType::kMissingBases: + if (query.length() == reference.length() && is_query_full_length) + { + bool found_non_n_base_in_query = false; + for (size_t index = 0; index != query.length(); ++index) + { + if (query[index] != 'N') + { + found_non_n_base_in_query = true; + } + } + if (!found_non_n_base_in_query) + { + return true; + } + } + return false; + + case OperationType::kDeletionFromRef: + if (query.empty() && !reference.empty() && is_ref_full_length) + return true; + return false; + + case OperationType::kInsertionToRef: + if (!query.empty() && reference.empty() && is_query_full_length) + return true; + return false; + + case OperationType::kSoftclip: + if (!query.empty() && reference.empty() && is_query_full_length) + return true; + return false; + } + + return false; +} + +OperationPair splitByReferenceLength(const Operation& operation, uint32_t prefix_reference_length) +{ + if (prefix_reference_length == 0 || operation.referenceLength() <= prefix_reference_length) + { + std::ostringstream os; + os << operation; + const string msg = os.str() + " cannot be split by reference length " + std::to_string(prefix_reference_length); + throw std::logic_error(msg); + } + + uint32_t suffix_reference_length = operation.referenceLength() - prefix_reference_length; + Operation prefix_operation(operation.type(), prefix_reference_length); + Operation suffix_operation(operation.type(), suffix_reference_length); + + return std::make_pair(prefix_operation, suffix_operation); +} +} \ No newline at end of file diff --git a/thirdparty/graph-tools-master/src/graphalign/PinnedAligner.cpp b/thirdparty/graph-tools-master/src/graphalign/PinnedAligner.cpp new file mode 100755 index 0000000..2b5912a --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphalign/PinnedAligner.cpp @@ -0,0 +1,139 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/PinnedAligner.hh" + +#include + +#include "graphalign/TracebackRunner.hh" +#include "graphutils/BaseMatching.hh" + +using std::string; + +namespace graphtools +{ +TracebackMatrix PinnedAligner::populateTracebackMatrix(const string& reference, const string& query) +{ + const size_t num_rows = query.length() + 1; + const size_t num_cols = reference.length() + 1; + + TracebackMatrix matrix(num_rows, num_cols); + + fillTopLeft(matrix); + fillTopRow(matrix); + fillLeftColumn(matrix); + fillBody(reference, query, matrix); + + return matrix; +} + +void PinnedAligner::fillTopLeft(TracebackMatrix& matrix) +{ + matrix.setScore(0, 0, 0); + matrix.setTracebackStep(0, 0, TracebackStep::kStop); +} + +void PinnedAligner::fillTopRow(TracebackMatrix& matrix) +{ + for (size_t col_index = 1; col_index != matrix.numCols(); ++col_index) + { + matrix.setScore(0, col_index, col_index * gap_score_); + matrix.setTracebackStep(0, col_index, TracebackStep::kLeft); + } +} + +void PinnedAligner::fillLeftColumn(TracebackMatrix& matrix) +{ + for (size_t row_index = 1; row_index != matrix.numRows(); ++row_index) + { + matrix.setScore(row_index, 0, row_index * gap_score_); + matrix.setTracebackStep(row_index, 0, TracebackStep::kTop); + } +} + +void PinnedAligner::fillBody(const string& reference, const string& query, TracebackMatrix& matrix) +{ + for (size_t row_index = 1; row_index != matrix.numRows(); ++row_index) + { + for (size_t col_index = 1; col_index != matrix.numCols(); ++col_index) + { + const bool do_bases_match + = checkIfReferenceBaseMatchesQueryBase(reference[col_index - 1], query[row_index - 1]); + fillBodyCell(matrix, row_index, col_index, do_bases_match); + } + } +} + +void PinnedAligner::fillBodyCell(TracebackMatrix& matrix, size_t row_index, size_t col_index, bool do_bases_match) +{ + const int32_t match_mismatch_score = do_bases_match ? match_score_ : mismatch_score_; + const TracebackStep traceback_step + = do_bases_match ? TracebackStep::kDiagonalMatch : TracebackStep::kDiagonalMismatch; + const int32_t no_gap_score = matrix.score(row_index - 1, col_index - 1) + match_mismatch_score; + matrix.setScore(row_index, col_index, no_gap_score); + matrix.setTracebackStep(row_index, col_index, traceback_step); + + const int32_t query_gap_score = matrix.score(row_index, col_index - 1) + gap_score_; + if (query_gap_score > matrix.score(row_index, col_index)) + { + matrix.setScore(row_index, col_index, query_gap_score); + matrix.setTracebackStep(row_index, col_index, TracebackStep::kLeft); + } + + const int32_t reference_gap_score = matrix.score(row_index - 1, col_index) + gap_score_; + if (reference_gap_score > matrix.score(row_index, col_index)) + { + matrix.setScore(row_index, col_index, reference_gap_score); + matrix.setTracebackStep(row_index, col_index, TracebackStep::kTop); + } +} + +Alignment PinnedAligner::prefixAlign(const string& reference, const string& query) +{ + TracebackMatrix matrix = populateTracebackMatrix(reference, query); + + size_t top_row_index, top_col_index; + matrix.locateTopScoringCell(top_row_index, top_col_index); + + TracebackRunner traceback_runner(matrix); + Alignment alignment = traceback_runner.runTraceback(top_row_index, top_col_index); + + return alignment; +} + +Alignment PinnedAligner::suffixAlign(string reference, string query) +{ + std::reverse(query.begin(), query.end()); + std::reverse(reference.begin(), reference.end()); + + Alignment alignment = prefixAlign(reference, query); + alignment.reverse(reference.length()); + + return alignment; +} +} diff --git a/thirdparty/graph-tools-master/src/graphalign/TracebackMatrix.cpp b/thirdparty/graph-tools-master/src/graphalign/TracebackMatrix.cpp new file mode 100755 index 0000000..902a4bc --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphalign/TracebackMatrix.cpp @@ -0,0 +1,186 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/TracebackMatrix.hh" + +#include +#include +#include +#include + +#include "graphutils/SequenceOperations.hh" + +using std::string; +using std::vector; + +namespace graphtools +{ + +TracebackStep decodeTracebackDirection(std::string encoding) +{ + if (encoding == "S") + { + return TracebackStep::kStop; + } + else if (encoding == "I") + { + return TracebackStep::kTop; + } + else if (encoding == "D") + { + return TracebackStep::kLeft; + } + else if (encoding == "M") + { + return TracebackStep::kDiagonalMatch; + } + else if (encoding == "X") + { + return TracebackStep::kDiagonalMismatch; + } + throw std::logic_error(encoding + " is an unknown traceback"); +} + +TracebackMatrix::TracebackMatrix(const string& encoding) +{ + vector lines = splitStringByDelimiter(encoding, '\n'); + num_rows_ = lines.size(); + num_cols_ = std::count(lines.front().begin(), lines.front().end(), '/'); + cells_.resize(num_rows_ * num_cols_); + + for (size_t row_index = 0; row_index != num_rows_; ++row_index) + { + const std::string& line = lines[row_index]; + std::vector words = splitStringByWhitespace(line); + for (size_t col_index = 0; col_index != num_cols_; ++col_index) + { + const std::string& word = words[col_index]; + std::vector tracedir_score_encodings = splitStringByDelimiter(word, '/'); + assert(tracedir_score_encodings.size() == 2); + const TracebackStep trace_dir = decodeTracebackDirection(tracedir_score_encodings.front()); + const int32_t score = std::stoi(tracedir_score_encodings.back()); + getCell(row_index, col_index) = TracebackMatrixCell(trace_dir, score); + } + } +} + +int32_t TracebackMatrix::score(size_t row_index, size_t col_index) const { return getCell(row_index, col_index).score; } + +void TracebackMatrix::setScore(size_t row_index, size_t col_index, int32_t score) +{ + getCell(row_index, col_index).score = score; +} + +TracebackStep TracebackMatrix::tracebackStep(size_t row_index, size_t col_index) const +{ + return getCell(row_index, col_index).direction; +} + +void TracebackMatrix::setTracebackStep(size_t row_index, size_t col_index, TracebackStep direction) +{ + getCell(row_index, col_index).direction = direction; +} + +void TracebackMatrix::locateTopScoringCell(size_t& top_row_index, size_t& top_col_index) const +{ + int32_t top_score = INT32_MIN; + + for (size_t row_index = 0; row_index != num_rows_; ++row_index) + { + for (size_t col_index = 0; col_index != num_cols_; ++col_index) + { + const int32_t cell_score = score(row_index, col_index); + if (top_score <= cell_score) + { + top_score = cell_score; + top_row_index = row_index; + top_col_index = col_index; + } + } + } +} + +bool TracebackMatrix::operator==(const TracebackMatrix& other) const +{ + return (num_rows_ == other.num_rows_ && num_cols_ == other.num_cols_ && cells_ == other.cells_); +} + +std::ostream& operator<<(std::ostream& out, const TracebackStep& trace_dir) +{ + switch (trace_dir) + { + case TracebackStep::kStop: + out << "S"; + break; + case TracebackStep::kTop: + out << "I"; + break; + case TracebackStep::kLeft: + out << "D"; + break; + case TracebackStep::kDiagonalMatch: + out << "M"; + break; + case TracebackStep::kDiagonalMismatch: + out << "X"; + break; + default: + out << "?"; + } + return out; +} + +std::ostream& operator<<(std::ostream& out, const TracebackMatrixCell& cell) +{ + out << cell.direction << "/" << cell.score; + return out; +} + +std::ostream& operator<<(std::ostream& out, const TracebackMatrix& matrix) +{ + out << "{"; + for (size_t row_index = 0; row_index != matrix.numRows(); ++row_index) + { + out << "{"; + for (size_t col_index = 0; col_index != matrix.numCols(); ++col_index) + { + out << matrix.tracebackStep(row_index, col_index) << "/" << matrix.score(row_index, col_index); + if (col_index != matrix.numCols() - 1) + { + out << ", "; + } + } + if (row_index != matrix.numRows() - 1) + { + out << "}, "; + } + } + out << "}}"; + return out; +} +} diff --git a/thirdparty/graph-tools-master/src/graphalign/TracebackRunner.cpp b/thirdparty/graph-tools-master/src/graphalign/TracebackRunner.cpp new file mode 100755 index 0000000..72608d9 --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphalign/TracebackRunner.cpp @@ -0,0 +1,144 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/TracebackRunner.hh" + +#include +#include +#include +#include + +using std::string; +using std::vector; + +namespace graphtools +{ + +Alignment TracebackRunner::runTraceback(size_t row_index, size_t col_index) +{ + operations_.clear(); + + if (row_index != matrix_.numRows() - 1) + { + softclipQuerySuffix(row_index); + } + + while (matrix_.tracebackStep(row_index, col_index) != TracebackStep::kStop) + { + computeTracebackRunForAlignmentOperation(row_index, col_index); + convertCurrentRunToAlignmentOperation(); + row_index = run_last_row_index; + col_index = run_last_col_index; + tracebackPosition(row_index, col_index); + } + + if (row_index != 0) + { + softclipQueryPrefix(row_index); + } + + std::reverse(operations_.begin(), operations_.end()); + return Alignment(col_index, operations_); +} + +void TracebackRunner::computeTracebackRunForAlignmentOperation(size_t row_index, size_t col_index) +{ + TracebackStep cur_traceback_step = matrix_.tracebackStep(row_index, col_index); + run_traceback_step = cur_traceback_step; + run_length = 0; + + while (run_traceback_step == cur_traceback_step) + { + run_last_col_index = col_index; + run_last_row_index = row_index; + ++run_length; + tracebackPosition(row_index, col_index); + cur_traceback_step = matrix_.tracebackStep(row_index, col_index); + } +} + +void TracebackRunner::tracebackPosition(size_t& row_index, size_t& col_index) +{ + TracebackStep traceback_step = matrix_.tracebackStep(row_index, col_index); + switch (traceback_step) + { + case TracebackStep::kDiagonalMatch: + case TracebackStep::kDiagonalMismatch: + --row_index; + --col_index; + break; + case TracebackStep::kLeft: + --col_index; + break; + case TracebackStep::kTop: + --row_index; + break; + case TracebackStep::kStop: + break; + } +} + +void TracebackRunner::convertCurrentRunToAlignmentOperation() +{ + OperationType operation_type; + + switch (run_traceback_step) + { + case TracebackStep::kDiagonalMatch: + operation_type = OperationType::kMatch; + break; + case TracebackStep::kDiagonalMismatch: + operation_type = OperationType::kMismatch; + break; + case TracebackStep::kLeft: + operation_type = OperationType::kDeletionFromRef; + break; + case TracebackStep::kTop: + operation_type = OperationType::kInsertionToRef; + break; + case TracebackStep::kStop: + default: + throw std::logic_error("Attempted invalid traceback run decoding"); + } + + operations_.emplace_back(operation_type, run_length); +} + +void TracebackRunner::softclipQuerySuffix(size_t& row_index) +{ + + const uint32_t softclip_len = matrix_.numRows() - row_index - 1; + + operations_.emplace_back(OperationType::kSoftclip, softclip_len); +} + +void TracebackRunner::softclipQueryPrefix(size_t& row_index) +{ + operations_.emplace_back(OperationType::kSoftclip, row_index); +} +} diff --git a/thirdparty/graph-tools-master/src/graphalign/dagAligner/PenaltyMatrix.cpp b/thirdparty/graph-tools-master/src/graphalign/dagAligner/PenaltyMatrix.cpp new file mode 100755 index 0000000..b4f7387 --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphalign/dagAligner/PenaltyMatrix.cpp @@ -0,0 +1,68 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Roman Petrovski +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/dagAligner/PenaltyMatrix.hh" + +namespace graphalign +{ + +namespace dagAligner +{ + + // clang-format off +// Notice: this one does not support X at the moment +const FreePenaltyMatrix::Oligo FreePenaltyMatrix::TRANSLATION_TABLE_[OLIGO_MAX_CHAR + 1] = { + N, N, N, N, N, N, N, N, N, N, + N, N, N, N, N, N, N, N, N, N, + N, N, N, N, N, N, N, N, N, N, + N, N, N, N, N, N, N, N, N, N, + N, N, N, N, N, N, N, N, N, N, + N, N, N, N, N, N, N, N, N, N, + N, N, N, N, N, + // capitals + A, N, C, N, N, N, G, N, N, N, + N, N, N, N, N, N, N, N, N, T, + N, N, N, N, N, N, + // rubbish + N, N, N, N, N, N, + // lowercase + A, N, C, N, N, N, G, N, N, N, + N, N, N, N, N, N, N, N, N, T, + N, N, N, N, N, N, + // padding + N, N, N, N, + N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, + N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, + N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, + N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, +}; + // clang-format on + +} // namespace dagAligner + +} // namespace graphalign diff --git a/thirdparty/graph-tools-master/src/graphcore/Graph.cpp b/thirdparty/graph-tools-master/src/graphcore/Graph.cpp new file mode 100755 index 0000000..be420bc --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphcore/Graph.cpp @@ -0,0 +1,201 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko , +// Peter Krusche , +// Felix Schlesinger +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphcore/Graph.hh" + +#include + +#include "graphutils/SequenceOperations.hh" + +using std::set; +using std::string; +using std::to_string; +using std::vector; + +namespace graphtools +{ + +void Graph::init(size_t numnodes_) +{ + nodes_.resize(numnodes_); + adjacency_list_.resize(numnodes_); + reverse_adjacency_list_.resize(numnodes_); +} + +void Graph::assertNodeExists(NodeId node_id) const +{ + if (node_id >= nodes_.size()) + { + throw std::logic_error("Node with id " + to_string(node_id) + " does not exist"); + } +} + +void Graph::assertEdgeExists(NodeIdPair node_id_pair) const +{ + if (!hasEdge(node_id_pair.first, node_id_pair.second)) + { + throw std::logic_error( + "There is no edge between " + to_string(node_id_pair.first) + " and " + to_string(node_id_pair.second)); + } +} + +void assertValidSequence(string const& seq) +{ + if (seq.empty()) + { + throw std::logic_error("Invalid node sequence " + seq); + } +} + +const string& Graph::nodeName(NodeId node_id) const +{ + assertNodeExists(node_id); + return nodes_[node_id].name; +} +void Graph::setNodeName(NodeId node_id, const std::string& node_name) +{ + assertNodeExists(node_id); + nodes_[node_id].name = node_name; +} + +const string& Graph::nodeSeq(NodeId node_id) const +{ + assertNodeExists(node_id); + return nodes_[node_id].sequence; +} + +const vector& Graph::nodeSeqExpansion(NodeId node_id) const +{ + assertNodeExists(node_id); + return nodes_[node_id].sequence_expansion; +} + +void Graph::setNodeSeq(NodeId node_id, const string& sequence) +{ + assertNodeExists(node_id); + assertValidSequence(sequence); + nodes_[node_id].sequence = sequence; + if (is_sequence_expansion_required_) + { + expandReferenceSequence(sequence, nodes_[node_id].sequence_expansion); + } + else + { + nodes_[node_id].sequence_expansion = { sequence }; + } +} + +void Graph::addEdge(NodeId source_id, NodeId sink_id) +{ + assertNodeExists(source_id); + assertNodeExists(sink_id); + + const string edge_encoding = "(" + to_string(source_id) + " ," + to_string(sink_id) + ")"; + if (hasEdge(source_id, sink_id)) + { + throw std::logic_error("Graph already contains edge " + edge_encoding); + } + + if (source_id > sink_id) + { + throw std::logic_error("Edge " + edge_encoding + " breaks topological order"); + } + + NodeIdPair node_id_pair = std::make_pair(source_id, sink_id); + edge_labels_[node_id_pair]; + + adjacency_list_[source_id].emplace(sink_id); + reverse_adjacency_list_[sink_id].emplace(source_id); +} + +bool Graph::hasEdge(NodeId source_id, NodeId sink_id) const +{ + assertNodeExists(source_id); + assertNodeExists(sink_id); + NodeIdPair node_id_pair(source_id, sink_id); + return edge_labels_.find(node_id_pair) != edge_labels_.end(); +} + +void Graph::addLabelToEdge(NodeId source_id, NodeId sink_id, const std::string& label) +{ + NodeIdPair node_pair = std::make_pair(source_id, sink_id); + assertEdgeExists(node_pair); + edge_labels_[node_pair].insert(label); +} + +SortedLabels Graph::allLabels() const +{ + SortedLabels labels; + for (const auto& single_edge_labels_ : edge_labels_) + { + labels.insert(single_edge_labels_.second.begin(), single_edge_labels_.second.end()); + } + return labels; +} + +const Labels& Graph::edgeLabels(NodeId source_id, NodeId sink_id) const +{ + NodeIdPair node_id_pair = std::make_pair(source_id, sink_id); + return edge_labels_.at(node_id_pair); +} + +std::set Graph::edgesWithLabel(const std::string& label) const +{ + std::set result; + for (const auto& pair : edge_labels_) + { + if (pair.second.count(label)) + { + result.insert(pair.first); + } + } + return result; +} + +void Graph::eraseLabel(const std::string& label) +{ + for (auto& pair : edge_labels_) + { + pair.second.erase(label); + } +} + +const set& Graph::successors(NodeId node_id) const +{ + assertNodeExists(node_id); + return adjacency_list_[node_id]; +} + +const std::set& Graph::predecessors(NodeId node_id) const +{ + assertNodeExists(node_id); + return reverse_adjacency_list_[node_id]; +} +} diff --git a/thirdparty/graph-tools-master/src/graphcore/GraphBuilders.cpp b/thirdparty/graph-tools-master/src/graphcore/GraphBuilders.cpp new file mode 100755 index 0000000..fc57c9f --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphcore/GraphBuilders.cpp @@ -0,0 +1,134 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko , +// Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphcore/GraphBuilders.hh" + +// needed in gcc 5 +#include + +using std::string; + +namespace graphtools +{ + +Graph makeDeletionGraph(const string& left_flank, const string& deletion, const string& right_flank) +{ + Graph graph(3); + + graph.setNodeSeq(0, left_flank); + graph.setNodeSeq(1, deletion); + graph.setNodeSeq(2, right_flank); + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(1, 2); + + return graph; +} + +Graph makeSwapGraph( + const string& left_flank, const string& deletion, const string& insertion, const string& right_flank) +{ + Graph graph(4); + + graph.setNodeSeq(0, left_flank); + graph.setNodeSeq(1, deletion); + graph.setNodeSeq(2, insertion); + graph.setNodeSeq(3, right_flank); + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(1, 3); + graph.addEdge(2, 3); + + return graph; +} + +Graph makeDoubleSwapGraph( + const string& left_flank, const string& deletion1, const string& insertion1, const string& middle, + const string& deletion2, const string& insertion2, const string& right_flank) +{ + Graph graph(7); + + graph.setNodeSeq(0, left_flank); + graph.setNodeSeq(1, deletion1); + graph.setNodeSeq(2, insertion1); + graph.setNodeSeq(3, middle); + graph.setNodeSeq(4, deletion2); + graph.setNodeSeq(5, insertion2); + graph.setNodeSeq(6, right_flank); + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(1, 3); + graph.addEdge(2, 3); + graph.addEdge(3, 4); + graph.addEdge(3, 5); + graph.addEdge(4, 6); + graph.addEdge(5, 6); + + return graph; +} + +Graph makeLooplessStrGraph( + size_t read_len, const string& left_flank, const string& repeat_unit, const string& right_flank) +{ + const auto num_repeat_unit_nodes = (size_t)std::ceil(read_len / (double)repeat_unit.length()); + const size_t num_nodes = num_repeat_unit_nodes + 2; // Account for flanks + + Graph graph(num_nodes); + + NodeId right_flank_node_id = static_cast(num_nodes - 1); + + graph.setNodeSeq(0, left_flank); + graph.setNodeSeq(right_flank_node_id, right_flank); + graph.addEdge(0, right_flank_node_id); + + for (NodeId node_id = 0; node_id != num_repeat_unit_nodes; ++node_id) + { + graph.setNodeSeq(node_id + 1, repeat_unit); + graph.addEdge(node_id, node_id + 1); + graph.addEdge(node_id + 1, right_flank_node_id); + } + + return graph; +} + +Graph makeStrGraph(const string& left_flank, const string& repeat_unit, const string& right_flank) +{ + Graph graph(3); + graph.setNodeSeq(0, left_flank); + graph.setNodeSeq(1, repeat_unit); + graph.setNodeSeq(2, right_flank); + + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(1, 1); + graph.addEdge(1, 2); + + return graph; +} +} diff --git a/thirdparty/graph-tools-master/src/graphcore/GraphCoordinates.cpp b/thirdparty/graph-tools-master/src/graphcore/GraphCoordinates.cpp new file mode 100755 index 0000000..1be6684 --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphcore/GraphCoordinates.cpp @@ -0,0 +1,237 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// this software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are +// disclaimed. in no event shall the copyright holder or contributors be liable +// for any direct, indirect, incidental, special, exemplary, or consequential +// damages (including, but not limited to, procurement of substitute goods or +// services; loss of use, data, or profits; or business interruption) however +// caused and on any theory of liability, whether in contract, strict liability, +// or tort including negligence or otherwise) arising in any way out of the use +// of this software, even if advised of the possibility of such damage. + +#include "graphcore/GraphCoordinates.hh" +#include "graphutils/PairHashing.hh" + +#include + +namespace graphtools +{ + +struct GraphCoordinates::GraphCoordinatesImpl +{ + explicit GraphCoordinatesImpl(Graph const* graph_) + : graph(graph_) + { + uint64_t canonical_offset = 0; + for (NodeId n_id = 0; n_id < (NodeId)graph_->numNodes(); ++n_id) + { + node_name_to_id[graph_->nodeName(n_id)] = n_id; + auto const& n_name = graph_->nodeName(n_id); + canonical_offsets[n_name] = canonical_offset; + node_starts[canonical_offset] = n_name; + canonical_offset += std::max((size_t)1, graph_->nodeSeq(n_id).size()); + + // nodes are sorted in topological order, so we can compute distances as min over all predecessors + for (NodeId n_source = 0; n_source < (NodeId)graph_->numNodes(); ++n_source) + { + // distance = zero in these cases + if (n_id == n_source || graph_->hasEdge(n_source, n_id)) + { + continue; + } + + size_t min_dist = std::numeric_limits::max(); + for (auto pred : graph_->predecessors(n_id)) + { + auto pred_distance_it = node_end_to_start_distance.find(std::make_pair(n_source, pred)); + if (pred_distance_it != node_end_to_start_distance.end()) + { + // minimal distance via that predecessor + min_dist = std::min(min_dist, pred_distance_it->second + graph_->nodeSeq(pred).size()); + } + else if (graph_->hasEdge(n_source, pred)) + { + min_dist = std::min(min_dist, graph_->nodeSeq(pred).size()); + } + } + + if (min_dist != std::numeric_limits::max()) + { + node_end_to_start_distance[std::make_pair(n_source, n_id)] = min_dist; + } + } + } + } + + Graph const* graph; + + std::unordered_map canonical_offsets; + std::map node_starts; + std::map node_name_to_id; + + std::unordered_map, size_t> node_end_to_start_distance; +}; + +GraphCoordinates::GraphCoordinates(Graph const* g) + : _impl(new GraphCoordinatesImpl(g)) +{ +} +GraphCoordinates::~GraphCoordinates() = default; + +GraphCoordinates::GraphCoordinates(GraphCoordinates&& rhs) noexcept + : _impl(std::move(rhs._impl)) +{ +} + +GraphCoordinates& GraphCoordinates::operator=(GraphCoordinates&& rhs) noexcept +{ + _impl = std::move(rhs._impl); + return *this; +} + +/** + * Get a "canonical" / linearized position -- every base on the graph has such a position + * @param node node name + * @param offset offset relative to start of node + * @return canonical position + */ +uint64_t GraphCoordinates::canonicalPos(std::string const& node, uint64_t offset) const +{ + auto ioffset = _impl->canonical_offsets.find(node); + assert(ioffset != _impl->canonical_offsets.end()); + return ioffset->second + offset; +} + +/** + * Calculated canonical start and end positions for a graph mapping + * @param mapping + * @return start and end + */ +std::pair GraphCoordinates::canonicalStartAndEnd(Path const& path) const +{ + std::pair start_end{ -1, -1 }; + + start_end.first + = canonicalPos(_impl->graph->nodeName(path.nodeIds().front()), static_cast(path.startPosition())); + + auto end_offset = static_cast(path.endPosition()); + if (path.numNodes() > 0 && end_offset > 0) + { + start_end.second = canonicalPos(_impl->graph->nodeName(path.nodeIds().back()), end_offset); + } + + if (start_end.first > start_end.second) + { + std::swap(start_end.first, start_end.second); + } + + return start_end; +} + +/** + * Reverse lookup : get node and offset from a canonical pos + * @param canonical_pos canonical position + * @param node output variable for node name + * @param offset output variable for offset + */ +void GraphCoordinates::nodeAndOffset(uint64_t canonical_pos, std::string& node, uint64_t& offset) const +{ + auto lb = _impl->node_starts.lower_bound(canonical_pos); + if (lb != _impl->node_starts.end()) + { + if (lb != _impl->node_starts.begin() && canonical_pos < lb->first) + { + lb = std::prev(lb); + } + node = lb->second; + offset = canonical_pos - lb->first; + } + else + { + node = _impl->node_starts.rbegin()->second; + offset = canonical_pos - _impl->node_starts.rbegin()->first; + } +} + +/** + * Calculate the minimum distance in bp between two canonical positions + * @param pos1 start pos + * @param pos2 end pos + * @return basepairs between pos1 and pos2 + */ +uint64_t GraphCoordinates::distance(uint64_t pos1, uint64_t pos2) const +{ + if (pos1 == pos2) + { + return 0; + } + if (pos2 < pos1) + { + std::swap(pos1, pos2); + } + + std::string n1, n2; + uint64_t offset1, offset2; + nodeAndOffset(pos1, n1, offset1); + nodeAndOffset(pos2, n2, offset2); + + // on on the same node-> can compute distance directly + if (n1 == n2) + { + return pos2 - pos1; + } + + const NodeId n1_id = _impl->node_name_to_id[n1]; + const NodeId n2_id = _impl->node_name_to_id[n2]; + const size_t n1_length = _impl->graph->nodeSeq(n1_id).size(); + + uint64_t result = std::numeric_limits::max(); + if (_impl->graph->hasEdge(n1_id, n2_id)) + { + result = n1_length - offset1 + offset2; + } + else + { + auto dist_it = _impl->node_end_to_start_distance.find(std::make_pair(n1_id, n2_id)); + + if (dist_it != _impl->node_end_to_start_distance.end()) + { + result = n1_length - offset1 + offset2 + dist_it->second; + } + } + return result; +} + +/** + * Return the node id for a node name + * @param node_name name of node + * @return node id for the node + */ +NodeId GraphCoordinates::nodeId(const std::string& node_name) const +{ + assert(_impl->node_name_to_id.count(node_name) != 0); + return _impl->node_name_to_id[node_name]; +} + +/** + * @return the graph for these coordinates + */ +Graph const& GraphCoordinates::getGraph() const { return *(_impl->graph); } +} \ No newline at end of file diff --git a/thirdparty/graph-tools-master/src/graphcore/GraphOperations.cpp b/thirdparty/graph-tools-master/src/graphcore/GraphOperations.cpp new file mode 100755 index 0000000..290945d --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphcore/GraphOperations.cpp @@ -0,0 +1,64 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko , +// Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphcore/GraphOperations.hh" + +#include "graphutils/SequenceOperations.hh" + +namespace graphtools +{ +/** + * Reverse (and optionally complement) a graph + * @param graph the graph to reverse + * @param complement true to also reverse-complement all sequences + */ +Graph reverseGraph(Graph const& graph, bool complement) +{ + Graph reversed(graph.numNodes(), "", graph.isSequenceExpansionRequired()); + + for (NodeId node_id = 0; node_id != graph.numNodes(); ++node_id) + { + reversed.setNodeSeq( + static_cast(graph.numNodes() - 1 - node_id), + complement ? reverseComplement(graph.nodeSeq(node_id)) : reverseString(graph.nodeSeq(node_id))); + + for (auto succ : graph.successors(node_id)) + { + const auto to_new = static_cast(graph.numNodes() - 1 - node_id); + const auto from_new = static_cast(graph.numNodes() - 1 - succ); + reversed.addEdge(from_new, to_new); + for (const auto& label : graph.edgeLabels(node_id, succ)) + { + reversed.addLabelToEdge(from_new, to_new, label); + } + } + } + return reversed; +} +} diff --git a/thirdparty/graph-tools-master/src/graphcore/GraphReferenceMapping.cpp b/thirdparty/graph-tools-master/src/graphcore/GraphReferenceMapping.cpp new file mode 100755 index 0000000..3a739b4 --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphcore/GraphReferenceMapping.cpp @@ -0,0 +1,151 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Felix Schlesinger +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphcore/GraphReferenceMapping.hh" + +#include +#include + +#include "graphcore/GraphCoordinates.hh" +#include "graphcore/PathOperations.hh" +#include "graphutils/IntervalList.hh" + +using std::string; + +namespace graphtools +{ + +ReferenceInterval::ReferenceInterval(ContigId const contig, Position const start, Position const end) + : contig(contig) + , start(start) + , end(end) +{ + if ((start < 0) || (start > end)) + { + throw std::logic_error("Invalid Interval"); + } +} +ReferenceInterval ReferenceInterval::makePosition(ContigId const& contig, Position const pos) +{ + return ReferenceInterval(contig, pos, pos); +} + +ReferenceInterval ReferenceInterval::parseRegion(std::string const& regionString) +{ + std::vector spl; + boost::split(spl, regionString, boost::is_any_of(":")); + if (spl.size() != 2) + { + throw std::runtime_error("Invalid region string: " + regionString); + } + string const contig = spl[0]; + boost::replace_all(spl[1], ",", ""); + boost::split(spl, spl[1], boost::is_any_of("-")); + if (spl.size() != 2) + { + throw std::runtime_error("Invalid region string: " + regionString); + } + int const start = std::stoll(spl[0]); + int const end = std::stoll(spl[1]); + return ReferenceInterval(contig, start, end); +} + +bool operator<(ReferenceInterval const& lhs, ReferenceInterval const& rhs) +{ + return lhs.contig < rhs.contig || (lhs.contig == rhs.contig && lhs.start < rhs.start) + || (lhs.contig == rhs.contig && lhs.start == rhs.start && lhs.end < rhs.end); +} +bool operator==(ReferenceInterval const& lhs, ReferenceInterval const& rhs) +{ + return lhs.contig == rhs.contig && lhs.start == rhs.start && lhs.end == rhs.end; +} +std::ostream& operator<<(std::ostream& output, ReferenceInterval const& ri) +{ + output << ri.contig << ":" << ri.start << "-" << ri.end; + return output; +} + +int32_t ReferenceInterval::length() const { return end - start; } + +NodeReferenceMapping::NodeReferenceMapping(Graph const& graph, NodeId const node, ReferenceInterval const& ref) + : nodeLength_(graph.nodeSeq(node).length()) + , ref_(ref) +{ + if (nodeLength_ != ref.length()) + { + throw std::logic_error("Length of node sequence does not match reference map length " + graph.nodeName(node)); + } +} + +ReferenceInterval NodeReferenceMapping::map(int32_t const offset) const +{ + if (offset >= nodeLength_) + { + throw std::logic_error("Cannot map position outside node"); + } + return ReferenceInterval::makePosition(ref_.contig, ref_.start + offset); +} + +GraphReferenceMapping::GraphReferenceMapping(Graph const* graph) + : graph_(graph) +{ +} + +void GraphReferenceMapping::addMapping(NodeId node, ReferenceInterval const& ref) +{ + NodeReferenceMapping const nodeMapping(*graph_, node, ref); + mappings_.insert({ node, nodeMapping }); +} + +boost::optional GraphReferenceMapping::map(NodeId node, int32_t offset) const +{ + if (node >= graph_->numNodes()) + { + throw std::logic_error("Invalid node " + std::to_string(node)); + } + auto const& mapping = mappings_.find(node); + if (mapping != mappings_.end()) + { + return mapping->second.map(offset); + } + return boost::optional(); +} + +boost::optional GraphReferenceMapping::map(Path const& path) const +{ + for (auto const& nodePath : generateSubpathForEachNode(path)) + { + auto const mapped = map(*nodePath.nodeIds().begin(), nodePath.startPosition()); + if (mapped) + { + return mapped; + } + } + return boost::optional(); +} +} diff --git a/thirdparty/graph-tools-master/src/graphcore/Path.cpp b/thirdparty/graph-tools-master/src/graphcore/Path.cpp new file mode 100755 index 0000000..a5fb9cd --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphcore/Path.cpp @@ -0,0 +1,524 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphcore/Path.hh" + +#include +#include +#include +#include +#include + +using std::list; +using std::ostream; +using std::set; +using std::shared_ptr; +using std::string; +using std::to_string; +using std::vector; + +namespace graphtools +{ +struct Path::Impl +{ + Impl( + const Graph* new_graph_raw_ptr, int32_t new_start_position, const vector& new_nodes, + int32_t new_end_position) + : graph_raw_ptr(new_graph_raw_ptr) + , start_position(new_start_position) + , end_position(new_end_position) + , nodes(new_nodes) + { + } + bool isValid() const; + bool isNodePositionValid(NodeId node_id, int32_t position) const; + bool arePositionsOrdered() const; + bool isPathEmpty() const; + bool isFirstNodePosValid() const; + bool isLastNodePosValid() const; + bool isPathConnected() const; + string encode() const; + void assertThatIndexIsValid(int32_t node_index) const + { + if (node_index < 0 || node_index >= (signed)nodes.size()) + { + const string msg = "Node index " + to_string(node_index) + "is out of bounds for path " + encode(); + throw std::logic_error(msg); + } + } + + bool operator==(const Impl& other) const + { + return (graph_raw_ptr == other.graph_raw_ptr) && (start_position == other.start_position) + && (end_position == other.end_position) && (nodes == other.nodes); + } + + const Graph* const graph_raw_ptr; + int32_t start_position; + int32_t end_position; + vector nodes; +}; + +bool Path::Impl::isValid() const +{ + return !isPathEmpty() && isFirstNodePosValid() && isLastNodePosValid() && arePositionsOrdered() + && isPathConnected(); +} + +bool Path::Impl::arePositionsOrdered() const { return nodes.size() != 1 || start_position <= end_position; } + +bool Path::Impl::isNodePositionValid(NodeId node_id, int32_t position) const +{ + if (position < 0) + { + return false; + } + const string& node_seq = graph_raw_ptr->nodeSeq(node_id); + return (unsigned)position <= node_seq.length(); +} + +bool Path::Impl::isPathEmpty() const { return nodes.empty(); } + +bool Path::Impl::isFirstNodePosValid() const +{ + const NodeId first_node_id = nodes.front(); + return isNodePositionValid(first_node_id, start_position); +} + +bool Path::Impl::isLastNodePosValid() const +{ + const NodeId last_node_id = nodes.back(); + return isNodePositionValid(last_node_id, end_position); +} + +bool Path::Impl::isPathConnected() const +{ + vector::const_iterator start_iter; + vector::const_iterator end_iter; + for (start_iter = nodes.begin(); start_iter != std::prev(nodes.end()); ++start_iter) + { + end_iter = std::next(start_iter); + if (!graph_raw_ptr->hasEdge(*start_iter, *end_iter)) + { + return false; + } + } + return true; +} + +string Path::Impl::encode() const +{ + string path_encoding; + + size_t node_index = 0; + const size_t last_index = nodes.size() - 1; + for (NodeId node_id : nodes) + { + const string node_name = to_string(node_id); + string node_encoding; + if (node_index == 0) // Encoding first node. + { + node_encoding = "(" + node_name + "@" + to_string(start_position) + ")"; + } + if (node_index == last_index) // Encoding last node. + { + node_encoding += "-(" + node_name + "@" + to_string(end_position) + ")"; + } + if (node_index != 0 && node_index != last_index) // Encoding intermediate node. + { + node_encoding = "-(" + node_name + ")"; + } + path_encoding += node_encoding; + ++node_index; + } + + return path_encoding; +} + +string Path::encode() const { return pimpl_->encode(); } + +Path::Path(const Graph* graph_raw_ptr, int32_t start_position, const vector& nodes, int32_t end_position) + : pimpl_(new Impl(graph_raw_ptr, start_position, nodes, end_position)) +{ + if (!pimpl_->isValid()) + { + throw std::logic_error("Cannot create invalid path"); + } +} + +Path::~Path() = default; + +Path::Path(const Path& other) + : pimpl_(new Impl(*other.pimpl_)) +{ +} + +Path::Path(Path&& other) noexcept + : pimpl_(std::move(other.pimpl_)) +{ +} + +Path& Path::operator=(const Path& other) +{ + if (this != &other) + { + pimpl_.reset(new Impl(*other.pimpl_)); + } + return *this; +} + +Path::const_iterator Path::begin() const { return pimpl_->nodes.begin(); } +Path::const_iterator Path::end() const { return pimpl_->nodes.end(); } + +Path& Path::operator=(Path&& other) noexcept +{ + pimpl_ = std::move(other.pimpl_); + return *this; +} + +int32_t Path::startPosition() const { return pimpl_->start_position; } +int32_t Path::endPosition() const { return pimpl_->end_position; } +const Graph* Path::graphRawPtr() const { return pimpl_->graph_raw_ptr; } + +vector const& Path::nodeIds() const { return pimpl_->nodes; } + +size_t Path::numNodes() const { return pimpl_->nodes.size(); } + +NodeId Path::getNodeIdByIndex(size_t node_index) const { return pimpl_->nodes[node_index]; } + +bool Path::checkOverlapWithNode(NodeId node_id) const +{ + const vector& nodes = pimpl_->nodes; + return std::find(nodes.begin(), nodes.end(), node_id) != nodes.end(); +} + +int32_t Path::getStartPositionOnNodeByIndex(size_t node_index) const +{ + pimpl_->assertThatIndexIsValid(static_cast(node_index)); + + if (node_index == 0) + { + return startPosition(); + } + + return 0; +} + +int32_t Path::getEndPositionOnNodeByIndex(size_t node_index) const +{ + pimpl_->assertThatIndexIsValid(static_cast(node_index)); + + if (node_index == numNodes() - 1) + { + return endPosition(); + } + + const int32_t node_id = pimpl_->nodes[node_index]; + const size_t node_length = graphRawPtr()->nodeSeq(static_cast(node_id)).length(); + + return node_length; +} + +size_t Path::getNodeOverlapLengthByIndex(size_t node_index) const +{ + pimpl_->assertThatIndexIsValid(static_cast(node_index)); + const int32_t node_id = pimpl_->nodes[node_index]; + const size_t node_length = graphRawPtr()->nodeSeq(static_cast(node_id)).length(); + auto length_on_node = (int32_t)node_length; // This is the length of all intermediate nodes. + + const bool is_first_node = node_index == 0; + const bool is_last_node = node_index + 1 == numNodes(); + + if (is_first_node && is_last_node) + { + length_on_node = pimpl_->end_position - pimpl_->start_position; + } + else if (is_first_node) + { + length_on_node = static_cast(node_length - pimpl_->start_position); + } + else if (is_last_node) + { + length_on_node = pimpl_->end_position; + } + + return static_cast(length_on_node); +} + +int32_t Path::getDistanceFromPathStart(NodeId node, int32_t offset) const +{ + size_t n = 0; + int32_t distance = 0; + bool found = false; + while (n < numNodes()) + { + const auto node_id = pimpl_->nodes[n]; + const int32_t node_start = n == 0 ? pimpl_->start_position : 0; + const int32_t node_end + = n == numNodes() - 1 ? pimpl_->end_position : (int32_t)pimpl_->graph_raw_ptr->nodeSeq(node_id).size() - 1; + + if (node_id == node && offset >= node_start && offset <= node_end) + { + distance += offset - node_start; + found = true; + break; + } + + distance += node_end - node_start + 1; + n++; + } + if (!found) + { + throw std::logic_error(std::to_string(node) + "@" + std::to_string(offset) + " is not on path " + encode()); + } + return distance; +} + +size_t Path::length() const +{ + size_t path_length = 0; + for (int32_t node_index = 0; node_index != (signed)pimpl_->nodes.size(); ++node_index) + { + path_length += getNodeOverlapLengthByIndex(static_cast(node_index)); + } + + return path_length; +} + +string Path::getNodeSeq(size_t node_index) const +{ + auto node_id = static_cast(pimpl_->nodes[node_index]); + const string& sequence = pimpl_->graph_raw_ptr->nodeSeq(static_cast(node_id)); + + if (node_index == 0) + { + const size_t node_overlap_len = getNodeOverlapLengthByIndex(node_index); + return sequence.substr(static_cast(pimpl_->start_position), node_overlap_len); + } + else if ((size_t)node_index == pimpl_->nodes.size() - 1) + { + const size_t node_overlap_len = getNodeOverlapLengthByIndex(node_index); + return sequence.substr(0, node_overlap_len); + } + else + { + return sequence; + } +} + +string Path::seq() const +{ + string path_seq; + size_t node_index = 0; + for (NodeId node_id : pimpl_->nodes) + { + string node_seq = pimpl_->graph_raw_ptr->nodeSeq(node_id); + if (node_index == 0) + { + node_seq = node_seq.substr(static_cast(pimpl_->start_position)); + } + + if (node_index == pimpl_->nodes.size() - 1) + { + const int32_t end_node_start = pimpl_->nodes.size() == 1 ? pimpl_->start_position : 0; + const int32_t segment_len = pimpl_->end_position - end_node_start; + node_seq = node_seq.substr(0, (unsigned long)segment_len); + } + + path_seq += node_seq; + ++node_index; + } + return path_seq; +} + +bool Path::operator==(const Path& other) const { return *pimpl_ == *other.pimpl_; } + +ostream& operator<<(ostream& os, const Path& path) { return os << path.encode(); } + +void Path::shiftStartAlongNode(int32_t shift_len) +{ + pimpl_->start_position -= shift_len; + if (!pimpl_->isValid()) + { + throw std::logic_error("Cannot move start by " + to_string(shift_len)); + } +} + +void Path::shiftEndAlongNode(int32_t shift_len) +{ + pimpl_->end_position += shift_len; + + if (!pimpl_->isValid()) + { + throw std::logic_error("Cannot move end by " + to_string(shift_len)); + } +} + +void Path::extendStartToNode(NodeId node_id) +{ + pimpl_->nodes.insert(pimpl_->nodes.begin(), node_id); + const auto new_node_seq_len = static_cast(pimpl_->graph_raw_ptr->nodeSeq(node_id).length()); + pimpl_->start_position = new_node_seq_len; + + if (!pimpl_->isValid()) + { + throw std::logic_error("Cannot extend to node " + to_string(node_id)); + } +} + +void Path::extendStartToIncludeNode(NodeId node_id) +{ + pimpl_->nodes.insert(pimpl_->nodes.begin(), node_id); + pimpl_->start_position = 0; + + if (!pimpl_->isValid()) + { + throw std::logic_error("Cannot extend to node " + to_string(node_id)); + } +} + +void Path::removeStartNode() +{ + pimpl_->nodes.erase(pimpl_->nodes.begin()); + pimpl_->start_position = 0; + + if (!pimpl_->isValid()) + { + throw std::logic_error("Cannot remove start node of " + encode()); + } +} + +void Path::removeZeroLengthStart() +{ + if (numNodes() > 1 && getNodeOverlapLengthByIndex(0) == 0) + { + removeStartNode(); + } +} + +void Path::removeZeroLengthEnd() +{ + const size_t index_of_last_node = numNodes() - 1; + if (numNodes() > 1 && getNodeOverlapLengthByIndex(index_of_last_node) == 0) + { + removeEndNode(); + } +} + +void Path::extendEndToNode(NodeId node_id) +{ + pimpl_->nodes.push_back(node_id); + pimpl_->end_position = 0; + + if (!pimpl_->isValid()) + { + throw std::logic_error("Cannot extend right to node " + to_string(node_id)); + } +} + +void Path::extendEndToIncludeNode(NodeId node_id) +{ + pimpl_->nodes.push_back(node_id); + const auto new_node_seq_len = static_cast(pimpl_->graph_raw_ptr->nodeSeq(node_id).length()); + pimpl_->end_position = new_node_seq_len; + + if (!pimpl_->isValid()) + { + throw std::logic_error("Cannot extend right to node " + to_string(node_id)); + } +} + +void Path::removeEndNode() +{ + pimpl_->nodes.erase(pimpl_->nodes.end() - 1); + NodeId new_last_node_id = pimpl_->nodes.back(); + auto new_last_node_len = static_cast(pimpl_->graph_raw_ptr->nodeSeq(new_last_node_id).length()); + pimpl_->end_position = new_last_node_len; + if (!pimpl_->isValid()) + { + throw std::logic_error("Cannot remove end node of " + encode()); + } +} + +void Path::shrinkStartBy(int32_t shrink_len) +{ + const int32_t node_len_left = getNodeOverlapLengthByIndex(0); + + if (shrink_len <= node_len_left) + { + shiftStartAlongNode(-shrink_len); + removeZeroLengthStart(); + } + else + { + removeStartNode(); + + const int32_t leftover_len = shrink_len - node_len_left; + shrinkStartBy(leftover_len); + } +} + +void Path::shrinkEndBy(int32_t shrink_len) +{ + const int32_t node_len_left = pimpl_->end_position; + + if (shrink_len <= node_len_left) + { + shiftEndAlongNode(-shrink_len); + removeZeroLengthEnd(); + } + else + { + removeEndNode(); + + const int32_t leftover_len = shrink_len - node_len_left; + shrinkEndBy(leftover_len); + } +} + +void Path::shrinkBy(int32_t start_shrink_len, int32_t end_shrink_len) +{ + shrinkStartBy(start_shrink_len); + shrinkEndBy(end_shrink_len); +} + +bool Path::operator<(const Path& other) const +{ + if (pimpl_->start_position != other.pimpl_->start_position) + { + return pimpl_->start_position < other.pimpl_->start_position; + } + + if (pimpl_->nodes != other.pimpl_->nodes) + { + return pimpl_->nodes < other.pimpl_->nodes; + } + + return pimpl_->end_position < other.pimpl_->end_position; +} +} diff --git a/thirdparty/graph-tools-master/src/graphcore/PathFamily.cpp b/thirdparty/graph-tools-master/src/graphcore/PathFamily.cpp new file mode 100755 index 0000000..fac6c7b --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphcore/PathFamily.cpp @@ -0,0 +1,149 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Felix Schlesinger , +// Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphcore/PathFamily.hh" + +#include +#include +#include +#include +#include +#include + +#include "graphcore/Path.hh" + +namespace graphtools +{ +struct PathFamily::Impl +{ + explicit Impl(Graph* const graph) + : graph_(graph) + { + } + + Graph* const graph_; + std::unordered_set edges_; + std::unordered_set inNodes_; + std::unordered_set outNodes_; +}; + +PathFamily::PathFamily(Graph* const graph) + : pimpl_(new PathFamily::Impl(graph)) +{ +} + +PathFamily::PathFamily(Graph* const graph, const std::string& label) + : pimpl_(new PathFamily::Impl(graph)) +{ + for (const auto& edge : graph->edgesWithLabel(label)) + { + addEdge(edge.first, edge.second); + } +} + +PathFamily::PathFamily(const PathFamily& other) + : pimpl_(new Impl(*other.pimpl_)) +{ +} + +PathFamily::PathFamily(PathFamily&& other) noexcept + : pimpl_(std::move(other.pimpl_)) +{ +} + +PathFamily::~PathFamily() noexcept = default; + +const Graph& PathFamily::graph() const { return *(pimpl_->graph_); } +const std::unordered_set& PathFamily::edges() const { return pimpl_->edges_; } + +bool PathFamily::operator==(const PathFamily& other) const +{ + return ((edges() == other.edges()) && (pimpl_->graph_ == other.pimpl_->graph_)); +} + +bool PathFamily::operator!=(const PathFamily& other) const { return !(*this == other); } + +bool PathFamily::containsPath(const Path& path) const +{ + int matched(0); + for (auto start = path.begin(); start != std::prev(path.end()); ++start) + { + auto end = std::next(start); + if (edges().count(NodeIdPair(*start, *end))) + { + ++matched; + } + else + { + if (pimpl_->outNodes_.count(*start) || pimpl_->inNodes_.count(*end)) + { + return false; + } + } + } + return matched > 0; +} + +bool PathFamily::includes(const PathFamily& other) const +{ + return std::all_of( + other.edges().begin(), other.edges().end(), [&](const NodeIdPair& e) { return edges().count(e); }); +} + +void PathFamily::addEdge(NodeId first, NodeId second) +{ + if (!graph().hasEdge(first, second)) + { + throw std::logic_error("Edges added to path family is not in the graph."); + } + pimpl_->edges_.emplace(first, second); + pimpl_->outNodes_.insert(first); + pimpl_->inNodes_.insert(second); +} + +void PathFamily::setLabel(const std::string& label) +{ + pimpl_->graph_->eraseLabel(label); + for (const auto& edge : edges()) + { + pimpl_->graph_->addLabelToEdge(edge.first, edge.second, label); + } +} + +std::ostream& operator<<(std::ostream& os, const PathFamily& path) +{ + os << "{"; + for (const auto& edge : path.edges()) + { + os << "(" << edge.first << ", " << edge.second << "), "; + } + os << "}"; + return os; +} +} diff --git a/thirdparty/graph-tools-master/src/graphcore/PathFamilyOperations.cpp b/thirdparty/graph-tools-master/src/graphcore/PathFamilyOperations.cpp new file mode 100755 index 0000000..f738079 --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphcore/PathFamilyOperations.cpp @@ -0,0 +1,213 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Felix Schlesinger , +// Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphcore/Path.hh" +#include "graphcore/PathFamily.hh" +#include "graphcore/PathOperations.hh" + +#include +#include +#include +#include +#include +#include + +using std::list; +using std::map; +using std::set; +using std::vector; + +namespace graphtools +{ + +std::list getPathSegmentsForFamily(graphtools::PathFamily const& family) +{ + std::list segments; + Graph const& graph = family.graph(); + + // sort edges to have their start nodes in topological order + vector edges{ family.edges().begin(), family.edges().end() }; + std::sort(edges.begin(), edges.end(), [](NodeIdPair const& a, NodeIdPair const& b) -> bool { + return a.first == b.first ? a.second < b.second : a.first < b.first; + }); + + // compute in and out degree of nodes for subgraph given by path family edges + map in_count; + map out_count; + for (const auto& edge : edges) + { + out_count[edge.first]++; + in_count[edge.second]++; + } + + // concatenate path segments within the family + for (const auto& edge : edges) + { + if (edge.first == edge.second) + { + continue; + } + + bool any_path_extended_by_edge = false; + for (auto& prefix : segments) + { + // only extend when we can do so uniquely + if (prefix.nodeIds().back() == edge.first && in_count[edge.first] == 1 && out_count[edge.first] == 1) + { + prefix.extendEndToIncludeNode(edge.second); + any_path_extended_by_edge = true; + } + } + if (!any_path_extended_by_edge) + { + segments.emplace_back(Path{ + &graph, 0, { edge.first, edge.second }, static_cast(graph.nodeSeq(edge.second).size()) }); + } + } + + return segments; +} + +bool enumeratePathCombinationsInFamily( + PathFamily const& family, std::list const& segments, std::list* paths, size_t maxPaths) +{ + if (paths == nullptr) + { + throw std::logic_error("paths cannot be null."); + } + paths->clear(); + + bool complete = true; + map> segmentsStartingAt; + map> segmentsEndingAt; + for (const auto& segment : segments) + { + segmentsStartingAt[segment.nodeIds().front()].insert(segment); + segmentsEndingAt[segment.nodeIds().back()].insert(segment); + } + + bool any_merged = true; + while (any_merged) + { + any_merged = false; + set merged_subpaths; + for (const auto& edge : family.edges()) + { + auto check_and_merge = [&](std::set const& prefixes, std::set const& suffixes) { + for (const auto& prefix : prefixes) + { + for (const auto& suffix : suffixes) + { + if (checkPathPrefixSuffixOverlap(prefix, suffix) || checkIfPathsAdjacent(prefix, suffix)) + { + Path segment = mergePaths(prefix, suffix); + segmentsStartingAt[segment.nodeIds().front()].insert(segment); + segmentsEndingAt[segment.nodeIds().back()].insert(segment); + + merged_subpaths.insert(prefix); + merged_subpaths.insert(suffix); + any_merged = true; + } + } + } + }; + + check_and_merge(segmentsEndingAt[edge.first], segmentsStartingAt[edge.first]); + check_and_merge(segmentsEndingAt[edge.second], segmentsStartingAt[edge.second]); + check_and_merge(segmentsEndingAt[edge.first], segmentsStartingAt[edge.second]); + } + for (const auto& path : merged_subpaths) + { + segmentsStartingAt[path.nodeIds().front()].erase(path); + segmentsEndingAt[path.nodeIds().back()].erase(path); + } + + // check we're not over the maximum count + size_t count = 0; + for (const auto& start_list : segmentsStartingAt) + { + count += start_list.second.size(); + } + if (count > maxPaths) + { + complete = false; + break; + } + } + + for (const auto& start_list : segmentsStartingAt) + { + for (const auto& path : start_list.second) + { + paths->push_back(path); + if (paths->size() > maxPaths) + { + complete = false; + break; + } + } + if (paths->size() > maxPaths) + { + break; + } + } + + return complete; +} + +bool getMaximalPathsForFamily(graphtools::PathFamily const& family, std::list* paths, size_t maxPaths) +{ + const auto segments = getPathSegmentsForFamily(family); + return enumeratePathCombinationsInFamily(family, segments, paths, maxPaths); +} + +std::map getPathFamiliesFromGraph(graphtools::Graph& graph) +{ + std::map families; + + for (const auto& label : graph.allLabels()) + { + families.insert(std::make_pair(label, PathFamily(&graph, label))); + } + + return families; +} + +graphtools::PathFamily pathToPathFamily(graphtools::Graph& graph, graphtools::Path const& path) +{ + graphtools::PathFamily family(&graph); + + for (size_t i = 1; i < path.numNodes(); ++i) + { + family.addEdge(path.getNodeIdByIndex(i - 1), path.getNodeIdByIndex(i)); + } + + return family; +} +} diff --git a/thirdparty/graph-tools-master/src/graphcore/PathOperations.cpp b/thirdparty/graph-tools-master/src/graphcore/PathOperations.cpp new file mode 100755 index 0000000..75f7ab4 --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphcore/PathOperations.cpp @@ -0,0 +1,797 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko , +// Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphcore/PathOperations.hh" + +#include +#include +#include +#include +#include +#include + +using std::list; +using std::set; +using std::string; +using std::vector; + +namespace graphtools +{ + +list extendPathStart(const Path& path, int32_t extension_len) +{ + list extended_paths; + + const NodeId start_node_id = path.nodeIds().front(); + + // Start position gives the maximum extension. + if (extension_len <= path.startPosition()) + { + Path extended_path(path); + extended_path.shiftStartAlongNode(extension_len); + extended_paths.push_back(extended_path); + } + else + { + const set pred_node_ids = path.graphRawPtr()->predecessors(start_node_id); + const int32_t leftover_length = extension_len - path.startPosition(); + for (NodeId pred_node_id : pred_node_ids) + { + Path path_with_this_node(path); + path_with_this_node.extendStartToNode(pred_node_id); + list extensions_of_path_with_this_node = extendPathStart(path_with_this_node, leftover_length); + + extended_paths.splice(extended_paths.end(), extensions_of_path_with_this_node); + } + } + + return extended_paths; +} + +list extendPathEnd(const Path& path, int32_t extension_len) +{ + list extended_paths; + + const NodeId end_node_id = path.nodeIds().back(); + + const auto end_node_length = static_cast(path.graphRawPtr()->nodeSeq(end_node_id).length()); + const int32_t max_extension_at_end_node = end_node_length - path.endPosition(); + + if (extension_len <= max_extension_at_end_node) + { + Path extended_path(path); + extended_path.shiftEndAlongNode(extension_len); + extended_paths.push_back(extended_path); + } + else + { + const set succ_node_ids = path.graphRawPtr()->successors(end_node_id); + const int32_t leftover_length = extension_len - max_extension_at_end_node; + for (NodeId succ_node_id : succ_node_ids) + { + Path path_with_this_node(path); + path_with_this_node.extendEndToNode(succ_node_id); + + list extensions_of_path_with_this_node = extendPathEnd(path_with_this_node, leftover_length); + extended_paths.splice(extended_paths.end(), extensions_of_path_with_this_node); + } + } + + return extended_paths; +} + +list extendPath(const Path& path, int32_t start_extension_len, int32_t end_extension_len) +{ + list extended_paths; + list start_extended_paths = extendPathStart(path, start_extension_len); + for (const Path& start_extended_path : start_extended_paths) + { + list end_extended_paths = extendPathEnd(start_extended_path, end_extension_len); + extended_paths.splice(extended_paths.end(), end_extended_paths); + } + return extended_paths; +} + +Path extendPathEndMatching(Path path, const std::string& query, size_t qpos) +{ + const Graph& graph = *path.graphRawPtr(); + size_t pos_in_query = qpos + path.length(); + NodeId node_in_graph = path.nodeIds().back(); + size_t pos_in_node = (size_t)path.endPosition(); + + std::vector nodes = path.nodeIds(); + bool moved = true; + + while (moved) + { + moved = false; + const std::string& node_sequence = graph.nodeSeq(node_in_graph); + + while (pos_in_query < query.size() && pos_in_node < node_sequence.size() + && query.at(pos_in_query) == node_sequence.at(pos_in_node)) + { + moved = true; + ++pos_in_node; + ++pos_in_query; + } + + if (pos_in_node >= node_sequence.size()) + { + const auto& successors = graph.successors(node_in_graph); + + size_t num_longest_matches = 0; + size_t current_longest_match = 0; + NodeId current_successor = 0; + + size_t successor_min_size = std::numeric_limits::max(); + for (auto successor : successors) + { + successor_min_size = std::min(successor_min_size, graph.nodeSeq(successor).size()); + } + + for (auto successor : successors) + { + size_t pos_in_successor = 0; + const std::string& successor_sequence = graph.nodeSeq(successor); + while (pos_in_successor < successor_min_size && pos_in_query + pos_in_successor < query.size() + && successor_sequence.at(pos_in_successor) == query[pos_in_query + pos_in_successor]) + { + ++pos_in_successor; + } + + if (pos_in_successor > current_longest_match) + { + current_longest_match = pos_in_successor; + current_successor = successor; + num_longest_matches = 1; + } + else if (pos_in_successor == current_longest_match) + { + ++num_longest_matches; + } + } + + if (current_longest_match == 0 || num_longest_matches != 1) + { + break; + } + + nodes.push_back(current_successor); + pos_in_query += current_longest_match; + pos_in_node = current_longest_match; + node_in_graph = current_successor; + moved = true; + } + } + return Path{ &graph, path.startPosition(), nodes, static_cast(pos_in_node) }; +} + +Path extendPathStartMatching(Path path, const std::string& query, size_t& pos_in_query) +{ + const Graph& graph = *path.graphRawPtr(); + NodeId node_in_graph = path.nodeIds().front(); + auto pos_in_node = (size_t)path.startPosition(); + + std::vector nodes = path.nodeIds(); + bool moved = true; + + while (moved) + { + moved = false; + + const std::string& node_sequence = graph.nodeSeq(node_in_graph); + + while (pos_in_query > 0 && pos_in_node > 0 && query.at(pos_in_query - 1) == node_sequence.at(pos_in_node - 1)) + { + moved = true; + --pos_in_node; + --pos_in_query; + } + + if (pos_in_node == 0) + { + const auto& predecessors = graph.predecessors(node_in_graph); + + size_t num_longest_matches = 0; + size_t current_longest_match = 0; + NodeId current_predecessor = 0; + + size_t predecessor_min_size = std::numeric_limits::max(); + for (auto predecessor : predecessors) + { + predecessor_min_size = std::min(predecessor_min_size, graph.nodeSeq(predecessor).size()); + } + for (auto predecessor : predecessors) + { + const std::string& predecessor_sequence = graph.nodeSeq(predecessor); + size_t pos_in_predecessor = predecessor_sequence.size(); + size_t match_length = 0; + while (pos_in_predecessor > (predecessor_sequence.size() - predecessor_min_size) + && pos_in_query - match_length > 0 + && predecessor_sequence.at(pos_in_predecessor - 1) == query[pos_in_query - match_length - 1]) + { + --pos_in_predecessor; + ++match_length; + } + + if (match_length > current_longest_match) + { + current_longest_match = match_length; + current_predecessor = predecessor; + num_longest_matches = 1; + } + else if (match_length == current_longest_match) + { + ++num_longest_matches; + } + } + + if (current_longest_match == 0 + || num_longest_matches != 1) // also gets the case where current_longest_match == 0 + { + break; + } + + nodes.insert(nodes.begin(), current_predecessor); + pos_in_query -= current_longest_match; + node_in_graph = current_predecessor; + pos_in_node = graph.nodeSeq(node_in_graph).size() - current_longest_match; + moved = true; + } + } + + return Path{ &graph, static_cast(pos_in_node), nodes, path.endPosition() }; +} + +Path extendPathMatching(Path path, const std::string& query, size_t& pos_in_query) +{ + return extendPathStartMatching(extendPathEndMatching(std::move(path), query, pos_in_query), query, pos_in_query); +} + +vector splitSequenceByPath(const Path& path, const string& sequence) +{ + if (path.length() != sequence.length()) + { + throw std::logic_error( + "Split operation requires that " + path.encode() + " and " + sequence + " have same length"); + } + + vector split_seq; + size_t cur_position = 0; + for (int32_t node_index = 0; node_index != (signed)path.numNodes(); ++node_index) + { + const size_t length_on_node = path.getNodeOverlapLengthByIndex(static_cast(node_index)); + split_seq.push_back(sequence.substr(cur_position, length_on_node)); + cur_position += length_on_node; + } + return split_seq; +} + +/** + * Return true if two paths are exactly adjacent + * (i.e. p1 starts just before p2, or the other way around) + * + * @param p1 first path + * @param p2 second path + * @return true if the paths are adjacent + */ +bool checkIfPathsAdjacent(Path const& p1, Path const& p2) +{ + // if p1 ends after p2 starts, try the other way around + if (p1.nodeIds().back() > p2.nodeIds().front()) + { + return checkIfPathsAdjacent(p2, p1); + } + + // now p1.nodeIds().back() <= p2.nodeIds().front() + const auto& graph = *p1.graphRawPtr(); + + const auto p1_end_node = p1.nodeIds().back(); + const auto p2_start_node = p2.nodeIds().front(); + + if (p1_end_node != p2_start_node && !graph.hasEdge(p1_end_node, p2_start_node)) + { + return false; + } + + // now we are left with two cases: + // p1 ends on node before p2 + // p1 ends on same node as p2 + + // adjacent nodes -- check if graph has an edge + if the start / end positions are compatible + if (p1_end_node != p2_start_node) + { + assert(graph.hasEdge(p1_end_node, p2_start_node)); + return p2.startPosition() == 0 && p1.endPosition() == (int32_t)graph.nodeSeq(p1_end_node).size() - 1; + } + + assert(p1_end_node == p2_start_node); + return p1.endPosition() + 1 == p2.startPosition(); +} + +/** + * Return true if two paths overlap either prefix - suffix or suffix-prefix + * @param p1 first path + * @param p2 second path + * @return true if the paths overlap + */ +bool checkPathPrefixSuffixOverlap(Path const& p1, Path const& p2) +{ + // technically we'd want to check that the two graphs match also + if (p1.numNodes() == 0 || p2.numNodes() == 0) + { + return false; + } + if (p1.nodeIds().back() < p2.nodeIds().front() || // p1 ends before p2 + p1.nodeIds().front() > p2.nodeIds().back()) // p1 starts after p2 + { + return false; + } + + auto p1_it = p1.nodeIds().begin(); + auto p2_it = p2.nodeIds().begin(); + + int shared_nodes = 0; + while (p1_it != p1.nodeIds().end() && p2_it != p2.nodeIds().end()) + { + if (*p1_it < *p2_it) + { + if (p2_it != p2.nodeIds().begin()) + { + // paths diverged + return false; + } + // --> ignore non-matching prefix of p1 until paths meet + ++p1_it; + } + else if (*p1_it > *p2_it) + { + if (p1_it != p1.nodeIds().begin()) + { + // paths diverged + return false; + } + // --> ignore non-matching prefix of p2 until paths meet + ++p2_it; + } + else + { // *p1_it == *p2_it + // paths have met. They must now match until one of them ends + ++shared_nodes; + ++p1_it; + ++p2_it; + } + } + + if (shared_nodes == 0) + { + return false; + } + + // if we only share one node, the paths may not overlap on that node + if (shared_nodes == 1) + { + if (p1_it == p1.nodeIds().end() && p2_it == p2.nodeIds().end()) + { + if (p1.numNodes() > 1 && p2.numNodes() > 1) + { + // if they both have > 1 nodes, they should also share more than one of them; + // otherwise they cannot both end here + assert(false); + } + else if (p1.numNodes() == 1 && p2.numNodes() > 1) + { + // p1 starts here, p2 ends here + if (p2.endPosition() < p1.startPosition()) + { + return false; + } + } + else if (p1.numNodes() > 1 && p2.numNodes() == 1) + { + // p2 starts here, p1 ends here + if (p1.endPosition() < p2.startPosition()) + { + return false; + } + } + else if (p1.numNodes() == 1 && p2.numNodes() == 1) + { + // both paths on same node, check if they overlap there + return p1.endPosition() >= p2.startPosition() && p2.endPosition() >= p1.startPosition(); + } + } + else if (p1_it != p1.nodeIds().end() && p2_it == p2.nodeIds().end()) + { + // p2 starts+ends on this node p1 starts -- check that p1 starts before p2 ends + if (p2.endPosition() < p1.startPosition()) + { + return false; + } + } + else if (p1_it == p1.nodeIds().end() && p2_it != p2.nodeIds().end()) + { + // p1 starts+ends on this node p2 starts -- check that p2 starts before p1 ends + if (p1.endPosition() < p2.startPosition()) + { + return false; + } + } + else + { + // this shouldn't happen. we iterate until one of them reaches end() above + assert(false); + } + } + + return true; +} + +/** + * Paths can be merged if they overlap prefix-suffix / suffix-prefix. + * + * @param p1 first path + * @param p2 second path + * @return merged path + */ +Path mergePaths(Path const& p1, Path const& p2) +{ + assert(checkPathPrefixSuffixOverlap(p1, p2) || checkIfPathsAdjacent(p1, p2)); + + int32_t start = -1; + int32_t end = -1; + std::vector nodes; + auto p1_it = p1.nodeIds().begin(); + auto p2_it = p2.nodeIds().begin(); + while ((p1_it != p1.nodeIds().end()) && (p2_it != p2.nodeIds().end())) + { + if (*p1_it < *p2_it) + { + if (start < 0) + { + start = p1.startPosition(); + } + nodes.push_back(*p1_it); + ++p1_it; + } + else if (*p1_it > *p2_it) + { + if (start < 0) + { + start = p2.startPosition(); + } + nodes.push_back(*p2_it); + ++p2_it; + } + else + { // *p1_it == *p2_it + if (start < 0) + { + start = std::min(p1.startPosition(), p2.startPosition()); + } + nodes.push_back(*p1_it); + ++p1_it; + ++p2_it; + } + } + if (p1_it == p1.nodeIds().end() && p2_it == p2.nodeIds().end()) + { + end = std::max(p1.endPosition(), p2.endPosition()); + } + else if (p1_it != p1.nodeIds().end() && p2_it == p2.nodeIds().end()) + { + nodes.insert(nodes.end(), p1_it, p1.nodeIds().end()); + end = p1.endPosition(); + } + else if (p1_it == p1.nodeIds().end() && p2_it != p2.nodeIds().end()) + { + nodes.insert(nodes.end(), p2_it, p2.nodeIds().end()); + end = p2.endPosition(); + } + assert(start >= 0 && end >= 0); + return Path(p1.graphRawPtr(), start, nodes, end); +} + +/** + * Merge a set of paths + * + * This will merge paths until none of the resulting paths overlap + * + * @param paths a list of paths + */ +void greedyMerge(std::list& paths) +{ + bool has_merged = true; + while (has_merged && paths.size() > 1) + { + auto path_a = paths.begin(); + has_merged = false; + while (path_a != paths.end()) + { + auto path_b = std::next(path_a); + while (path_b != paths.end()) + { + if (checkPathPrefixSuffixOverlap(*path_a, *path_b)) + { + const Path merged_a_b{ mergePaths(*path_a, *path_b) }; + paths.erase(path_a); + paths.erase(path_b); + paths.push_back(merged_a_b); + has_merged = true; + break; + } + ++path_b; + } + if (has_merged) + { + break; + } + ++path_a; + } + } +} + +/** + * Merge a set of paths + * + * This will merge paths exhaustively, each path is merged with all + * paths it overlaps until we cannot merge anymore + * + * @param paths a list of paths + */ +void exhaustiveMerge(std::list& paths) +{ + bool has_merged = true; + while (has_merged && paths.size() > 1) + { + auto path_a = paths.begin(); + has_merged = false; + + list new_paths; + while (path_a != paths.end()) + { + auto path_b = paths.begin(); + while (path_b != paths.end()) + { + if (path_a == path_b) + { + ++path_b; + continue; + } + if (checkPathPrefixSuffixOverlap(*path_a, *path_b)) + { + const Path merged_a_b{ mergePaths(*path_a, *path_b) }; + const bool a_contained_in_b = merged_a_b.encode() == path_b->encode(); + const bool b_contained_in_a = merged_a_b.encode() == path_a->encode(); + const bool a_eq_b = a_contained_in_b && b_contained_in_a; + + if (a_eq_b) + { + // keep only one of them + new_paths.push_back(*path_b); + } + else if (a_contained_in_b || b_contained_in_a) + { + new_paths.push_back(merged_a_b); + } + else + { + new_paths.push_back(merged_a_b); + new_paths.push_back(*path_a); + new_paths.push_back(*path_b); + } + ++path_b; + has_merged = true; + } + else + { + new_paths.push_back(*path_b); + ++path_b; + } + } + if (has_merged) + { + break; + } + new_paths.push_back(*path_a); + ++path_a; + } + if (has_merged) + { + paths = new_paths; + } + } +} + +std::list intersectPaths(Path const& p1, Path const& p2) +{ + std::list result; + + int32_t start = -1; + int32_t end = -1; + std::vector nodes; + + const auto savePath = [&p1, &start, &end, &nodes, &result]() { + if (!nodes.empty()) + { + result.emplace_back(p1.graphRawPtr(), start, nodes, end); + nodes.clear(); + start = -1; + end = -1; + } + }; + + auto p1_it = p1.nodeIds().begin(); + auto p2_it = p2.nodeIds().begin(); + while ((p1_it != p1.nodeIds().end()) && (p2_it != p2.nodeIds().end())) + { + if (*p1_it < *p2_it) + { + savePath(); + ++p1_it; + } + else if (*p1_it > *p2_it) + { + savePath(); + ++p2_it; + } + else + { // *p1_it == *p2_it + const auto p1_nodesize = (int32_t)p1.graphRawPtr()->nodeSeq(*p1_it).size(); + const auto p2_nodesize = (int32_t)p2.graphRawPtr()->nodeSeq(*p2_it).size(); + if (p1_nodesize != p2_nodesize) + { + throw std::logic_error("Intersecting paths on different graphs is not possible."); + } + + const int32_t start_p1 = p1_it == p1.nodeIds().begin() ? p1.startPosition() : 0; + const int32_t start_p2 = p2_it == p2.nodeIds().begin() ? p2.startPosition() : 0; + const int32_t end_p1 = std::next(p1_it) == p1.nodeIds().end() ? p1.endPosition() : p1_nodesize; + const int32_t end_p2 = std::next(p2_it) == p2.nodeIds().end() ? p2.endPosition() : p2_nodesize; + + const int32_t start_pos = std::max(start_p1, start_p2); + const int32_t end_pos = std::min(end_p1, end_p2); + + if (start_pos <= end_pos) + { + // start within the node => cannot extend previous matched path + if (start_pos > 0) + { + savePath(); + } + + if (nodes.empty()) + { + // Not sure why cppcheck complains here. start will be read when we call savePath in line 503 + // cppcheck-suppress unreadVariable + start = start_pos; + } + else if (!p1.graphRawPtr()->hasEdge(nodes.back(), *p1_it)) + { + savePath(); + } + + // Not sure why cppcheck complains here. end will be read when we call savePath in line 503 + // cppcheck-suppress unreadVariable + end = end_pos; + nodes.push_back(*p1_it); + + // ends before end of node => cannot combine with match on next node + if (end_pos + 1 < p1_nodesize) + { + savePath(); + } + } + else if (!nodes.empty()) + { + savePath(); + } + + ++p1_it; + ++p2_it; + } + } + savePath(); + + return result; +} + +list generateSubpathForEachNode(const Path& path) +{ + list subpaths; + + for (size_t node_index = 0; node_index != path.numNodes(); ++node_index) + { + const vector subpath_nodes = { path.getNodeIdByIndex(node_index) }; + const int32_t subpath_start = path.getStartPositionOnNodeByIndex(node_index); + const int32_t subpath_end = path.getEndPositionOnNodeByIndex(node_index); + + subpaths.emplace_back(path.graphRawPtr(), subpath_start, subpath_nodes, subpath_end); + } + + return subpaths; +} + +bool checkIfBookended(const Path& first_path, const Path& second_path) +{ + const NodeId first_path_end_node = first_path.getNodeIdByIndex(first_path.numNodes() - 1); + const NodeId second_path_start_node = second_path.getNodeIdByIndex(0); + const bool are_ends_on_same_node = first_path_end_node == second_path_start_node; + const bool are_positions_adjacent = first_path.endPosition() == second_path.startPosition(); + + if (are_ends_on_same_node && are_positions_adjacent) + { + return true; + } + + const Graph& graph = *first_path.graphRawPtr(); + const auto& successors = graph.successors(first_path_end_node); + const bool are_ends_on_neighboring_nodes = successors.find(second_path_start_node) != successors.end(); + const auto first_paths_last_node_length = static_cast(graph.nodeSeq(first_path_end_node).length()); + const bool is_first_path_ends_at_node_end = first_path.endPosition() == first_paths_last_node_length; + const bool is_second_path_starts_at_node_start = second_path.startPosition() == 0; + + if (are_ends_on_neighboring_nodes && is_first_path_ends_at_node_end && is_second_path_starts_at_node_start) + { + return true; + } + + return false; +} + +Path concatenatePaths(const Path& first_path, const Path& second_path) +{ + if (!checkIfBookended(first_path, second_path)) + { + std::ostringstream msg; + msg << "Paths " << first_path << " and " << second_path << " are not bookended"; + throw std::logic_error(msg.str()); + } + + const NodeId first_path_end_node = first_path.getNodeIdByIndex(first_path.numNodes() - 1); + const NodeId second_path_start_node = second_path.getNodeIdByIndex(0); + const bool are_ends_on_same_node = first_path_end_node == second_path_start_node; + const bool are_positions_adjacent = first_path.endPosition() == second_path.startPosition(); + + vector merged_node_ids; + merged_node_ids.reserve(first_path.nodeIds().size() + second_path.nodeIds().size()); + merged_node_ids.insert(merged_node_ids.end(), first_path.nodeIds().begin(), first_path.nodeIds().end()); + + if (are_ends_on_same_node && are_positions_adjacent) + { + merged_node_ids.insert(merged_node_ids.end(), second_path.nodeIds().begin() + 1, second_path.nodeIds().end()); + } + else + { + merged_node_ids.insert(merged_node_ids.end(), second_path.nodeIds().begin(), second_path.nodeIds().end()); + } + + return Path(first_path.graphRawPtr(), first_path.startPosition(), merged_node_ids, second_path.endPosition()); +} +} diff --git a/thirdparty/graph-tools-master/src/graphutils/DepthTest.cpp b/thirdparty/graph-tools-master/src/graphutils/DepthTest.cpp new file mode 100755 index 0000000..4d232a0 --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphutils/DepthTest.cpp @@ -0,0 +1,55 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Sai Chen , +// Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphutils/DepthTest.hh" + +DepthTest::DepthTest( + int32_t expectedNumReads, double stdDeviation, double lowerSignificanceThreshold, double upperSignificanceThreshold) + : expectedNumReads_(expectedNumReads) + , stdDeviation_(stdDeviation) + , lowerSignificanceThreshold_(lowerSignificanceThreshold) + , upperSignificanceThreshold_(upperSignificanceThreshold) + , coverageDistribution_(expectedNumReads_, stdDeviation_) +{ +} + +bool DepthTest::testReadCount(int32_t observedNumReads) +{ + double coverageTestPvalue = cdf(coverageDistribution_, observedNumReads); + + if (coverageTestPvalue <= 0.5) + { + return coverageTestPvalue < lowerSignificanceThreshold_ ? false : true; + } + else + { + coverageTestPvalue = 1 - coverageTestPvalue; + return coverageTestPvalue < upperSignificanceThreshold_ ? false : true; + } +} diff --git a/thirdparty/graph-tools-master/src/graphutils/IntervalBuffer.cpp b/thirdparty/graph-tools-master/src/graphutils/IntervalBuffer.cpp new file mode 100755 index 0000000..d2b9b94 --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphutils/IntervalBuffer.cpp @@ -0,0 +1,184 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphutils/IntervalBuffer.hh" +#include "graphutils/IntervalList.hh" + +#include +#include +#include +#include +#include +#include + +namespace intervals +{ + +struct IntervalBuffer::Impl +{ + Impl() = default; + Impl(Impl const& rhs) = default; + Impl& operator=(Impl const& rhs) = default; + Impl(Impl&& rhs) = delete; + Impl& operator=(Impl&& rhs) = delete; + ~Impl() = default; + + typedef IntervalList ivmap_t; + + std::vector lanes; +}; + +/** tracks intervals over a number of lanes */ +IntervalBuffer::IntervalBuffer() + : pimpl_(new Impl()) +{ +} + +IntervalBuffer::~IntervalBuffer() = default; + +IntervalBuffer::IntervalBuffer(IntervalBuffer const& rhs) + : pimpl_(new Impl(*rhs.pimpl_)) +{ +} + +IntervalBuffer& IntervalBuffer::operator=(IntervalBuffer const& rhs) +{ + if (&rhs == this) + { + return *this; + } + pimpl_.reset(new Impl(*rhs.pimpl_)); + return *this; +} + +/** + * @brief Add an interval to a lane + * + * @param start interval coordinates + * @param end interval coordinates + * @param lane lane to add to + * @return Interval identifier + */ +void IntervalBuffer::addInterval(int64_t start, int64_t end, size_t lane) +{ + if (start > end) + { + return; + } + if (pimpl_->lanes.size() <= lane) + { + pimpl_->lanes.resize(lane + 1); + } + pimpl_->lanes[lane].add(interval(start, end)); +} + +/** + * @brief Advance buffer, discarding all intervals with start < to + * + * @param to interval minimum end position + */ +void IntervalBuffer::advance(int64_t to) +{ + if (to < 0) + { + pimpl_->lanes.clear(); + return; + } + + for (auto& lane : pimpl_->lanes) + { + lane.remove_to(to - 1); + } +} + +/** + * @brief Check if interval is fully covered in a given lane + */ +bool IntervalBuffer::isCovered(int64_t start, int64_t end, size_t lane) const +{ + if (lane >= pimpl_->lanes.size()) + { + return false; + } + + // intervals of zero length count as covered + if (end < start) + { + return true; + } + + std::list is; + pimpl_->lanes[lane].get(start, end, is); + if (is.size() != 1) + { + // if we overlap with more than one interval, then there must be a gap + return false; + } + + interval& it = is.front(); + return it.start <= start && it.end >= end; +} + +/** + * @brief Check if interval is partially covered in a given lane + */ +bool IntervalBuffer::hasOverlap(int64_t start, int64_t end, size_t lane) const +{ + if (lane >= pimpl_->lanes.size()) + { + return false; + } + + // intervals of zero length count as covered + if (end < start) + { + return true; + } + + interval it = pimpl_->lanes[lane].query(start, end); + return it.start >= 0 && it.end >= 0 && it.end - it.start + 1 > 0; +} + +/** + * Get the intervals for a particular lane + * @return intervals for a particular lane + */ +std::list> IntervalBuffer::getIntervals(size_t lane) const +{ + if (lane >= pimpl_->lanes.size()) + { + throw std::logic_error(std::string("Unknown lane: ") + std::to_string(lane)); + } + std::list> result; + for (auto const& iv : pimpl_->lanes[lane].getIntervals()) + { + result.emplace_back(iv.start, iv.end); + } + return result; +} +} diff --git a/thirdparty/graph-tools-master/src/graphutils/SequenceOperations.cpp b/thirdparty/graph-tools-master/src/graphutils/SequenceOperations.cpp new file mode 100755 index 0000000..136369a --- /dev/null +++ b/thirdparty/graph-tools-master/src/graphutils/SequenceOperations.cpp @@ -0,0 +1,221 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphutils/SequenceOperations.hh" + +#include +#include +#include +#include + +using std::string; +using std::unordered_map; +using std::vector; + +namespace graphtools +{ + +vector splitStringByDelimiter(const std::string& str, char sep) +{ + vector words; + std::stringstream sstream(str); + string word; + while (std::getline(sstream, word, sep)) + { + words.push_back(word); + } + + return words; +} + +vector splitStringByWhitespace(const std::string& str) +{ + vector words; + std::stringstream sstream(str); + string word; + while (sstream >> word) + { + words.push_back(word); + } + return words; +} + +static char complementBase(char base) +{ + switch (base) + { + case 'A': + return 'T'; + case 'a': + return 't'; + case 'C': + return 'G'; + case 'c': + return 'g'; + case 'G': + return 'C'; + case 'g': + return 'c'; + case 'T': + return 'A'; + case 't': + return 'a'; + case 'R': + return 'Y'; + case 'Y': + return 'R'; + case 'K': + return 'M'; + case 'M': + return 'K'; + case 'S': + return 'S'; + case 'W': + return 'W'; + case 'B': + return 'V'; + case 'D': + return 'H'; + case 'H': + return 'D'; + case 'V': + return 'B'; + default: + return 'N'; + } +} + +string reverseComplement(string seq) +{ + std::transform(seq.begin(), seq.end(), seq.begin(), complementBase); + std::reverse(seq.begin(), seq.end()); + return seq; +} + +const unordered_map kSymbolExpansion + = { { 'A', "A" }, { 'C', "C" }, { 'T', "T" }, { 'G', "G" }, { 'R', "AG" }, { 'Y', "CT" }, + { 'K', "GT" }, { 'M', "AC" }, { 'S', "CG" }, { 'W', "AT" }, { 'B', "CGT" }, { 'D', "AGT" }, + { 'H', "ACT" }, { 'V', "ACG" }, { 'N', "ACGT" }, { 'X', "X" } }; + +static bool checkIfNucleotideReferenceSymbol(char symbol) +{ + return (symbol == 'A') || (symbol == 'C') || (symbol == 'T') || (symbol == 'G'); +} + +static bool hasExpandableSymbols(const std::string& s) +{ + static const struct ExpandableSyms + { + ExpandableSyms() + { + for (const auto& sym : kSymbolExpansion) + { + if (sym.second.size() > 1) + { + value.push_back(sym.first); + } + } + } + string value; + } expandableSyms; + return s.find_first_of(expandableSyms.value) != string::npos; +} + +bool checkIfNucleotideReferenceSequence(const std::string& sequence) +{ + for (char symbol : sequence) + { + if (!checkIfNucleotideReferenceSymbol(symbol)) + { + return false; + } + } + return true; +} + +static bool checkIfReferenceSymbol(char symbol) { return kSymbolExpansion.find(symbol) != kSymbolExpansion.end(); } + +bool checkIfReferenceSequence(const std::string& sequence) +{ + for (char symbol : sequence) + { + if (!checkIfReferenceSymbol(symbol)) + { + return false; + } + } + return true; +} + +string const& expandReferenceSymbol(char symbol) +{ + if (!checkIfReferenceSymbol(symbol)) + { + const string symbol_encoding(1, symbol); + throw std::logic_error("Symbol " + symbol_encoding + " is not a valid reference symbol"); + } + return kSymbolExpansion.at(symbol); +} + +void expandReferenceSequence(const string& sequence, vector& expanded_sequences) +{ + if (!hasExpandableSymbols(sequence)) + { + expanded_sequences = { sequence }; + return; + } + expanded_sequences.resize(1); + expanded_sequences.front().clear(); + expanded_sequences.front().reserve(2 * sequence.size()); + + for (char symbol : sequence) + { + const auto& expansions = expandReferenceSymbol(symbol); + + // first expansion: append to all + for (auto& expanded_sequence : expanded_sequences) + { + expanded_sequence.push_back(expansions.front()); + } + + size_t sequences_to_expand = expanded_sequences.size(); + size_t exp_pos = 1; + while (exp_pos < expansions.size()) + { + // more than one expansion: create expanded copy for each of them + for (size_t to_copy_pos = 0; to_copy_pos < sequences_to_expand; ++to_copy_pos) + { + string to_copy = expanded_sequences[to_copy_pos]; + to_copy.back() = expansions[exp_pos]; + expanded_sequences.emplace_back(std::move(to_copy)); + } + exp_pos++; + } + } +} +} diff --git a/thirdparty/graph-tools-master/src/sh/check-format.sh b/thirdparty/graph-tools-master/src/sh/check-format.sh new file mode 100755 index 0000000..4091afb --- /dev/null +++ b/thirdparty/graph-tools-master/src/sh/check-format.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +SOURCE_DIR="${DIR}/../.." +export CLANG_FORMAT=clang-format-5.0 + +set +e +DIFFS=0 +for x in $(find ${SOURCE_DIR} -iname *.h -o -iname *.hh -o -iname *.cpp); do + ${CLANG_FORMAT} -style=file ${x} | diff --ignore-blank-lines --strip-trailing-cr ${x} - + if [[ $? != 0 ]]; then + echo "Differences found in $x" + DIFFS=1 + fi +done + +if [[ ${DIFFS} == 0 ]]; then + echo "No formatting issues found." + exit 0 +else + echo "Some formatting issues were found." + exit 1 +fi \ No newline at end of file diff --git a/thirdparty/graph-tools-master/src/sh/docker-build.sh b/thirdparty/graph-tools-master/src/sh/docker-build.sh new file mode 100755 index 0000000..52d2ad1 --- /dev/null +++ b/thirdparty/graph-tools-master/src/sh/docker-build.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +# Make a build using Docker +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +set -e + +PULL=1 +CLANG=0 +CLANG_ASAN=0 +CLANG_MSAN=0 +VALGRIND=0 +while test $# -gt 0 +do + case "$1" in + --no-pull) echo "Not pulling image."; PULL=0 + ;; + --valgrind) echo "Using valgrind"; VALGRIND=1 + ;; + --clang) echo "Using CLang"; CLANG=1 + ;; + --clang-asan) echo "Using CLang+Address Sanitizer"; CLANG_ASAN=1; CLANG=1 + ;; + --clang-msan) echo "Using CLang+Memory Sanitizer"; CLANG_MSAN=1; CLANG=1 + ;; + *) echo "Unknown argument $1"; exit 1 + ;; + esac + shift +done + +if [[ ${PULL} -ne 0 ]]; then + docker pull ilmncgrpmi/cpp-dev:latest +fi + +EXTRA_CMAKE="" +if [[ ${CLANG} -ne 0 ]]; then + EXTRA_CMAKE="-DCMAKE_CXX_COMPILER=clang++" +fi +if [[ ${CLANG_ASAN} -ne 0 ]]; then + EXTRA_CMAKE="${EXTRA_CMAKE} -DUSE_ASAN=ON" +fi +if [[ ${CLANG_MSAN} -ne 0 ]]; then + EXTRA_CMAKE="${EXTRA_CMAKE} -DUSE_MSAN=ON" +fi + +if [[ ${VALGRIND} -ne 0 ]]; then + VALGRIND='for x in `find /graphtools-build/tests -executable -type f`; do valgrind --leak-check=full --xml=yes --xml-file=/graphtools-source/valgrind_`basename ${x}`.xml $x ; done && find /graphtools-source/valgrind_*.xml | xargs -n1 python /graphtools-source/src/sh/valgrind-check.py &&' +else + VALGRIND="" +fi + +docker run -v ${DIR}/../..:/graphtools-source --rm ilmncgrpmi/cpp-dev:latest /bin/bash -c "mkdir -p /graphtools-build && \ + cd /graphtools-build && \ + cmake ../graphtools-source -DBUILD_TESTS=ON -DCMAKE_INSTALL_PREFIX=/graphtools-install ${EXTRA_CMAKE} && \ + make -j8 && \ + make test && \ + make install && \ + ${VALGRIND} \ + cd /graphtools-install && \ + tar czf /graphtools-source/graphtools-install.tar.gz * " + diff --git a/thirdparty/graph-tools-master/src/sh/docker-check-format.sh b/thirdparty/graph-tools-master/src/sh/docker-check-format.sh new file mode 100755 index 0000000..a0cb90c --- /dev/null +++ b/thirdparty/graph-tools-master/src/sh/docker-check-format.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +set -e + +PULL=1 +while test $# -gt 0 +do + case "$1" in + --no-pull) echo "Not pulling image."; PULL=0 + ;; + *) echo "Unknown argument $1"; exit 1 + ;; + esac + shift +done + +if [[ ${PULL} -ne 0 ]]; then + docker pull ilmncgrpmi/cpp-dev:latest +fi + +LOGFILE=$(mktemp) +docker run -v ${DIR}/../..:/graphtools-source --rm ilmncgrpmi/cpp-dev:latest /bin/bash -c "cd /graphtools-source && \ + /graphtools-source/src/sh/check-format.sh" diff --git a/thirdparty/graph-tools-master/src/sh/docker-cppcheck.sh b/thirdparty/graph-tools-master/src/sh/docker-cppcheck.sh new file mode 100755 index 0000000..280bcf6 --- /dev/null +++ b/thirdparty/graph-tools-master/src/sh/docker-cppcheck.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +set -e + +PULL=1 +while test $# -gt 0 +do + case "$1" in + --no-pull) echo "Not pulling image."; PULL=0 + ;; + *) echo "Unknown argument $1"; exit 1 + ;; + esac + shift +done + +if [[ ${PULL} -ne 0 ]]; then + docker pull ilmncgrpmi/cpp-dev:latest +fi + +LOGFILE=$(mktemp) +docker run -v ${DIR}/../..:/graphtools-source --rm ilmncgrpmi/cpp-dev:latest /bin/bash -c "cd /graphtools-source && \ + cppcheck --enable=all --suppress=missingIncludeSystem --suppress=unusedFunction --inline-suppr -I/graphtools-source/include /graphtools-source/src > /dev/null" &> ${LOGFILE} + +cat ${LOGFILE} + +MESSAGES=$(cat ${LOGFILE} | wc -l) +if [[ ${MESSAGES} -ne 0 ]]; then + echo "$MESSAGES cppcheck errors were found!" + exit 1 +fi + +exit 0 diff --git a/thirdparty/graph-tools-master/src/sh/docker-format-everything.sh b/thirdparty/graph-tools-master/src/sh/docker-format-everything.sh new file mode 100755 index 0000000..aaac3eb --- /dev/null +++ b/thirdparty/graph-tools-master/src/sh/docker-format-everything.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +set -e + +PULL=1 +while test $# -gt 0 +do + case "$1" in + --no-pull) echo "Not pulling image."; PULL=0 + ;; + *) echo "Unknown argument $1"; exit 1 + ;; + esac + shift +done + +if [[ ${PULL} -ne 0 ]]; then + docker pull ilmncgrpmi/cpp-dev:latest +fi + +LOGFILE=$(mktemp) +docker run -v ${DIR}/../..:/graphtools-source --rm ilmncgrpmi/cpp-dev:latest /bin/bash -c "cd /graphtools-source && \ + /graphtools-source/src/sh/format-everything.sh" \ No newline at end of file diff --git a/thirdparty/graph-tools-master/src/sh/format-everything.sh b/thirdparty/graph-tools-master/src/sh/format-everything.sh new file mode 100755 index 0000000..d6c9d8e --- /dev/null +++ b/thirdparty/graph-tools-master/src/sh/format-everything.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +CLANG_FORMAT=clang-format + +if [ ! -x "$(command -v ${CLANG_FORMAT})" ]; then + CLANG_FORMAT=clang-format-5.0 +fi + +if [ ! -x "$(command -v ${CLANG_FORMAT})" ]; then + CLANG_FORMAT=clang-format-mp-5.0 +fi + +if [ ! -x "$(command -v ${CLANG_FORMAT})" ]; then + echo "Clang-format not found, please install as clang-format or clang-format-mp-5.0" + exit 1 +fi + +if [[ -z "$(${CLANG_FORMAT} --version | grep 'version 5')" ]]; then + echo "Clang-format has the wrong version, please install as clang-format version 5.0" + exit 1 +fi + +find ${DIR}/../../src -iname *.h -o -iname *.hh -o -iname *.cpp | xargs ${CLANG_FORMAT} -i -style=file +find ${DIR}/../../include -iname *.h -o -iname *.hh -o -iname *.cpp | xargs ${CLANG_FORMAT} -i -style=file +find ${DIR}/../../tests -iname *.h -o -iname *.hh -o -iname *.cpp | xargs ${CLANG_FORMAT} -i -style=file diff --git a/thirdparty/graph-tools-master/src/sh/valgrind-check.py b/thirdparty/graph-tools-master/src/sh/valgrind-check.py new file mode 100755 index 0000000..c78516c --- /dev/null +++ b/thirdparty/graph-tools-master/src/sh/valgrind-check.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# Simple checker for whether valgrind found errors + +import sys +import xml.etree.ElementTree as ElementTree + +e = ElementTree.parse(sys.argv[1]) + +states = [x.find('state').text for x in e.findall('status')] +errors = [x.find('kind').text for x in e.findall('error')] + +if "RUNNING" not in states or "FINISHED" not in states: + raise Exception("Valgrind didn't run successfully, states seen: %s" % str(states)) + +if errors: + raise Exception("Valgrind found some errors in %s: %s" % (sys.argv[1], str(errors))) + +sys.exit(0) diff --git a/thirdparty/graph-tools-master/tests/BaseMatchingTest.cpp b/thirdparty/graph-tools-master/tests/BaseMatchingTest.cpp new file mode 100755 index 0000000..4806dee --- /dev/null +++ b/thirdparty/graph-tools-master/tests/BaseMatchingTest.cpp @@ -0,0 +1,60 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphutils/BaseMatching.hh" + +#include "gtest/gtest.h" + +using namespace graphtools; + +TEST(MatchingBases, CoreBases_Matched) +{ + EXPECT_TRUE(checkIfReferenceBaseMatchesQueryBase('A', 'A')); + EXPECT_TRUE(checkIfReferenceBaseMatchesQueryBase('T', 't')); + EXPECT_TRUE(checkIfReferenceBaseMatchesQueryBase('c', 'C')); + EXPECT_TRUE(checkIfReferenceBaseMatchesQueryBase('G', 'g')); + + EXPECT_FALSE(checkIfReferenceBaseMatchesQueryBase('T', 'c')); +} + +TEST(MatchingBases, DegenerateBases_Matched) +{ + EXPECT_TRUE(checkIfReferenceBaseMatchesQueryBase('Y', 'c')); + EXPECT_TRUE(checkIfReferenceBaseMatchesQueryBase('Y', 'T')); + EXPECT_TRUE(checkIfReferenceBaseMatchesQueryBase('W', 'a')); + EXPECT_TRUE(checkIfReferenceBaseMatchesQueryBase('N', 'A')); + + EXPECT_FALSE(checkIfReferenceBaseMatchesQueryBase('Y', 'a')); +} + +TEST(MatchingStrings, TypicalStrings_Matched) +{ + EXPECT_TRUE(checkIfReferenceAndQuerySequencesMatch("RRTCS", "AaTCG")); + EXPECT_FALSE(checkIfReferenceAndQuerySequencesMatch("WG", "CG")); + EXPECT_FALSE(checkIfReferenceAndQuerySequencesMatch("CC", "CCC")); +} diff --git a/thirdparty/graph-tools-master/tests/CMakeLists.txt b/thirdparty/graph-tools-master/tests/CMakeLists.txt new file mode 100755 index 0000000..156a770 --- /dev/null +++ b/thirdparty/graph-tools-master/tests/CMakeLists.txt @@ -0,0 +1,122 @@ +add_executable(SequenceOperationsTest SequenceOperationsTest.cpp) +target_link_libraries(SequenceOperationsTest graphtools gtest_main) +add_test(NAME SequenceOperationsTest COMMAND SequenceOperationsTest) + +add_executable(BaseMatchingTest BaseMatchingTest.cpp) +target_link_libraries(BaseMatchingTest graphtools gtest_main) +add_test(NAME BaseMatchingTest COMMAND BaseMatchingTest) + +add_executable(OperationTest OperationTest.cpp) +target_link_libraries(OperationTest graphtools gtest_main) +add_test(NAME OperationTest COMMAND OperationTest) + +add_executable(OperationOperationsTest OperationOperationsTest.cpp) +target_link_libraries(OperationOperationsTest graphtools gtest_main) +add_test(NAME OperationOperationsTest COMMAND OperationOperationsTest) + +add_executable(LinearAlignmentTest LinearAlignmentTest.cpp) +target_link_libraries(LinearAlignmentTest graphtools gtest_main) +add_test(NAME LinearAlignmentTest COMMAND LinearAlignmentTest) + +add_executable(LinearAlignmentOperationsTest LinearAlignmentOperationsTest.cpp) +target_link_libraries(LinearAlignmentOperationsTest graphtools gtest_main) +add_test(NAME LinearAlignmentOperationsTest COMMAND LinearAlignmentOperationsTest) + +add_executable(TracebackMatrixTest TracebackMatrixTest.cpp) +target_link_libraries(TracebackMatrixTest graphtools gtest_main) +add_test(NAME TracebackMatrixTest COMMAND TracebackMatrixTest) + +add_executable(TracebackRunnerTest TracebackRunnerTest.cpp) +target_link_libraries(TracebackRunnerTest graphtools gtest_main) +add_test(NAME TracebackRunnerTest COMMAND TracebackRunnerTest) + +add_executable(PinnedAlignerTest PinnedAlignerTest.cpp) +target_link_libraries(PinnedAlignerTest graphtools gtest_main) +add_test(NAME PinnedAlignerTest COMMAND PinnedAlignerTest) + +add_executable(DagAlignerTest DagAlignerTest.cpp) +target_link_libraries(DagAlignerTest graphtools gtest_main) +add_test(NAME DagAlignerTest COMMAND DagAlignerTest) + +add_executable(PinnedDagAlignerTest PinnedDagAlignerTest.cpp) +target_link_libraries(PinnedDagAlignerTest graphtools gtest_main) +add_test(NAME PinnedDagAlignerTest COMMAND PinnedDagAlignerTest) + +add_executable(GraphAlignmentTest GraphAlignmentTest.cpp) +target_link_libraries(GraphAlignmentTest graphtools gtest_main) +add_test(NAME GraphAlignmentTest COMMAND GraphAlignmentTest) + +add_executable(GraphAlignmentOperationsTest GraphAlignmentOperationsTest.cpp) +target_link_libraries(GraphAlignmentOperationsTest graphtools gtest_main) +add_test(NAME GraphAlignmentOperationsTest COMMAND GraphAlignmentOperationsTest) + +add_executable(GraphTest GraphTest.cpp) +target_link_libraries(GraphTest graphtools gtest_main) +add_test(NAME GraphTest COMMAND GraphTest) + +add_executable(GraphBuildersTest GraphBuildersTest.cpp) +target_link_libraries(GraphBuildersTest graphtools gtest_main) +add_test(NAME GraphBuildersTest COMMAND GraphBuildersTest) + +add_executable(PathTest PathTest.cpp) +target_link_libraries(PathTest graphtools gtest_main) +add_test(NAME PathTest COMMAND PathTest) + +add_executable(PathOperationsTest PathOperationsTest.cpp) +target_link_libraries(PathOperationsTest graphtools gtest_main) +add_test(NAME PathOperationsTest COMMAND PathOperationsTest) + +add_executable(KmerIndexTest KmerIndexTest.cpp) +target_link_libraries(KmerIndexTest graphtools gtest_main) +add_test(NAME KmerIndexTest COMMAND KmerIndexTest) + +add_executable(KmerIndexOperationsTest KmerIndexOperationsTest.cpp) +target_link_libraries(KmerIndexOperationsTest graphtools gtest_main) +add_test(NAME KmerIndexOperationsTest COMMAND KmerIndexOperationsTest) + +add_executable(GaplessAlignerTest GaplessAlignerTest.cpp) +target_link_libraries(GaplessAlignerTest graphtools gtest_main) +add_test(NAME GaplessAlignerTest COMMAND GaplessAlignerTest) + +add_executable(GappedAlignerTest GappedAlignerTest.cpp) +target_link_libraries(GappedAlignerTest graphtools gtest_main) +add_test(NAME GappedAlignerTest COMMAND GappedAlignerTest) + +add_executable(GraphCoordinatesTest GraphCoordinatesTest.cpp) +target_link_libraries(GraphCoordinatesTest graphtools gtest_main) +add_test(NAME GraphCoordinatesTest COMMAND GraphCoordinatesTest) + +add_executable(GraphOperationsTest GraphOperationsTest.cpp) +target_link_libraries(GraphOperationsTest graphtools gtest_main) +add_test(NAME GraphOperationsTest COMMAND GraphOperationsTest) + +add_executable(PathFamilyTest PathFamilyTest.cpp) +target_link_libraries(PathFamilyTest graphtools gtest_main) +add_test(NAME PathFamilyTest COMMAND PathFamilyTest) + +add_executable(PathFamilyOperationsTest PathFamilyOperationsTest.cpp) +target_link_libraries(PathFamilyOperationsTest graphtools gtest_main) +add_test(NAME PathFamilyOperationsTest COMMAND PathFamilyOperationsTest) + +add_executable(IntervalBufferTest IntervalBufferTest.cpp) +target_link_libraries(IntervalBufferTest graphtools gtest_main) +add_test(NAME IntervalBufferTest COMMAND IntervalBufferTest) + +add_executable(IntervalListTest IntervalListTest.cpp) +target_link_libraries(IntervalListTest graphtools gtest_main) +add_test(NAME IntervalListTest COMMAND IntervalListTest) + +add_executable(GraphReferenceMappingTest GraphReferenceMappingTest.cpp) +target_link_libraries(GraphReferenceMappingTest graphtools gtest_main) +add_test(NAME GraphReferenceMappingTest COMMAND GraphReferenceMappingTest) + +add_executable(DepthTestTest DepthTestTest.cpp) +target_link_libraries(DepthTestTest graphtools gtest_main) +add_test(NAME DepthTestTest COMMAND DepthTestTest) + + +if (BUILD_GRAPHIO) + add_executable(GraphIOTest GraphIOTest.cpp) + target_link_libraries(GraphIOTest graphIO graphtools gtest_main) + add_test(NAME GraphIOTest COMMAND GraphIOTest) +endif(BUILD_GRAPHIO) \ No newline at end of file diff --git a/thirdparty/graph-tools-master/tests/DagAlignerTest.cpp b/thirdparty/graph-tools-master/tests/DagAlignerTest.cpp new file mode 100755 index 0000000..199a731 --- /dev/null +++ b/thirdparty/graph-tools-master/tests/DagAlignerTest.cpp @@ -0,0 +1,783 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Roman Petrovski +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/DagAlignerAffine.hh" + +#include "gtest/gtest.h" + +using std::string; + +using namespace graphalign; +using namespace graphalign::dagAligner; + +template std::string toString(const T& obj) +{ + std::stringstream ss; + ss << obj; + return ss.str(); +} + +TEST(SimpleAlignment, Short_to_short) +{ + DagAligner aligner({ 5, -4 }, 0, -8); + + EdgeMap edges(std::vector>({ { 151, 151 } }), std::vector({ 0 })); + + const string query = "tgCccgcCCcCCCCcccC"; + + const string reference = "TGCAGTCCCGCCCCGTCCC"; + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + Score secondBestScore = 0; + std::vector cigars; + const Score bestScore = aligner.backtrackAllPaths(edges, cigars, secondBestScore); + + EXPECT_EQ(std::size_t(3), cigars.size()); + EXPECT_EQ("0[3=3X3=1X4=1X1D3=]", toString(cigars.at(2))); + EXPECT_EQ("0[3=3X3=1X4=1D1X3=]", toString(cigars.at(1))); + EXPECT_EQ("0[3=3X3=1D4=2X3=]", toString(cigars.at(0))); + EXPECT_EQ(37, bestScore); +} + +TEST(SimpleAlignment, Long_to_Long) +{ + DagAligner aligner({ 5, -4 }, 0, -8); + + EdgeMap edges(std::vector>({ { 151, 151 } }), std::vector({ 0 })); + + const string query = "TCTCGCCCCGCCCCTCAGGCGGCCTCCCTGCtgtgCCCCGCCCCGGCCcCGCCCCgCCCCcCCCCCcCCaCgCCCCCCcCccCcCCCCgCCCC" + "CCCCctCcCCCCccctCCCCtccCCtgCccgcCCcCCCCcccC"; + const string reference = "CCGCCCCGCCCCCGTCTCGCCCCGCCCCTCAGGCGGCCTCCCTGCTGTGCCCCGCCCCGGCCTCGCCACGCCCCTACCTCACCACGCCC" + "CCCGCATCGCCACGCCCCCCGCATCGCCACGCCTCCCTTACCATGCAGTCCCGCCCCGTCCC"; + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + Score secondBestScore = 0; + std::vector cigars; + const Score bestScore = aligner.backtrackAllPaths(edges, cigars, secondBestScore); + EXPECT_EQ(std::size_t(3), cigars.size()); + + EXPECT_EQ( + "0[14D48=1X4=1X6=2X2=1X1=1X11=1X1=2X1=1X2=1X8=1X1=1X2=1X2=1X1=1X6=1X1=1X2=1X3=3X3=1X4=1X1D3=]", + toString(cigars.at(2))); + EXPECT_EQ( + "0[14D48=1X4=1X6=2X2=1X1=1X11=1X1=2X1=1X2=1X8=1X1=1X2=1X2=1X1=1X6=1X1=1X2=1X3=3X3=1X4=1D1X3=]", + toString(cigars.at(1))); + EXPECT_EQ( + "0[14D48=1X4=1X6=2X2=1X1=1X11=1X1=2X1=1X2=1X8=1X1=1X2=1X2=1X1=1X6=1X1=1X2=1X3=3X3=1D4=2X3=]", + toString(cigars.at(0))); + EXPECT_EQ(344, bestScore); +} + +TEST(SimpleAlignment, AAAC_to_AGC) +{ + DagAligner aligner({ 1, -1 }, 0, -2); + + EdgeMap edges(std::vector>({ { 3, 3 } }), std::vector({ 0 })); + + const string query = "AAAC"; + const string reference = "AGC"; + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + Score secondBestScore = 0; + + std::vector cigars; + Score bestScore = aligner.backtrackAllPaths(edges, cigars, secondBestScore); + EXPECT_EQ(std::size_t(3), cigars.size()); + EXPECT_EQ("0[1S1=1X1=]", toString(cigars.at(0))); + EXPECT_EQ("0[1=1I1X1=]", toString(cigars.at(1))); + EXPECT_EQ("0[1=1X1I1=]", toString(cigars.at(2))); + + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ("0[1S1=1X1=]", toString(cigar)); +} + +TEST(SimpleAlignment, ATGC_to_AGC) +{ + DagAligner aligner({ 1, -1 }, 0, -2); + + const string query = "ATGC"; + const string reference = "AGC"; + + EdgeMap edges(std::vector>({ { 3, 3 } }), std::vector({ 0 })); + + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + Score bestScore = 0; + Score secondBestScore = 0; + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ("0[1=1I2=]", toString(cigar)); +} + +TEST(SimpleAlignment, TAACTTTTGGG_to_TGCTTTTAA) +{ + // query: TAACTTTTGGG + // |x |||||xx + // reference: TG-CTTTTAA- + + const string query = "TAACTTTTGGG"; + const string reference = "TGCTTTTAA"; + + DagAligner aligner({ 1, -1 }, 0, -2); + + EdgeMap edges(std::vector>({ { 9, 9 } }), std::vector({ 0 })); + + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + std::vector cigars; + Score secondBestScore = 0; + Score bestScore = aligner.backtrackAllPaths(edges, cigars, secondBestScore); + EXPECT_EQ(std::size_t(2), cigars.size()); + EXPECT_EQ("0[1=1I1X5=3S]", toString(cigars.at(0))); + EXPECT_EQ("0[1=1X1I5=3S]", toString(cigars.at(1))); + + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + + EXPECT_EQ("0[1=1I1X5=3S]", toString(cigar)); +} + +TEST(SimpleAlignment, TCACGGAGA_to_TACGAGAG) +{ + // TCACGGAGA + // | ||| ||| + // T-ACG-AGAG + + const string query = "TCACGGAGA"; + const string reference = "TACGAGAG"; + + DagAligner aligner({ 5, -4 }, 0, -8); + + EdgeMap edges(std::vector>({ { 8, 8 } }), std::vector({ 0 })); + + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + std::vector cigars; + Score secondBestScore = 0; + Score bestScore = aligner.backtrackAllPaths(edges, cigars, secondBestScore); + EXPECT_EQ(std::size_t(2), cigars.size()); + EXPECT_EQ("0[1=1I2=1I4=]", toString(cigars.at(0))); + EXPECT_EQ("0[1=1I3=1I3=]", toString(cigars.at(1))); + + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ("0[1=1I2=1I4=]", toString(cigar)); +} + +TEST(ForkAlignment, AAAC_to_AAC_fork_AAC) +{ + DagAligner aligner({ 1, -1 }, 0, -2); + + const string query = "AAAC"; + const string reference = "AAC" + "AAC"; + + EdgeMap edges(std::vector>({ { -1, 3 }, { 6, 6 } }), std::vector({ 0, 1 })); + + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + std::vector cigars; + Score secondBestScore = 0; + Score bestScore = aligner.backtrackAllPaths(edges, cigars, secondBestScore); + EXPECT_EQ(std::size_t(6), cigars.size()); + EXPECT_EQ("0[1S3=]", toString(cigars.at(0))); + EXPECT_EQ("0[1=1I2=]", toString(cigars.at(1))); + EXPECT_EQ("0[2=1I1=]", toString(cigars.at(2))); + EXPECT_EQ("1[1S3=]", toString(cigars.at(3))); + EXPECT_EQ("1[1=1I2=]", toString(cigars.at(4))); + EXPECT_EQ("1[2=1I1=]", toString(cigars.at(5))); + + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ("0[1S3=]", toString(cigar)); +} + +TEST(ForkAlignment, AAAC_to_AGC_fork_AAC) +{ + DagAligner aligner({ 1, -1 }, 0, -2); + + const string query = "AAAC"; + const string reference = "AGC" + "AAC"; + EdgeMap edges(std::vector>({ { -1, 3 }, { 6, 6 } }), std::vector({ 0, 1 })); + + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + std::vector cigars; + Score secondBestScore = 0; + Score bestScore = aligner.backtrackAllPaths(edges, cigars, secondBestScore); + EXPECT_EQ(std::size_t(3), cigars.size()); + EXPECT_EQ("1[1S3=]", toString(cigars.at(0))); + EXPECT_EQ("1[1=1I2=]", toString(cigars.at(1))); + EXPECT_EQ("1[2=1I1=]", toString(cigars.at(2))); + + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ("1[1S3=]", toString(cigar)); +} + +TEST(Fork, AAC_to_AGC_AAC) +{ + DagAligner aligner({ 1, -1 }, 0, -2); + + const string query = "AAC"; + const string reference = "AGC" + "AAC"; + + EdgeMap edges(std::vector>({ { -1, 3 }, { 6, 6 } }), std::vector({ 0, 1 })); + + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + Score bestScore = 0; + Score secondBestScore = 0; + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ("1[3=]", toString(cigar)); +} + +/* + * A + * / \ + * A C + * \ / + * G + */ +TEST(ForkJoin1Base, AAC_to_AGC_AAC) +{ + DagAligner aligner({ 1, -1 }, 0, -2); + + const string query = "AAC"; + const string reference = "A" + "A" + "G" + "C"; + + EdgeMap edges( + std::vector>({ { 0, 1 }, { 0, 2 }, { 1, 3 }, { 2, 3 }, { 4, 4 } }), + std::vector({ 0, 1, 2, 3 })); + + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + Score bestScore = 0; + Score secondBestScore = 0; + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ("0[1=]1[1=]3[1=]", toString(cigar)); +} + +/* + * AA + * / \ + * A C + * \ / + * AG + */ +TEST(ForkJoin2Base, AAGC_to_AAGC_AAAC) +{ + DagAligner aligner({ 1, -1 }, 0, -2); + + const string query = "AAGC"; + const string reference = "A" + "AA" + "AG" + "C"; + + EdgeMap edges( + std::vector>({ { 0, 1 }, { 0, 3 }, { 2, 5 }, { 4, 5 }, { 6, 6 } }), + std::vector({ 0, 1, 2, 3 })); + + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + Score bestScore = 0; + Score secondBestScore = 0; + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ("0[1=]2[2=]3[1=]", toString(cigar)); +} + +/* + * AA + * / \ + * A C + * \ / + * AC + */ +TEST(ForkJoin2Base, AAGC_to_AAAC_AACC) +{ + DagAligner aligner({ 1, -1 }, 0, -2); + + const string query = "AAGC"; + const string reference = "A" + "AA" + "AC" + "C"; + EdgeMap edges( + std::vector>({ { 0, 1 }, { 0, 3 }, { 2, 5 }, { 4, 5 }, { 6, 6 } }), + std::vector({ 0, 1, 2, 3 })); + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + std::vector cigars; + Score secondBestScore = 0; + Score bestScore = aligner.backtrackAllPaths(edges, cigars, secondBestScore); + EXPECT_EQ(std::size_t(2), cigars.size()); + EXPECT_EQ("0[1=]1[1=1X]3[1=]", toString(cigars.at(0))); + EXPECT_EQ("0[1=]2[1=1X]3[1=]", toString(cigars.at(1))); + + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ("0[1=]2[1=1X]3[1=]", toString(cigar)); +} + +/* + * AA + * / \ + * A C + * \ / + * AC + */ +TEST(ForkJoin2Base, AAC_to_AAAC_AACC) +{ + DagAligner aligner({ 1, -1 }, 0, -2); + + const string query = "AAC"; + const string reference = "A" + "AA" + "AC" + "C"; + EdgeMap edges( + std::vector>({ { 0, 1 }, { 0, 3 }, { 2, 5 }, { 4, 5 }, { 6, 6 } }), + std::vector({ 0, 1, 2, 3 })); + + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + Score bestScore = 0; + Score secondBestScore = 0; + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ("0[1=]2[2=]", toString(cigar)); +} + +/* + * AAAA + * \ + * T + * / + * GACC + */ +TEST(JoinStartAtOffset, ACCT_to_AAAAT_GACCT) +{ + DagAligner aligner({ 1, -1 }, 0, -2); + + const string query = "ACCT"; + const string reference = "AAAA" + "GACC" + "T"; + + EdgeMap edges( + std::vector>({ { -1, 4 }, { 3, 8 }, { 7, 8 }, { 9, 9 } }), std::vector({ 0, 1, 2 })); + + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + Score bestScore = 0; + Score secondBestScore = 0; + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ("1[1D3=]2[1=]", toString(cigar)); +} + +/* + * AA + * \ + * T + * / + * C + */ +TEST(JoinStartAtOffset, AAT_to_AAT_CT) +{ + DagAligner aligner({ 1, -1 }, 0, -2); + + const string query = "AAT"; + const string reference = "AA" + "C" + "T"; + + EdgeMap edges( + std::vector>({ { -1, 2 }, { 1, 3 }, { 2, 3 }, { 4, 4 } }), std::vector({ 0, 1, 2 })); + + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + EXPECT_EQ( + "Aligner(AffineAlignMatrix(AlignMatrix(\n" + "[0\t-2\t-4\t-6]\n" + "[-2\t1\t-1\t-3]\n" + "[-4\t-1\t2\t0]\n" + "[-2\t-1\t-3\t-5]\n" + "[-4\t-3\t0\t3]\n" + ")))", + toString(aligner)); + + Score bestScore = 0; + Score secondBestScore = 0; + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ("0[2=]2[1=]", toString(cigar)); +} + +/* + * C + * \ + * T + * / + * AA + */ +TEST(JoinStartAtOffset, AAT_to_CT_AAT) +{ + DagAligner aligner({ 1, -1 }, 0, -2); + + const string query = "AAT"; + const string reference = "C" + "AA" + "T"; + + EdgeMap edges( + std::vector>({ { -1, 1 }, { 0, 3 }, { 2, 3 }, { 4, 4 } }), std::vector({ 0, 1, 2 })); + + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + EXPECT_EQ( + "Aligner(AffineAlignMatrix(AlignMatrix(\n" + "[0\t-2\t-4\t-6]\n" + "[-2\t-1\t-3\t-5]\n" + "[-2\t1\t-1\t-3]\n" + "[-4\t-1\t2\t0]\n" + "[-4\t-3\t0\t3]\n" + ")))", + toString(aligner)); + + Score bestScore = 0; + Score secondBestScore = 0; + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ("1[2=]2[1=]", toString(cigar)); +} + +/* + * AA + * / \ + * TCGTGTAA CCCCCCCCTTTTT + * \ / + * GC + */ +TEST(ForkJoinLong, AAGCCCCCCCCCTTTTT_to_TCGTGTAACCCCCCCCTTTTT_TCGTGTGCCCCCCCCCTTTTT) +{ + DagAligner aligner({ 1, -1 }, 0, -2); + + const string query = "AAGCCCCCCCCCTTTTT"; + const string reference = "TCGTGTAA" + "AA" + "GC" + "CCCCCCCCTTTTT"; + + EdgeMap edges( + std::vector>({ { 7, 8 }, { 7, 10 }, { 9, 12 }, { 11, 12 }, { 25, 25 } }), + std::vector({ 0, 1, 2, 3 })); + + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + Score bestScore = 0; + Score secondBestScore = 0; + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ("0[6D2=]2[2=]3[13=]", toString(cigar)); +} + +/* + * AAA + * / \ + * TCGTGTAA CCCCCCCCTTTTT + * \ / + * GC + */ +TEST(ForkJoinLong, AAGCCCCCCCCCTTTTT_to_TCGTGTAAAAACCCCCCCCTTTTT_TCGTGTAAGCCCCCCCCCTTTTT) +{ + DagAligner aligner({ 1, -1 }, 0, -2); + + const string query = "AAGCCCCCCCCCTTTTT"; + const string reference = "TCGTGTAA" + "AAA" + "GC" + "CCCCCCCCTTTTT"; + + EdgeMap edges( + std::vector>({ { 7, 8 }, { 7, 11 }, { 10, 13 }, { 12, 13 }, { 26, 26 } }), + std::vector({ 0, 1, 2, 3 })); + + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + Score bestScore = 0; + Score secondBestScore = 0; + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ("0[6D2=]2[2=]3[13=]", toString(cigar)); +} + +/* + * A--- TT + * \ / + * T + * / \ + * GACC C + */ +class SimpleGraphTest : public testing::Test +{ +protected: + string reference; + EdgeMap edges; + DagAligner aligner; + Score bestScore = 0; + Score secondBestScore = 0; + + SimpleGraphTest() + : reference("A" + "GACC" + "T" + "TT" + "C") + , edges( + std::vector>({ { -1, 1 }, { 0, 5 }, { 4, 5 }, { 5, 6 }, { 5, 8 }, { 9, 9 } }), + std::vector({ 0, 1, 2, 3, 4 })) + , aligner({ 1, -1 }, 0, -2) + { + } +}; + +TEST_F(SimpleGraphTest, OffEnd) +{ + string query = "ATCTG"; + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + std::vector cigars; + bestScore = aligner.backtrackAllPaths(edges, cigars, secondBestScore); + EXPECT_EQ(std::size_t(2), cigars.size()); + EXPECT_EQ("0[1=]2[1=1I]3[1=1S]", toString(cigars.at(0))); + EXPECT_EQ("0[1=]2[1=]3[1X1=1S]", toString(cigars.at(1))); + + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ(0, bestScore); + EXPECT_EQ("0[1=]2[1=1I]3[1=1S]", toString(cigar)); +} +// +TEST_F(SimpleGraphTest, QueryAllN) +{ + string query = "N"; + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + std::vector cigars; + bestScore = aligner.backtrackAllPaths(edges, cigars, secondBestScore); + EXPECT_EQ(std::size_t(2), cigars.size()); + EXPECT_EQ("0[1=]", toString(cigars.at(0))); + EXPECT_EQ("1[1=]", toString(cigars.at(1))); + + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ(1, bestScore); + EXPECT_EQ("0[1=]", toString(cigar)); + + cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ(1, bestScore); + EXPECT_EQ("0[1=]", toString(cigar)); + + query = "NNNNNNNNNN"; + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + cigars.clear(); + bestScore = aligner.backtrackAllPaths(edges, cigars, secondBestScore); + EXPECT_EQ(std::size_t(1), cigars.size()); + EXPECT_EQ("1[4=]2[1=]3[2=3S]", toString(cigars.at(0))); + + cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ(7, bestScore); + EXPECT_EQ("1[4=]2[1=]3[2=3S]", toString(cigar)); + + cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ(1, bestScore); + EXPECT_EQ("1[3S4=]2[1=]3[2=]", toString(cigar)); +} + +TEST_F(SimpleGraphTest, QuerySomeN) +{ + string query = "GANCNC"; + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ(6, bestScore); + EXPECT_EQ("1[4=]2[1=]4[1=]", toString(cigar)); +} + +TEST_F(SimpleGraphTest, EmptyQuery) +{ + string query = ""; + EXPECT_ANY_THROW(aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges)); +} + +TEST_F(SimpleGraphTest, BadQualities) +{ + string query = "gACc"; + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ(4, bestScore); + EXPECT_EQ("1[4=]", toString(cigar)); +} + +/* + * _ + * / \ + * G-TCC-AAAAA + */ +TEST(RepeatExpansion, SimpleRepeat) +{ + DagAligner aligner({ 1, -1 }, 0, -2); + + const string reference = "G" + "TCC" + "TCC" + "TCC" + "AAAAA"; + + EdgeMap edges( + std::vector>({ { 0, 1 }, + { 3, 4 }, + { 6, 7 }, + { 3, 10 }, + { 6, 10 }, + { 9, 10 }, + { reference.length(), reference.length() } }), + std::vector({ 0, 1, 2, 3, 4 })); + + string query = "TCCTCCAA"; + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + Score bestScore = 0; + Score secondBestScore = 0; + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ(6, bestScore); + EXPECT_EQ("1[3=]2[3=]4[2=]", toString(cigar)); + + query = "GTCTCCCCAA"; + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + std::vector cigars; + bestScore = aligner.backtrackAllPaths(edges, cigars, secondBestScore); + EXPECT_EQ(std::size_t(2), cigars.size()); + EXPECT_EQ("0[1=]1[1=1D1=]2[3=]3[1D2=]4[2=]", toString(cigars.at(0))); + EXPECT_EQ("0[1=]1[2=1D]2[3=]3[1D2=]4[2=]", toString(cigars.at(1))); + + cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ(6, bestScore); + EXPECT_EQ("0[1=]1[1=1D1=]2[3=]3[1D2=]4[2=]", toString(cigar)); +} + +/* + * |\ |\ + * G--TCC-C + */ +TEST(RepeatExpansion, HomoPolymers) +{ + DagAligner aligner({ 1, -1 }, 0, -2); + + const string query = "GGTCCGC"; + const string reference = "G" + "G" + "G" + "TCC" + "C" + "C"; + + EdgeMap edges( + std::vector>({ { 0, 1 }, + { 1, 2 }, + { 0, 3 }, + { 1, 3 }, + { 2, 3 }, + { 5, 6 }, + { 6, 7 }, + { reference.length(), reference.length() } }), + std::vector({ 0, 1, 2, 3, 4, 5 })); + + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + Score bestScore = 0; + Score secondBestScore = 0; + Cigar cigar = aligner.backtrackBestPath(edges, bestScore, secondBestScore); + EXPECT_EQ(5, bestScore); + EXPECT_EQ("0[1=]1[1=]3[3=]4[1X]5[1=]", toString(cigar)); +} + +/* + * --- + * / \ + * C-GCC-GACAAC-GAC-CTTCCTGAACT + * \ / \ / + * - - + */ +TEST(RepeatExpansion, two_repeats) +{ + DagAligner aligner({ 5, -4 }, 0, -8); + + const string query = "CgCCGCCA"; + const string reference = "C" + "GCC" + "GCC" + "GCC" + "GCC" + "GCC" + "GCC" + "GACAAC" + "GAC" + "GAC" + "GAC" + "GAC" + "CTTCCTGAACT"; + + EdgeMap edges( + std::vector>({ { 0, 1 }, + { 3, 4 }, + { 6, 7 }, + { 9, 10 }, + { 12, 13 }, + { 15, 16 }, + { 0, 19 }, + { 3, 19 }, + { 6, 19 }, + { 9, 19 }, + { 12, 19 }, + { 15, 19 }, + { 18, 19 }, + { 24, 25 }, + { 27, 28 }, + { 30, 31 }, + { 33, 34 }, + { 27, 37 }, + { 30, 37 }, + { 33, 37 }, + { 36, 37 }, + { 24, 37 }, + { reference.length(), reference.length() } }), + std::vector({ 6, 7, 8, 9, 10, 11, 12, 5, 1, 2, 3, 4, 0 })); + + aligner.align(query.begin(), query.end(), reference.begin(), reference.end(), edges); + + Score secondBestScore = 0; + std::vector cigars; + const Score bestScore = aligner.backtrackAllPaths(edges, cigars, secondBestScore); + + EXPECT_EQ(32, bestScore); + EXPECT_EQ("6[1=]7[3=]8[3=]5[1D1=]", toString(cigars.at(0))); +} diff --git a/thirdparty/graph-tools-master/tests/DepthTestTest.cpp b/thirdparty/graph-tools-master/tests/DepthTestTest.cpp new file mode 100755 index 0000000..932682d --- /dev/null +++ b/thirdparty/graph-tools-master/tests/DepthTestTest.cpp @@ -0,0 +1,46 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Sai Chen , +// Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphutils/DepthTest.hh" + +#include "gtest/gtest.h" + +TEST(TestingCoverageDepth, ReasonableCoverage_TestPasses) +{ + DepthTest depth_test(20, 5, 0.05, 0.001); + EXPECT_TRUE(depth_test.testReadCount(15)); + EXPECT_TRUE(depth_test.testReadCount(30)); +} + +TEST(TestingCoverageDepth, PoorCoverage_TestFails) +{ + DepthTest depth_test(20, 5, 0.05, 0.001); + EXPECT_FALSE(depth_test.testReadCount(10)); + EXPECT_FALSE(depth_test.testReadCount(40)); +} diff --git a/thirdparty/graph-tools-master/tests/GaplessAlignerTest.cpp b/thirdparty/graph-tools-master/tests/GaplessAlignerTest.cpp new file mode 100755 index 0000000..9fa4e09 --- /dev/null +++ b/thirdparty/graph-tools-master/tests/GaplessAlignerTest.cpp @@ -0,0 +1,190 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/GaplessAligner.hh" +#include "graphalign/GraphAlignmentOperations.hh" +#include "graphcore/GraphBuilders.hh" +#include "graphutils/SequenceOperations.hh" + +#include "gtest/gtest.h" + +using std::list; +using std::string; + +using namespace graphtools; + +TEST(AligningTwoSequences, SequencesWithUnequalLength_ExceptionThrown) +{ + EXPECT_ANY_THROW(alignWithoutGaps(0, "AAA", "AAAA")); +} + +TEST(AligningTwoSequences, EmptySequences_ExceptionThrown) { EXPECT_ANY_THROW(alignWithoutGaps(0, "", "")); } + +TEST(AligningSequences, TypicalSequences_Aligned) +{ + const string reference = "NNNNATCGTTTG"; + const string query = "AGGTTTTG"; + const Alignment expected_alignment(4, "1M3X4M"); + ASSERT_EQ(expected_alignment, alignWithoutGaps(4, reference, query)); +} + +TEST(AligningSequences, SequenceWithDegenerateBases_Aligned) +{ + const string reference = "VVVVV"; + const string query = "AATTC"; + const Alignment expected_alignment(0, "2M2X1M"); + ASSERT_EQ(expected_alignment, alignWithoutGaps(0, reference, query)); +} + +TEST(AligningSequenceToPath, SingleNodePath_Aligned) +{ + Graph graph = makeDeletionGraph("AAAACC", "TTTGG", "ATTT"); + Path path(&graph, 1, { 1 }, 5); + const string query = "ATGC"; + + GraphAlignment expected_graph_alignment = decodeGraphAlignment(1, "1[1X2M1X]", &graph); + GraphAlignment graph_alignment = alignWithoutGaps(path, query); + EXPECT_EQ(expected_graph_alignment, graph_alignment); +} + +TEST(AligningSequenceToPath, MultiNodePath_Aligned) +{ + Graph graph = makeDeletionGraph("AAAACC", "TTTGG", "ATTT"); + Path path(&graph, 2, { 0, 1, 2 }, 2); + const string query = "TTCCTTAGGAT"; + + GraphAlignment expected_graph_alignment = decodeGraphAlignment(2, "0[2X2M]1[2M1X2M]2[2M]", &graph); + GraphAlignment graph_alignment = alignWithoutGaps(path, query); + EXPECT_EQ(expected_graph_alignment, graph_alignment); +} + +TEST(AligningSequenceToPath, TypicalStrPath_Aligned) +{ + Graph graph = makeStrGraph("AAAACC", "CCG", "ATTT"); + Path path(&graph, 2, { 0, 1, 1, 1, 2 }, 4); + // FFFFRRRRRRRRRFFFF + const string query = "AACCCCGCCGCCGATTT"; + + GraphAlignment expected_graph_alignment = decodeGraphAlignment(2, "0[4M]1[3M]1[3M]1[3M]2[4M]", &graph); + GraphAlignment graph_alignment = alignWithoutGaps(path, query); + EXPECT_EQ(expected_graph_alignment, graph_alignment); +} + +TEST(KmerExtraction, TypicalSequence_KmersExtracted) +{ + const string sequence = "AAatTT"; + const list expected_4mers = { "AAAT", "AATT", "ATTT" }; + ASSERT_EQ(expected_4mers, extractKmersFromAllPositions(sequence, 4)); + + const list expected_7mers = {}; + ASSERT_EQ(expected_7mers, extractKmersFromAllPositions(sequence, 7)); +} + +TEST(AlignmentOfSequenceToShortPath, TypicalSequence_BestAlignmentObtained) +{ + Graph graph = makeDeletionGraph("AAACC", "TTGGG", "TTAAA"); + const Path path(&graph, 4, { 0 }, 4); + const string query = "CCTTA"; + + list alignments = getBestAlignmentToShortPath(path, 1, query); + + list expected_alignments = { decodeGraphAlignment(3, "0[2M]2[3M]", &graph) }; + ASSERT_EQ(expected_alignments, alignments); +} + +TEST(AlignmentOfSequenceToGraph, TypicalSequence_BestAlignmentObtained) +{ + Graph graph = makeDeletionGraph("AAAACC", "TTTGG", "ATTT"); + + const int32_t kmer_len = 3; + GaplessAligner aligner(&graph, kmer_len); + + const string query = "TTCCTTAGGAT"; + list alignments = aligner.align(query); + + list expected_alignments = { decodeGraphAlignment(2, "0[2X2M]1[2M1X2M]2[2M]", &graph) }; + ASSERT_EQ(expected_alignments, alignments); +} + +TEST(GraphAlignment, TypicalStrGraph_BestAlignmentObtained) +{ + Graph graph = makeStrGraph("AAAACG", "CCG", "ATTT"); + const int32_t kmer_len = 3; + GaplessAligner aligner(&graph, kmer_len); + + { + // FFFFRRRRRRRRRFFFF + const string spanning_read = "AACGCCGCCGCCGATTT"; + list alignments = aligner.align(spanning_read); + + list expected_alignments = { decodeGraphAlignment(2, "0[4M]1[3M]1[3M]1[3M]2[4M]", &graph) }; + EXPECT_EQ(expected_alignments, alignments); + } + + { + // RRRRRRRRRRR + const string repeat_read = "CGCCGCCGCCG"; + list alignments = aligner.align(repeat_read); + list expected_alignments = { decodeGraphAlignment(4, "0[2M]1[3M]1[3M]1[3M]", &graph), + decodeGraphAlignment(1, "1[2M]1[3M]1[3M]1[3M]", &graph) }; + EXPECT_EQ(expected_alignments, alignments); + } + + { + // RRRXRRRRXRRR + const string repeat_read = "CCGACGCCTCCG"; + list alignments = aligner.align(repeat_read); + list expected_alignments = { decodeGraphAlignment(0, "1[3M]1[1X2M]1[2M1X]1[3M]", &graph) }; + EXPECT_EQ(expected_alignments, alignments); + } +} + +TEST(GraphAlignment, PolyalanineGraph_BestAlignmentObtained) +{ + Graph graph = makeStrGraph("AACG", "GCN", "ATTT"); + const int32_t kmer_len = 3; + GaplessAligner aligner(&graph, kmer_len); + + { + // FFFFGCNGCNGCNFFFF + const string spanning_read = "AACGGCAGCTGCGATTT"; + list alignments = aligner.align(spanning_read); + + list expected_alignments = { decodeGraphAlignment(0, "0[4M]1[3M]1[3M]1[3M]2[4M]", &graph) }; + EXPECT_EQ(expected_alignments, alignments); + } + + { + // CNGCNGCNGCN + const string repeat_read = "CGGCAGCTGCG"; + list alignments = aligner.align(repeat_read); + list expected_alignments = { decodeGraphAlignment(2, "0[2M]1[3M]1[3M]1[3M]", &graph), + decodeGraphAlignment(1, "1[2M]1[3M]1[3M]1[3M]", &graph) }; + EXPECT_EQ(expected_alignments, alignments); + } +} diff --git a/thirdparty/graph-tools-master/tests/GappedAlignerTest.cpp b/thirdparty/graph-tools-master/tests/GappedAlignerTest.cpp new file mode 100755 index 0000000..9b82b4f --- /dev/null +++ b/thirdparty/graph-tools-master/tests/GappedAlignerTest.cpp @@ -0,0 +1,259 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/GappedAligner.hh" + +#include "gtest/gtest.h" + +#include "graphalign/GraphAlignmentOperations.hh" +#include "graphalign/LinearAlignment.hh" +#include "graphcore/Graph.hh" +#include "graphcore/GraphBuilders.hh" +#include "graphcore/Path.hh" + +using std::list; +using std::make_pair; +using std::string; + +using namespace graphtools; + +TEST(DISABLED_ExtendingAlignmentSuffix, UniquelyMappingQuery_AlignmentExtended) +{ + Graph graph = makeStrGraph("ATA", "CG", "TATTTTTTTTT"); + + const size_t kmer_len = 3; + const size_t padding_len = 5; + const int32_t seed_affix_trim_len = 0; + GappedGraphAligner aligner(&graph, kmer_len, padding_len, seed_affix_trim_len, "path-aligner"); + + // -> CGCGCGTA + // | |||||| + // -> C-CGCGTA + // 11111122 + + Path seed_path(&graph, 3, { 0 }, 3); + const size_t extension_len = 12; + list extensions = aligner.extendAlignmentSuffix(seed_path, "CCGCGTA", extension_len); + + Alignment expected_alignment(0, "1M1D6M"); + Path expected_path(&graph, 3, { 0, 1, 1, 1, 2 }, 2); + list expected_extensions = { make_pair(expected_path, expected_alignment) }; + + EXPECT_EQ(expected_extensions, extensions); +} + +TEST(DISABLED_ExtendingAlignmentSuffix, MultiMappingQuery_AlignmentExtended) +{ + Graph graph = makeStrGraph("AAA", "C", "CCA"); + + const size_t kmer_len = 3; + const size_t padding_len = 0; + const int32_t seed_affix_trim_len = 0; + GappedGraphAligner aligner(&graph, kmer_len, padding_len, seed_affix_trim_len, "path-aligner"); + + Path seed_path(&graph, 3, { 0 }, 3); + list extensions = aligner.extendAlignmentSuffix(seed_path, "CCC", 3); + + Alignment expected_alignment_a = Alignment(0, "3M"); + Path expected_path_a(&graph, 3, { 0, 1, 1, 1 }, 1); + + Alignment expected_alignment_b = Alignment(0, "3M"); + Path expected_path_b(&graph, 3, { 0, 1, 1, 2 }, 1); + + Alignment expected_alignment_c = Alignment(0, "3M"); + Path expected_path_c(&graph, 3, { 0, 1, 2 }, 2); + + list expected_extensions + = { make_pair(expected_path_a, expected_alignment_a), make_pair(expected_path_b, expected_alignment_b), + make_pair(expected_path_c, expected_alignment_c) }; + + EXPECT_EQ(expected_extensions, extensions); +} + +class AlignerTests : public ::testing::TestWithParam +{ +}; + +// TODO: Throw an error if there are no valid extensions? +TEST_P(AlignerTests, ExtendingAlignmentPrefix_TypicalSequences_AlignmentExtended) +{ + Graph graph = makeStrGraph("ATATTA", "CG", "TATTT"); + + const size_t kmer_len = 3; + const size_t padding_len = 5; + const size_t seed_affix_trim_len = 0; + GappedGraphAligner aligner(&graph, kmer_len, padding_len, seed_affix_trim_len, GetParam()); + + // ATTAC-GCGC <- + // || || ||| + // ATAACAGCGG <- + // 00001 1111 + + Path seed_path(&graph, 1, { 1 }, 1); + const size_t extension_len = 10; + list extensions = aligner.extendAlignmentPrefix(seed_path, "ATAACAGCGG", extension_len); + + Alignment expected_alignment(0, "2M1X2M1I3M1X"); + Path expected_path(&graph, 2, { 0, 1, 1, 1 }, 1); + list expected_extensions = { make_pair(expected_path, expected_alignment) }; + + EXPECT_EQ(expected_extensions, extensions); +} + +TEST_P(AlignerTests, PerformingGappedAlignment_UniquelyMappingQuery_AlignmentPerformed) +{ + Graph graph = makeStrGraph("ATATTA", "CG", "TATTT"); + + const size_t kmer_len = 3; + const size_t padding_len = 2; + const int32_t seed_affix_trim_len = 0; + GappedGraphAligner aligner( + &graph, kmer_len, padding_len, seed_affix_trim_len, GetParam(), LinearAlignmentParameters(5, -4, -8, 0)); + + // TTA-CG-CG-TAT + // || || | ||| + // TT--CG-C--TAT + + list alignments = aligner.align("TTCGCTAT"); + + list expected_alignments = { decodeGraphAlignment(3, "0[2M1D]1[2M]1[1M1D]2[3M]", &graph) }; + // with default m=5,mm=-4,go=-8,ge=-2 2M1D=10-8-2=0, 1M1X=5-4=1, so, test needs an update: + // list expected_alignments = { decodeGraphAlignment(4, "0[1M1X]1[2M]1[1M1D]2[3M]", &graph) }; + EXPECT_EQ(expected_alignments, alignments); +} + +TEST_P(AlignerTests, PerformingGappedAlignment_MultimappingQuery_BestAlignmentsComputed) +{ + Graph graph = makeStrGraph("AAG", "CGG", "CTT"); + GappedGraphAligner aligner(&graph, 3, 0, 0, GetParam()); + + // G-CG-C + // 0-11-1 + // 1-11-1 + + list alignments = aligner.align("GCGGC"); + + list expected_alignments + = { decodeGraphAlignment(2, "0[1M]1[3M]1[1M]", &graph), decodeGraphAlignment(2, "0[1M]1[3M]2[1M]", &graph), + decodeGraphAlignment(2, "1[1M]1[3M]1[1M]", &graph), decodeGraphAlignment(2, "1[1M]1[3M]2[1M]", &graph) }; + EXPECT_EQ(expected_alignments, alignments); +} + +TEST_P(AlignerTests, PerformingGappedAlignment_KmerExtensionInBothDirectionsNotNeeded_BestAlignmentsComputed) +{ + Graph graph = makeStrGraph("AAG", "CGG", "CTT"); + GappedGraphAligner aligner(&graph, 3, 0, 0, GetParam()); + + { + list alignments = aligner.align("CGGCT"); + list expected_alignments = { decodeGraphAlignment(0, "1[3M]2[2M]", &graph) }; + EXPECT_EQ(expected_alignments, alignments); + } + + { + list alignments = aligner.align("AATCGG"); + list expected_alignments = { decodeGraphAlignment(0, "0[2M1X]1[3M]", &graph) }; + EXPECT_EQ(expected_alignments, alignments); + } + + { + list alignments = aligner.align("CTT"); + list expected_alignments = { decodeGraphAlignment(0, "2[3M]", &graph) }; + EXPECT_EQ(expected_alignments, alignments); + } +} + +TEST_P(AlignerTests, PerformingGappedAlignment_KmerExtensionIsUnalignable_BestAlignmentsComputed) +{ + Graph graph = makeStrGraph("AAG", "CGG", "CTT"); + GappedGraphAligner aligner(&graph, 3, 0, 0, GetParam()); + + { + list alignments = aligner.align("CGGAA"); + list expected_alignments = { decodeGraphAlignment(0, "1[3M2S]", &graph) }; + EXPECT_EQ(expected_alignments, alignments); + } + + { + list alignments = aligner.align("TTCGG"); + list expected_alignments = { decodeGraphAlignment(0, "1[2S3M]", &graph) }; + EXPECT_EQ(expected_alignments, alignments); + } + + { + list alignments = aligner.align("TCGGA"); + list expected_alignments = { decodeGraphAlignment(0, "1[1S3M1S]", &graph) }; + EXPECT_EQ(expected_alignments, alignments); + } +} + +TEST_P(AlignerTests, PerformingGappedAlignment_PolyalanineRepeat_ReadAligned) +{ + Graph graph = makeStrGraph("AAG", "GCN", "ATT"); + GappedGraphAligner aligner(&graph, 4, 0, 0, GetParam()); + + list alignments = aligner.align("AGGCCGTGGCAATT"); + list expected_alignments = { decodeGraphAlignment(1, "0[2M]1[3M]1[1M1X1M]1[3M]2[3M]", &graph) }; + + EXPECT_EQ(expected_alignments, alignments); +} + +TEST_P(AlignerTests, PerformingGappedAlignment_ReadWithLowqualityBases_ReadAligned) +{ + Graph graph = makeStrGraph("AAG", "CGG", "CTT"); + GappedGraphAligner aligner(&graph, 4, 0, 0, GetParam()); + + list alignments = aligner.align("aagcggctt"); + list expected_alignments = { decodeGraphAlignment(0, "0[3M]1[3M]2[3M]", &graph) }; + + EXPECT_EQ(expected_alignments, alignments); +} + +TEST_P(AlignerTests, PerformingGappedAlignment_IncorrectSeedKmer_ReadAligned) +{ + Graph graph = makeStrGraph("AAAA", "CCG", "TTTT"); + const int32_t seed_affix_trim_len = 2; + GappedGraphAligner aligner(&graph, 4, 0, seed_affix_trim_len, GetParam()); + + { + list alignments = aligner.align("CCACCGTTTT"); + list expected_alignments = { decodeGraphAlignment(0, "1[2M1X]1[3M]2[4M]", &graph) }; + + EXPECT_EQ(expected_alignments, alignments); + } + + { + list alignments = aligner.align("CCGTCG"); + list expected_alignments = { decodeGraphAlignment(0, "1[3M]1[1X2M]", &graph) }; + + EXPECT_EQ(expected_alignments, alignments); + } +} + +INSTANTIATE_TEST_CASE_P( + AlignerTestsInst, AlignerTests, ::testing::Values(std::string("path-aligner"), std::string("dag-aligner")), ); diff --git a/thirdparty/graph-tools-master/tests/GraphAlignmentOperationsTest.cpp b/thirdparty/graph-tools-master/tests/GraphAlignmentOperationsTest.cpp new file mode 100755 index 0000000..8f23161 --- /dev/null +++ b/thirdparty/graph-tools-master/tests/GraphAlignmentOperationsTest.cpp @@ -0,0 +1,158 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/GraphAlignmentOperations.hh" + +#include + +#include "gtest/gtest.h" + +#include "graphcore/Graph.hh" +#include "graphcore/GraphBuilders.hh" +#include "graphcore/Path.hh" + +using std::list; +using std::string; + +using namespace graphtools; + +TEST(SplitingNodeCigarEncoding, TypicalCigarEncoding_CigarAndNodeIdExtracted) +{ + string cigar; + NodeId node_id; + splitNodeCigar("1[4M5S]", cigar, node_id); + EXPECT_EQ(1ul, node_id); + EXPECT_EQ("4M5S", cigar); +} + +TEST(DecodingGraphAlignment, SinglenodeGraphAlignment_Decoded) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + GraphAlignment alignment = decodeGraphAlignment(1, "1[2M]", &graph); + + GraphAlignment expected_alignment(Path(&graph, 1, { 1 }, 3), { Alignment(1, "2M") }); + EXPECT_EQ(expected_alignment, alignment); +} + +TEST(DecodingGraphAlignment, MultinodeGraphAlignment_Decoded) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + GraphAlignment alignment = decodeGraphAlignment(0, "0[4M]1[2M3S]", &graph); + + GraphAlignment expected_alignment(Path(&graph, 0, { 0, 1 }, 2), { Alignment(0, "4M"), Alignment(0, "2M3S") }); + EXPECT_EQ(expected_alignment, alignment); +} + +TEST(CheckingConsistencyOfAlignments, ConsistentAlignment_CheckPassed) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + GraphAlignment alignment = decodeGraphAlignment(0, "0[4M]1[2M3S]", &graph); + + const string query = "AAAATTCCC"; + ASSERT_TRUE(checkConsistency(alignment, query)); +} + +TEST(CheckingConsistencyOfAlignments, QueryIsShorterThanAlignment_CheckFailed) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + GraphAlignment alignment = decodeGraphAlignment(0, "0[4M]1[2M3S]", &graph); + + const string query = "AAAATT"; + ASSERT_FALSE(checkConsistency(alignment, query)); +} + +TEST(CheckingConsistencyOfAlignments, GraphAlignmentWithInconsistentLinearAlignment_CheckFailed) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + GraphAlignment alignment = decodeGraphAlignment(0, "0[4M]1[2M3S]", &graph); + + const string query = "AAAAGGCCC"; + ASSERT_FALSE(checkConsistency(alignment, query)); +} + +TEST(CheckingConsistencyOfAlignments, UnderlyingPathCanBeShortened_CheckFailed) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + + GraphAlignment alignment(Path(&graph, 4, { 0, 1 }, 2), { Alignment(4, "3S"), Alignment(0, "2M") }); + ASSERT_FALSE(checkConsistency(alignment, "GGGTT")); +} + +TEST(GettingQuerySequencesForEachNode, TypicalAlignment_SequencePairs) +{ + // AAA:CGG:CGG:TG + // |: :|||:|| + // TTTA:C--:CGG:TGGTTT + + Graph graph = makeStrGraph("AAA", "CGG", "TG"); + GraphAlignment alignment = decodeGraphAlignment(2, "0[3S1M]1[1M2D]1[3M]2[2M4S]", &graph); + + const string query = "TTTACCGGTGGTTT"; + const list query_pieces = getQuerySequencesForEachNode(alignment, query); + + const list expected_pieces = { "TTTA", "C", "CGG", "TGGTTT" }; + ASSERT_EQ(expected_pieces, query_pieces); +} + +TEST(PrettyPrintingAlignments, TypicalAlignment_PrettyPrinted) +{ + // AAA:CGG:CGG:TG + // |: :|||:|| + // TTTA:C--:CGG:TGGTTT + + Graph graph = makeStrGraph("AAA", "CGG", "TG"); + GraphAlignment alignment = decodeGraphAlignment(2, "0[3S1M]1[1M2D]1[3M]2[2M4S]", &graph); + + const string query = "TTTACCGGTGGTTT"; + const string encoding = prettyPrint(alignment, query); + + const string expected_encoding = "---A:CGG:CGG:TG----\n" + " |:| :|||:|| \n" + "TTTA:C--:CGG:TGGTTT"; + ASSERT_EQ(expected_encoding, encoding); +} + +TEST(ProjectingLinearAlignmentOntoPath, TypicalLinearAlignment_GraphAlignment) +{ + // CATAC + // || | + // GGAT-CGAA + // 00122 + const string query = "GGATCGAA"; + const string reference = "CAWAC"; + Alignment linear_alignment(1, "2S2M1D1M3S"); + + Graph graph = makeStrGraph("AACA", "W", "ACTTT"); + Path path(&graph, 2, { 0, 1, 2 }, 1); + + GraphAlignment graph_alignment = projectAlignmentOntoGraph(linear_alignment, path); + + GraphAlignment expected_graph_alignment = decodeGraphAlignment(3, "0[2S1M]1[1M]2[1D1M3S]", &graph); + + ASSERT_EQ(expected_graph_alignment, graph_alignment); +} diff --git a/thirdparty/graph-tools-master/tests/GraphAlignmentTest.cpp b/thirdparty/graph-tools-master/tests/GraphAlignmentTest.cpp new file mode 100755 index 0000000..a5771c9 --- /dev/null +++ b/thirdparty/graph-tools-master/tests/GraphAlignmentTest.cpp @@ -0,0 +1,259 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/GraphAlignment.hh" + +#include "gtest/gtest.h" + +#include "graphalign/GraphAlignmentOperations.hh" +#include "graphcore/Graph.hh" +#include "graphcore/GraphBuilders.hh" +#include "graphcore/Path.hh" + +using std::list; +using std::string; +using std::vector; + +using namespace graphtools; + +TEST(InitializingGraphAlignment, CompatiblePath_GraphAlignmentCreated) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + + { + Path path(&graph, 3, { 0, 1, 2 }, 3); + vector alignments = { Alignment(3, "1M"), Alignment(0, "4M"), Alignment(0, "3M") }; + EXPECT_NO_THROW(GraphAlignment(path, alignments)); + } + + { + Path path(&graph, 2, { 1 }, 3); + vector alignments = { Alignment(2, "1M") }; + EXPECT_NO_THROW(GraphAlignment(path, alignments)); + } +} + +TEST(InitializingGraphAlignment, IncompatiblePath_ExceptionThrown) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + Path path(&graph, 2, { 0, 1, 2 }, 3); + + { + vector alignments = { Alignment(3, "1M"), Alignment(0, "4M"), Alignment(0, "3M") }; + EXPECT_ANY_THROW(GraphAlignment(path, alignments)); + } + + { + vector alignments = { Alignment(2, "2M"), Alignment(0, "4M"), Alignment(0, "4M") }; + EXPECT_ANY_THROW(GraphAlignment(path, alignments)); + } + + { + vector alignments = { Alignment(2, "2M"), Alignment(0, "3M"), Alignment(0, "3M") }; + EXPECT_ANY_THROW(GraphAlignment(path, alignments)); + } + + { + vector alignments = { Alignment(2, "2M"), Alignment(1, "4M"), Alignment(0, "3M") }; + EXPECT_ANY_THROW(GraphAlignment(path, alignments)); + } +} + +TEST(GettingNumMatchesInGraphAlignment, TypicalGraphAlignment_GotNumMatches) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + const string query = "AAAATTCCC"; + GraphAlignment graph_alignment = decodeGraphAlignment(0, "0[4M]1[2M3S]", &graph); + EXPECT_EQ(6u, graph_alignment.numMatches()); +} + +TEST(GettingGraphAlignmentSpans, TypicalGraphAlignment_GotQueryAndReferenceSpans) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + const string query = "AAAATTCCC"; + GraphAlignment graph_alignment = decodeGraphAlignment(0, "0[4M]1[2M3S]", &graph); + EXPECT_EQ(9u, graph_alignment.queryLength()); + EXPECT_EQ(6u, graph_alignment.referenceLength()); +} + +TEST(AccessingNodeAlignmentsByIndex, TypicalGraphAlignment_NodeAlignmentsAccessed) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGC", "TTTT"); + const string query = "AAAATTCCC"; + GraphAlignment graph_alignment = decodeGraphAlignment(0, "0[4M]1[2M3S]", &graph); + EXPECT_EQ(Alignment(0, "4M"), graph_alignment[0]); + EXPECT_EQ(Alignment(0, "2M3S"), graph_alignment[1]); +} + +TEST(GettingIndexesOfNode, TypicalAlignment_IndexesObtained) +{ + Graph graph = makeStrGraph("AAAACC", "CCG", "ATTT"); + const string read = "CCCCGCCGAT"; + GraphAlignment alignment = decodeGraphAlignment(4, "0[2M]1[3M]1[3M]2[2M]", &graph); + const list left_flank_indexes = { 0 }; + const list repeat_unit_indexes = { 1, 2 }; + const list right_flank_indexes = { 3 }; + EXPECT_EQ(left_flank_indexes, alignment.getIndexesOfNode(0)); + EXPECT_EQ(repeat_unit_indexes, alignment.getIndexesOfNode(1)); + EXPECT_EQ(right_flank_indexes, alignment.getIndexesOfNode(2)); +} + +TEST(GettingIndexesOfNode, NodeNotInAlignment_EmptyListReturned) +{ + Graph graph = makeStrGraph("AAAACC", "CCG", "ATTT"); + const string read = "ACCCCG"; + GraphAlignment alignment = decodeGraphAlignment(3, "0[3M]1[3M]", &graph); + const list empty_list; + EXPECT_EQ(empty_list, alignment.getIndexesOfNode(2)); + EXPECT_EQ(empty_list, alignment.getIndexesOfNode(4)); +} + +TEST(CheckingIfAlignmentOverlapsNode, TypicalAlignment_ChecksPerformed) +{ + Graph graph = makeStrGraph("AAAACC", "CCG", "ATTT"); + const string read = "ACCCCG"; + GraphAlignment alignment = decodeGraphAlignment(3, "0[3M]1[3M]", &graph); + EXPECT_TRUE(alignment.overlapsNode(0)); + EXPECT_TRUE(alignment.overlapsNode(1)); + EXPECT_FALSE(alignment.overlapsNode(2)); + EXPECT_FALSE(alignment.overlapsNode(3)); +} + +TEST(EncodingGraphAlignment, TypicalGraphAlignment_CigarStringObtained) +{ + Graph graph = makeStrGraph("AAAACC", "CCG", "ATTT"); + const string read = "CCCCGCCGAT"; + const string cigar_string = "0[2M]1[3M]1[3M]2[2M]"; + GraphAlignment alignment = decodeGraphAlignment(4, cigar_string, &graph); + + ASSERT_EQ(cigar_string, alignment.generateCigar()); +} + +TEST(ComparingGraphAlignments, TypicalGraphAlignments_Compared) +{ + Graph graph = makeStrGraph("ATT", "CCG", "CTTT"); + + GraphAlignment alignment_a = decodeGraphAlignment(1, "0[2M]1[3M]1[1M]", &graph); + GraphAlignment alignment_b = decodeGraphAlignment(1, "0[2M]1[3M]2[1M]", &graph); + + EXPECT_TRUE(alignment_a < alignment_b); + EXPECT_FALSE(alignment_b < alignment_a); + EXPECT_FALSE(alignment_a == alignment_b); +} + +TEST(ShrinkingGraphAlignmentStarts, TypicalGraphAlignment_Shrank) +{ + Graph graph = makeStrGraph("ATT", "CCG", "CTTT"); + + { + GraphAlignment alignment = decodeGraphAlignment(1, "0[2M]1[3M]1[1M]", &graph); + alignment.shrinkStart(1); + GraphAlignment expectedAlignment = decodeGraphAlignment(2, "0[1S1M]1[3M]1[1M]", &graph); + + EXPECT_EQ(alignment, expectedAlignment); + } + + { + GraphAlignment alignment = decodeGraphAlignment(1, "0[2M]1[3M]1[1M]", &graph); + alignment.shrinkStart(2); + GraphAlignment expectedAlignment = decodeGraphAlignment(0, "1[2S3M]1[1M]", &graph); + + EXPECT_EQ(alignment, expectedAlignment); + } + + { + GraphAlignment alignment = decodeGraphAlignment(1, "0[2M]1[3M]1[1M]", &graph); + alignment.shrinkStart(5); + GraphAlignment expectedAlignment = decodeGraphAlignment(0, "1[5S1M]", &graph); + + EXPECT_EQ(alignment, expectedAlignment); + } + + { + GraphAlignment alignment = decodeGraphAlignment(1, "0[1S2M]1[3M]1[1M]", &graph); + alignment.shrinkStart(3); + GraphAlignment expectedAlignment = decodeGraphAlignment(1, "1[4S2M]1[1M]", &graph); + + EXPECT_EQ(alignment, expectedAlignment); + } +} + +TEST(ShrinkingGraphAlignmentStarts, ShrinkingByAlignmentLengthOrMore_ExceptionThrown) +{ + Graph graph = makeStrGraph("ATT", "CCG", "CTTT"); + + GraphAlignment alignment = decodeGraphAlignment(1, "0[2M]1[3M]1[1M]", &graph); + EXPECT_ANY_THROW(alignment.shrinkStart(alignment.referenceLength())); + EXPECT_ANY_THROW(alignment.shrinkStart(alignment.referenceLength() + 1)); +} + +TEST(ShrinkingGraphAlignmentEnds, TypicalGraphAlignment_Shrank) +{ + Graph graph = makeStrGraph("ATT", "CCG", "CTTT"); + + { + GraphAlignment alignment = decodeGraphAlignment(1, "0[2M]1[3M]1[1M]", &graph); + alignment.shrinkEnd(1); + GraphAlignment expectedAlignment = decodeGraphAlignment(1, "0[2M]1[3M1S]", &graph); + + EXPECT_EQ(alignment, expectedAlignment); + } + + { + GraphAlignment alignment = decodeGraphAlignment(1, "0[2M]1[3M]1[1M]", &graph); + alignment.shrinkEnd(2); + GraphAlignment expectedAlignment = decodeGraphAlignment(1, "0[2M]1[2M2S]", &graph); + + EXPECT_EQ(alignment, expectedAlignment); + } + + { + GraphAlignment alignment = decodeGraphAlignment(1, "0[2M]1[3M]1[1M]", &graph); + alignment.shrinkEnd(5); + GraphAlignment expectedAlignment = decodeGraphAlignment(1, "0[1M5S]", &graph); + + EXPECT_EQ(alignment, expectedAlignment); + } + + { + GraphAlignment alignment = decodeGraphAlignment(1, "0[1S2M]1[3M]1[1M3S]", &graph); + alignment.shrinkEnd(4); + GraphAlignment expectedAlignment = decodeGraphAlignment(1, "0[1S2M7S]", &graph); + + EXPECT_EQ(alignment, expectedAlignment); + } +} + +TEST(ShrinkingGraphAlignmentEnds, ShrinkingByAlignmentLengthOrMore_ExceptionThrown) +{ + Graph graph = makeStrGraph("ATT", "CCG", "CTTT"); + + GraphAlignment alignment = decodeGraphAlignment(1, "0[2M]1[3M]1[1M]", &graph); + EXPECT_ANY_THROW(alignment.shrinkEnd(alignment.referenceLength())); + EXPECT_ANY_THROW(alignment.shrinkEnd(alignment.referenceLength() + 1)); +} diff --git a/thirdparty/graph-tools-master/tests/GraphBuildersTest.cpp b/thirdparty/graph-tools-master/tests/GraphBuildersTest.cpp new file mode 100755 index 0000000..7312898 --- /dev/null +++ b/thirdparty/graph-tools-master/tests/GraphBuildersTest.cpp @@ -0,0 +1,148 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphcore/GraphBuilders.hh" + +#include "gtest/gtest.h" + +using std::string; + +using namespace graphtools; + +TEST(CreatingGraphs, TypicalSequences_DeletionGraphCreated) +{ + const string left_flank = "AATT"; + const string deletion = "CCCC"; + const string right_flank = "GGGCC"; + Graph graph = makeDeletionGraph(left_flank, deletion, right_flank); + + EXPECT_EQ(3ul, graph.numNodes()); + EXPECT_EQ(left_flank, graph.nodeSeq(0)); + EXPECT_EQ(deletion, graph.nodeSeq(1)); + EXPECT_EQ(right_flank, graph.nodeSeq(2)); + EXPECT_TRUE(graph.hasEdge(0, 1)); + EXPECT_TRUE(graph.hasEdge(0, 2)); + EXPECT_TRUE(graph.hasEdge(1, 2)); +} + +TEST(CreatingGraphs, TypicalSequences_SwapGraphCreated) +{ + const string left_flank = "AATT"; + const string deletion = "CCCC"; + const string insertion = "TTTT"; + const string right_flank = "GGGCC"; + Graph graph = makeSwapGraph(left_flank, deletion, insertion, right_flank); + + EXPECT_EQ(4ul, graph.numNodes()); + EXPECT_EQ(left_flank, graph.nodeSeq(0)); + EXPECT_EQ(deletion, graph.nodeSeq(1)); + EXPECT_EQ(insertion, graph.nodeSeq(2)); + EXPECT_EQ(right_flank, graph.nodeSeq(3)); + EXPECT_TRUE(graph.hasEdge(0, 1)); + EXPECT_TRUE(graph.hasEdge(0, 2)); + EXPECT_TRUE(graph.hasEdge(1, 3)); + EXPECT_TRUE(graph.hasEdge(2, 3)); +} + +TEST(CreatingGraphs, TypicalSequences_DoubleSwapGraphCreated) +{ + const string left_flank = "AATT"; + const string deletion1 = "CCCC"; + const string insertion1 = "TTTT"; + const string middle = "CCCC"; + const string deletion2 = "AAAA"; + const string insertion2 = "GGGG"; + const string right_flank = "GGGCC"; + Graph graph = makeDoubleSwapGraph(left_flank, deletion1, insertion1, middle, deletion2, insertion2, right_flank); + + EXPECT_EQ(7ul, graph.numNodes()); + EXPECT_EQ(left_flank, graph.nodeSeq(0)); + EXPECT_EQ(deletion1, graph.nodeSeq(1)); + EXPECT_EQ(insertion1, graph.nodeSeq(2)); + EXPECT_EQ(middle, graph.nodeSeq(3)); + EXPECT_EQ(deletion2, graph.nodeSeq(4)); + EXPECT_EQ(insertion2, graph.nodeSeq(5)); + EXPECT_EQ(right_flank, graph.nodeSeq(6)); + EXPECT_TRUE(graph.hasEdge(0, 1)); + EXPECT_TRUE(graph.hasEdge(0, 2)); + EXPECT_TRUE(graph.hasEdge(1, 3)); + EXPECT_TRUE(graph.hasEdge(2, 3)); + EXPECT_TRUE(graph.hasEdge(3, 4)); + EXPECT_TRUE(graph.hasEdge(3, 5)); + EXPECT_TRUE(graph.hasEdge(4, 6)); + EXPECT_TRUE(graph.hasEdge(5, 6)); +} + +TEST(CreatingGraphs, TypicalSequences_LooplessStrGraphCreated) +{ + const string left_flank = "AATT"; + const string repeat_unit = "CGG"; + const string right_flank = "ATTT"; + const int32_t read_len = 10; + Graph graph = makeLooplessStrGraph(read_len, left_flank, repeat_unit, right_flank); + + ASSERT_EQ(6ul, graph.numNodes()); + EXPECT_EQ(left_flank, graph.nodeSeq(0)); + EXPECT_EQ(repeat_unit, graph.nodeSeq(1)); + EXPECT_EQ(repeat_unit, graph.nodeSeq(2)); + EXPECT_EQ(repeat_unit, graph.nodeSeq(3)); + EXPECT_EQ(repeat_unit, graph.nodeSeq(4)); + EXPECT_EQ(right_flank, graph.nodeSeq(5)); + + EXPECT_TRUE(graph.hasEdge(0, 5)); + + EXPECT_TRUE(graph.hasEdge(0, 1)); + EXPECT_TRUE(graph.hasEdge(1, 5)); + + EXPECT_TRUE(graph.hasEdge(1, 2)); + EXPECT_TRUE(graph.hasEdge(2, 5)); + + EXPECT_TRUE(graph.hasEdge(2, 3)); + EXPECT_TRUE(graph.hasEdge(3, 5)); + + EXPECT_TRUE(graph.hasEdge(3, 4)); + EXPECT_TRUE(graph.hasEdge(4, 5)); +} + +TEST(CreatingGraphs, TypicalSequences_StrGraphCreated) +{ + const string left_flank = "AATT"; + const string repeat_unit = "CGG"; + const string right_flank = "ATTT"; + Graph graph = makeStrGraph(left_flank, repeat_unit, right_flank); + + ASSERT_EQ(3ul, graph.numNodes()); + EXPECT_EQ(left_flank, graph.nodeSeq(0)); + EXPECT_EQ(repeat_unit, graph.nodeSeq(1)); + EXPECT_EQ(right_flank, graph.nodeSeq(2)); + + EXPECT_TRUE(graph.hasEdge(0, 1)); + EXPECT_TRUE(graph.hasEdge(0, 2)); + EXPECT_TRUE(graph.hasEdge(1, 1)); + EXPECT_TRUE(graph.hasEdge(1, 2)); +} diff --git a/thirdparty/graph-tools-master/tests/GraphCoordinatesTest.cpp b/thirdparty/graph-tools-master/tests/GraphCoordinatesTest.cpp new file mode 100755 index 0000000..4e703d2 --- /dev/null +++ b/thirdparty/graph-tools-master/tests/GraphCoordinatesTest.cpp @@ -0,0 +1,135 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphcore/GraphCoordinates.hh" + +#include "gtest/gtest.h" + +using namespace testing; +using namespace graphtools; + +class GraphCoordinatesTest : public Test +{ +public: + Graph graph{ 4 }; + + void SetUp() override + { + graph.setNodeName(0, "LF"); + graph.setNodeSeq(0, "AAAAAAAAAAA"); + + graph.setNodeName(1, "P1"); + graph.setNodeSeq(1, "TTTTTT"); + + graph.setNodeName(2, "Q1"); + graph.setNodeSeq(2, "GGGGGGGG"); + + graph.setNodeName(3, "RF"); + graph.setNodeSeq(3, "AAAAAAAAAAA"); + + /** + * + * LF RF + * | ^ + * | | + * *-> P1 -----* + * | | + * *-> Q1 -----* + */ + + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(1, 3); + graph.addEdge(2, 3); + } +}; + +TEST_F(GraphCoordinatesTest, CanonicalPositionLookup) +{ + GraphCoordinates coordinates(&graph); + + // LF has offset 0 + ASSERT_EQ(static_cast(6), coordinates.canonicalPos("LF", 6)); + ASSERT_EQ(static_cast(11 + 4), coordinates.canonicalPos("P1", 4)); + ASSERT_EQ(static_cast(11 + 6 + 3), coordinates.canonicalPos("Q1", 3)); + ASSERT_EQ(static_cast(11 + 6 + 8 + 2), coordinates.canonicalPos("RF", 2)); +} + +TEST_F(GraphCoordinatesTest, ReverseLookup) +{ + GraphCoordinates coordinates(&graph); + for (size_t j = 0; j < graph.nodeSeq(0).size(); ++j) // node 0 == LF + { + std::string n; + uint64_t offset = static_cast(-1); + coordinates.nodeAndOffset(j, n, offset); + ASSERT_EQ("LF", n); + ASSERT_EQ(static_cast(j), offset); + } + + for (size_t j = 0; j < graph.nodeSeq(1).size(); ++j) // node 1 == P1 + { + std::string n; + uint64_t offset = static_cast(-1); + coordinates.nodeAndOffset(11 + j, n, offset); + ASSERT_EQ("P1", n); + ASSERT_EQ(static_cast(j), offset); + } + for (size_t j = 0; j < graph.nodeSeq(2).size(); ++j) // node 2 == Q1 + { + std::string n; + uint64_t offset = static_cast(-1); + coordinates.nodeAndOffset(11 + 6 + j, n, offset); + ASSERT_EQ("Q1", n); + ASSERT_EQ(static_cast(j), offset); + } + for (size_t j = 0; j < graph.nodeSeq(3).size(); ++j) // node 3 == RF + { + std::string n; + uint64_t offset = static_cast(-1); + coordinates.nodeAndOffset(11 + 6 + 8 + j, n, offset); + ASSERT_EQ("RF", n); + ASSERT_EQ(static_cast(j), offset); + } +} + +TEST_F(GraphCoordinatesTest, DistanceComputation) +{ + GraphCoordinates coordinates(&graph); + + // both on LF + ASSERT_EQ(static_cast(5), coordinates.distance(10, 5)); + ASSERT_EQ(static_cast(5), coordinates.distance(5, 10)); + + // one on LF, one on neighbour (P1 or Q1) + ASSERT_EQ(static_cast(8), coordinates.distance(14, 6)); + ASSERT_EQ(static_cast(8), coordinates.distance(20, 6)); + + // LF -> RF should go via P1 because this is shorter + ASSERT_EQ(static_cast(9 + 6 + 4), coordinates.distance(2, 11 + 6 + 8 + 4)); +} diff --git a/thirdparty/graph-tools-master/tests/GraphIOTest.cpp b/thirdparty/graph-tools-master/tests/GraphIOTest.cpp new file mode 100755 index 0000000..ab3035c --- /dev/null +++ b/thirdparty/graph-tools-master/tests/GraphIOTest.cpp @@ -0,0 +1,327 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Felix Schlesinger +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include + +#include "nlohmann/json.hpp" +#include "gtest/gtest.h" +#include +#include + +#include "graphIO/BamWriter.hh" +#include "graphIO/GraphJson.hh" +#include "graphIO/ReferenceGenome.hh" +#include "graphcore/Graph.hh" +#include "graphcore/GraphBuilders.hh" + +#include +#include + +using std::string; +namespace fs = boost::filesystem; +using namespace testing; +using namespace graphtools; +using namespace graphIO; + +class ReferenceGenome : public testing::Test +{ +protected: + // Write Fasta file shared by all refGenome dependent tests. + static void SetUpTestCase() + { + auto tmpFasta = fs::unique_path(fs::temp_directory_path() / "%%%%%%%%.fa"); + fs::ofstream fastaOut(tmpFasta); + fastaOut << ">chr12\n" + << "AAAAAGGGGG" << std::endl; + fastaOut.close(); + fastaPath = tmpFasta.string(); + } + + static void TearDownTestCase() + { + auto tmpFasta = fs::path(fastaPath); + fs::remove(tmpFasta); + fs::remove(tmpFasta.replace_extension(".fa.fai")); + } + + static string fastaPath; +}; +string ReferenceGenome::fastaPath = ""; + +TEST_F(ReferenceGenome, GetSequence_success) +{ + RefGenome ref(fastaPath); + string const seq = ref.extractSeq(ReferenceInterval("chr12", 3, 6)); + ASSERT_EQ("AAG", seq); +} + +TEST_F(ReferenceGenome, ParseInvalidRegion_throws) +{ + RefGenome ref(fastaPath); + EXPECT_ANY_THROW(ReferenceInterval::parseRegion("chr12-4-6")); +} + +TEST_F(ReferenceGenome, NonExistingSequence_throws) +{ + RefGenome ref(fastaPath); + EXPECT_ANY_THROW(ref.extractSeq(ReferenceInterval("chr12", 4, 11))); + EXPECT_ANY_THROW(ref.extractSeq(ReferenceInterval("chr13", 4, 6))); +} + +TEST_F(ReferenceGenome, ParseRegion_success) +{ + RefGenome ref(fastaPath); + ReferenceInterval reg = ReferenceInterval::parseRegion("chr12:4-6"); + + ASSERT_EQ("chr12", reg.contig); + ASSERT_EQ(4, reg.start); + ASSERT_EQ(6, reg.end); +} + +std::mutex HtsLibMutex; +class BamWriterTest : public Test +{ +public: + fs::path bamFile; + void SetUp() override + { + HtsLibMutex.lock(); + bamFile = fs::unique_path(fs::temp_directory_path() / "%%%%%%%%.bam"); + } + + void TearDown() override + { + HtsLibMutex.unlock(); + fs::remove(bamFile); + } +}; + +TEST_F(BamWriterTest, UnplacedAlignment_SingleRead) +{ + ReferenceContigs contigs = {}; + BamWriter bw(bamFile.string(), contigs); + auto aln = bw.makeAlignment("Read2", "GATC", std::vector(), BamWriter::PairingInfo::Unpaired, "1(1M2D)2(4M)"); + EXPECT_NO_THROW(bw.writeAlignment(aln)); + HtsLibMutex.unlock(); + ASSERT_EQ("", aln.chromName); + ASSERT_EQ(-1, aln.pos); + ASSERT_EQ(false, aln.isMate1); + ASSERT_EQ(false, aln.isPaired); + // TODO GT-538: Add test to validate BAM +} + +TEST_F(BamWriterTest, UnplacedAlignment_PairedReads) +{ + ReferenceContigs contigs = {}; + BamWriter bw(bamFile.string(), contigs); + auto aln1 = bw.makeAlignment("Read1", "ATTAC", std::vector(), BamWriter::PairingInfo::FirstMate, "1(3M)"); + ASSERT_NO_THROW(bw.writeAlignment(aln1)); + ASSERT_TRUE(aln1.isMate1); + ASSERT_TRUE(aln1.isPaired); + auto aln2 + = bw.makeAlignment("Read1", "GATC", std::vector(), BamWriter::PairingInfo::SecondMate, "1(1M2D)2(4M)"); + ASSERT_NO_THROW(bw.writeAlignment(aln2)); + ASSERT_FALSE(aln2.isMate1); + ASSERT_TRUE(aln2.isPaired); + // TODO GT-538: Add test to validate BAM +} + +TEST_F(BamWriterTest, PlacedAlignment_SingleRead) +{ + ReferenceContigs contigs = { std::make_pair("chr1", 10), std::make_pair("chr2", 20) }; + BamWriter bw(bamFile.string(), contigs); + Graph graph = makeSwapGraph("AAAA", "C", "T", "GGGG"); + GraphReferenceMapping mapping(&graph); + mapping.addMapping(0, ReferenceInterval("chr2", 10, 14)); + Path path(&graph, 2, { 0, 1, 3 }, 3); + std::vector alignments = { Alignment(2, "2M"), Alignment(0, "1M"), Alignment(0, "3M") }; + GraphAlignment gAlign(path, alignments); + + auto aln = bw.makeAlignment(mapping, "read1", "AACGGG", {}, BamWriter::PairingInfo::Unpaired, gAlign); + ASSERT_NO_THROW(bw.writeAlignment(aln)); + ASSERT_EQ("chr2", aln.chromName); + ASSERT_EQ(12, aln.pos); + ASSERT_EQ("0[Ref start: 2, 2M]1[Ref start: 0, 1M]3[Ref start: 0, 3M]", aln.graphCigar); +} + +TEST(GraphLoading, ValidGraph_Loaded) +{ + Json jGraph; + jGraph["nodes"] = { { { "name", "n1" }, { "sequence", "AATG" } }, + { { "name", "n2" }, { "sequence", "AA" } }, + { { "name", "n3" }, { "sequence", "TG" } } }; + jGraph["edges"] = { { { "from", "n1" }, { "to", "n2" } }, + { { "from", "n2" }, { "to", "n3" } }, + { { "from", "n2" }, { "to", "n2" } } }; + jGraph["graph_id"] = "TestGraph"; + + Graph const graph = parseGraph(jGraph); + + ASSERT_EQ("TestGraph", graph.graphId); + ASSERT_EQ(jGraph["nodes"].size(), graph.numNodes()); + ASSERT_EQ(jGraph["edges"].size(), graph.numEdges()); + for (size_t i = 0; i != graph.numNodes(); ++i) + { + ASSERT_EQ(jGraph["nodes"][i]["name"], graph.nodeName(i)); + ASSERT_EQ(jGraph["nodes"][i]["sequence"], graph.nodeSeq(i)); + } + ASSERT_TRUE(graph.hasEdge(0, 1)); + ASSERT_TRUE(graph.hasEdge(1, 2)); + ASSERT_TRUE(graph.hasEdge(1, 1)); + ASSERT_FALSE(graph.hasEdge(0, 0)); + ASSERT_FALSE(graph.hasEdge(0, 2)); +} + +TEST(GraphLoading, MissingSequence_Throws) +{ + Json jGraph; + jGraph["nodes"] = { + { { "name", "n1" } }, + }; + jGraph["edges"] = Json::array(); + + ASSERT_ANY_THROW(parseGraph(jGraph)); +} + +TEST(GraphLoading, EmptySequence_Throws) +{ + Json jGraph; + jGraph["nodes"] = { + { { "name", "n1" }, { "sequence", "" } }, + }; + jGraph["edges"] = Json::array(); + + ASSERT_ANY_THROW(parseGraph(jGraph)); +} + +TEST(GraphLoading, InvalidEdgeNode_Throws) +{ + Json jGraph; + jGraph["nodes"] = { + { { "name", "n1" }, { "sequence", "AATG" } }, + }; + jGraph["edges"] = { + { { "from", "n1" }, { "to", "n2" } }, + }; + + ASSERT_ANY_THROW(parseGraph(jGraph)); +} + +TEST(GraphLoading, BackwardsEdge_Throws) +{ + Json jGraph; + jGraph["nodes"] = { + { { "name", "n1" }, { "sequence", "AATG" } }, + { { "name", "n2" }, { "sequence", "AATG" } }, + }; + jGraph["edges"] = { + { { "from", "n2" }, { "to", "n1" } }, + }; + + ASSERT_ANY_THROW(parseGraph(jGraph)); +} + +TEST_F(ReferenceGenome, LoadGraphSequence_Success) +{ + Json jGraph; + jGraph["reference_genome"] = fastaPath; + jGraph["nodes"] = { { { "name", "n1" }, { "reference", "chr12:3-7" } } }; + jGraph["edges"] = Json::array(); + + Graph const graph = parseGraph(jGraph); + + ASSERT_EQ("AAGG", graph.nodeSeq(0)); +} + +TEST(GraphLoading, MissingReference_Throws) +{ + Json jGraph; + jGraph["nodes"] = { { { "name", "n1" }, { "reference", "chr12:4-7" } } }; + jGraph["edges"] = Json::array(); + + ASSERT_ANY_THROW(parseGraph(jGraph)); +} + +TEST(GraphWriting, EmptyGraph_RoundTrip) +{ + Graph graph(0); + Json jGraph = graphToJson(graph); + Graph newGraph = parseGraph(jGraph); + + ASSERT_EQ((size_t)0, newGraph.numNodes()); +} + +TEST(GraphWriting, Graph_RoundTrip) +{ + Graph graph(2, "Small Graph"); + graph.setNodeName(0, "n0"); + graph.setNodeSeq(0, "AA"); + graph.setNodeName(1, "n1"); + graph.setNodeSeq(1, "TT"); + graph.addEdge(0, 1); + graph.addEdge(1, 1); + graph.addLabelToEdge(1, 1, "foo"); + + Json jGraph = graphToJson(graph); + Graph newGraph = parseGraph(jGraph); + + ASSERT_EQ("Small Graph", graph.graphId); + ASSERT_EQ(graph.numNodes(), newGraph.numNodes()); + ASSERT_EQ(graph.numEdges(), newGraph.numEdges()); + for (size_t i = 0; i != graph.numNodes(); ++i) + { + ASSERT_EQ(graph.nodeName(i), newGraph.nodeName(i)); + ASSERT_EQ(graph.nodeSeq(i), newGraph.nodeSeq(i)); + } + ASSERT_TRUE(newGraph.hasEdge(0, 1)); + ASSERT_TRUE(newGraph.hasEdge(1, 1)); + ASSERT_FALSE(newGraph.hasEdge(0, 0)); + ASSERT_EQ(graph.edgeLabels(1, 1), newGraph.edgeLabels(1, 1)); +} + +TEST_F(ReferenceGenome, LoadGraphMapping_Success) +{ + Json jGraph; + jGraph["reference_genome"] = fastaPath; + jGraph["nodes"] + = { { { "name", "n1" }, { "reference", "chr12:4-7" } }, { { "name", "n2" }, { "sequence", "TCGA" } } }; + jGraph["edges"] = Json::array(); + + Graph const graph = parseGraph(jGraph); + GraphReferenceMapping const refmap = parseReferenceMapping(jGraph, graph); + + auto const pos = refmap.map(0, 2); + ASSERT_TRUE(pos); + ASSERT_EQ("chr12", pos->contig); + ASSERT_EQ(6, pos->start); + + ASSERT_FALSE(refmap.map(1, 2)); // Node without mapping + ASSERT_ANY_THROW(refmap.map(0, 3)); // Position outside node + ASSERT_ANY_THROW(refmap.map(2, 0)); // Nonexistent node +} \ No newline at end of file diff --git a/thirdparty/graph-tools-master/tests/GraphOperationsTest.cpp b/thirdparty/graph-tools-master/tests/GraphOperationsTest.cpp new file mode 100755 index 0000000..d709e98 --- /dev/null +++ b/thirdparty/graph-tools-master/tests/GraphOperationsTest.cpp @@ -0,0 +1,87 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphcore/GraphOperations.hh" +#include "graphcore/Graph.hh" +#include "graphcore/GraphBuilders.hh" + +#include "gtest/gtest.h" + +using std::set; +using std::string; +using std::vector; + +using namespace graphtools; + +TEST(GraphReversal, SwapGraph_Reversed) +{ + Graph graph = makeSwapGraph("CCCC", "AAAA", "GGGG", "TTTT"); + ASSERT_EQ(4ul, graph.numNodes()); + auto reversed_graph = reverseGraph(graph, false); + ASSERT_EQ(4ul, reversed_graph.numNodes()); + ASSERT_EQ("TTTT", reversed_graph.nodeSeq(0)); + ASSERT_EQ("GGGG", reversed_graph.nodeSeq(1)); + ASSERT_EQ("AAAA", reversed_graph.nodeSeq(2)); + ASSERT_EQ("CCCC", reversed_graph.nodeSeq(3)); + ASSERT_EQ((set{ 1, 2 }), reversed_graph.successors(0)); + ASSERT_EQ((set{ 3 }), reversed_graph.successors(1)); + ASSERT_EQ((set{ 3 }), reversed_graph.successors(2)); + ASSERT_TRUE(reversed_graph.successors(3).empty()); +} + +TEST(GraphReversal, SwapGraph_SequenceReversed) +{ + Graph graph = makeSwapGraph("ACCC", "ATAA", "GGTG", "TTTA"); + ASSERT_EQ(4ul, graph.numNodes()); + auto reversed_graph = reverseGraph(graph, false); + ASSERT_EQ(4ul, reversed_graph.numNodes()); + ASSERT_EQ("ATTT", reversed_graph.nodeSeq(0)); + ASSERT_EQ("GTGG", reversed_graph.nodeSeq(1)); + ASSERT_EQ("AATA", reversed_graph.nodeSeq(2)); + ASSERT_EQ("CCCA", reversed_graph.nodeSeq(3)); + ASSERT_EQ((set{ 1, 2 }), reversed_graph.successors(0)); + ASSERT_EQ((set{ 3 }), reversed_graph.successors(1)); + ASSERT_EQ((set{ 3 }), reversed_graph.successors(2)); + ASSERT_TRUE(reversed_graph.successors(3).empty()); +} + +TEST(GraphReversal, SwapGraph_SequenceReverseComplemented) +{ + Graph graph = makeSwapGraph("ACCC", "ATAA", "GGTG", "TTTA"); + ASSERT_EQ(4ul, graph.numNodes()); + auto reversed_graph = reverseGraph(graph, true); + ASSERT_EQ(4ul, reversed_graph.numNodes()); + ASSERT_EQ("TAAA", reversed_graph.nodeSeq(0)); + ASSERT_EQ("CACC", reversed_graph.nodeSeq(1)); + ASSERT_EQ("TTAT", reversed_graph.nodeSeq(2)); + ASSERT_EQ("GGGT", reversed_graph.nodeSeq(3)); + ASSERT_EQ((set{ 1, 2 }), reversed_graph.successors(0)); + ASSERT_EQ((set{ 3 }), reversed_graph.successors(1)); + ASSERT_EQ((set{ 3 }), reversed_graph.successors(2)); + ASSERT_TRUE(reversed_graph.successors(3).empty()); +} diff --git a/thirdparty/graph-tools-master/tests/GraphReferenceMappingTest.cpp b/thirdparty/graph-tools-master/tests/GraphReferenceMappingTest.cpp new file mode 100755 index 0000000..76ac801 --- /dev/null +++ b/thirdparty/graph-tools-master/tests/GraphReferenceMappingTest.cpp @@ -0,0 +1,82 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Felix Schlesinger +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphcore/GraphReferenceMapping.hh" +#include "graphcore/GraphBuilders.hh" + +#include + +#include "gtest/gtest.h" + +#include "graphcore/Graph.hh" +#include "graphcore/Path.hh" + +using namespace graphtools; + +class GraphMapping : public testing::Test +{ +public: + GraphMapping() + : graph(makeSwapGraph("AAAA", "C", "T", "GGGG")) + , mapping(&graph) + { + mapping.addMapping(0, ReferenceInterval("chr1", 10, 14)); + mapping.addMapping(2, ReferenceInterval("chr1", 15, 16)); + mapping.addMapping(3, ReferenceInterval("chr1", 16, 20)); + } + + Graph graph; + GraphReferenceMapping mapping; +}; +TEST_F(GraphMapping, MapNodePosition_Success) +{ + ASSERT_EQ(ReferenceInterval::makePosition("chr1", 10), mapping.map(0, 0)); + ASSERT_EQ(ReferenceInterval::makePosition("chr1", 13), mapping.map(0, 3)); + ASSERT_EQ(ReferenceInterval::makePosition("chr1", 15), mapping.map(2, 0)); +} +TEST_F(GraphMapping, UnmappedNode_ReturnEmpty) { ASSERT_FALSE(mapping.map(1, 0)); } +TEST_F(GraphMapping, MapInvalidPos_Throws) +{ + ASSERT_ANY_THROW(mapping.map(2, 1)); // Outside Node + ASSERT_ANY_THROW(mapping.map(5, 0)); // Invalid node +} +TEST_F(GraphMapping, MapPath_StartingNode) +{ + auto const map = mapping.map(Path(&graph, 1, { 0, 1, 3 }, 4)); + ASSERT_EQ(ReferenceInterval::makePosition("chr1", 11), map); +} +TEST_F(GraphMapping, MapPath_ExtendingNode) +{ + auto const map = mapping.map(Path(&graph, 0, { 1, 3 }, 4)); + ASSERT_EQ(ReferenceInterval::makePosition("chr1", 16), map); +} +TEST_F(GraphMapping, UnmappedPath_ReturnEmpty) +{ + auto const map = mapping.map(Path(&graph, 0, { 1 }, 1)); + ASSERT_FALSE(map); +} diff --git a/thirdparty/graph-tools-master/tests/GraphTest.cpp b/thirdparty/graph-tools-master/tests/GraphTest.cpp new file mode 100755 index 0000000..c293c91 --- /dev/null +++ b/thirdparty/graph-tools-master/tests/GraphTest.cpp @@ -0,0 +1,184 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphcore/Graph.hh" + +#include "gtest/gtest.h" + +using std::set; +using std::string; +using std::vector; + +using namespace graphtools; + +TEST(GraphConstruction, TypicalNodeCount_GraphConstructed) +{ + Graph graph(3); + ASSERT_EQ(3ul, graph.numNodes()); +} + +TEST(NodeNameManipulation, TypicalNode_NodeNameSet) +{ + Graph graph(3); + graph.setNodeName(1, "LF"); + ASSERT_EQ("LF", graph.nodeName(1)); +} + +TEST(NodeNameManipulation, NonexistingNode_ExceptionRaised) +{ + Graph graph(1); + ASSERT_ANY_THROW(graph.setNodeName(1, "LF")); + ASSERT_ANY_THROW(graph.nodeName(1)); +} + +TEST(NodeSequenceManipulation, TypicalSequence_SequenceSet) +{ + Graph graph(3); + graph.setNodeSeq(1, "ATT"); + EXPECT_EQ("ATT", graph.nodeSeq(1)); +} + +TEST(NodeSequenceManipulation, DegenerateSequence_SequenceExpansionObtained) +{ + Graph graph(3); + graph.setNodeSeq(1, "WC"); + vector expected_expansion = { "AC", "TC" }; + EXPECT_EQ(expected_expansion, graph.nodeSeqExpansion(1)); +} + +TEST(NodeSequenceManipulation, NonexistingNode_ExceptionRaised) +{ + Graph graph(3); + EXPECT_ANY_THROW(graph.setNodeSeq(4, "ATT")); + EXPECT_ANY_THROW(graph.nodeSeq(4)); + EXPECT_ANY_THROW(graph.nodeSeqExpansion(4)); +} + +TEST(NodeSequenceManipulation, EmptySequence_ExceptionRaised) +{ + Graph graph(3); + EXPECT_ANY_THROW(graph.setNodeSeq(1, "")); +} + +TEST(AddingEdges, TypicalEdge_EdgeAdded) +{ + Graph graph(3); + graph.addEdge(0, 1); + graph.addEdge(0, 0); + EXPECT_TRUE(graph.hasEdge(0, 1)); + EXPECT_TRUE(graph.hasEdge(0, 0)); +} + +TEST(AddingEdges, EdgeBreakingTopologicalOrder_ExceptionRaised) +{ + Graph graph(3); + EXPECT_ANY_THROW(graph.addEdge(2, 1)); +} + +TEST(AddingEdges, EdgesBetweenNonexistingNodes_ExceptionRaised) +{ + Graph graph(4); + EXPECT_ANY_THROW(graph.addEdge(-1, 2)); + EXPECT_ANY_THROW(graph.addEdge(1, 4)); + EXPECT_ANY_THROW(graph.addEdge(4, 5)); +} + +TEST(AddingEdges, EdgesThatAlreadyExist_ExceptionRaised) +{ + Graph graph(4); + graph.addEdge(1, 2); + EXPECT_ANY_THROW(graph.addEdge(1, 2)); +} + +TEST(CheckingIfEdgesExist, EdgesBetweenNonexistingNodes_ExceptionRaised) +{ + Graph graph(4); + EXPECT_ANY_THROW(graph.hasEdge(-1, 2)); + EXPECT_ANY_THROW(graph.hasEdge(1, 4)); + EXPECT_ANY_THROW(graph.hasEdge(4, 5)); +} + +TEST(EdgeLabelManipulation, TypicalEdges_EdgesLabeled) +{ + Graph graph(4); + graph.addEdge(0, 2); + graph.addLabelToEdge(0, 2, "ref"); + graph.addLabelToEdge(0, 2, "alt"); + Labels expected_labels = { "ref", "alt" }; + ASSERT_EQ(expected_labels, graph.edgeLabels(0, 2)); +} + +TEST(EdgeLabelManipulation, NonexistingEdges_ExceptionRaised) +{ + Graph graph(4); + EXPECT_ANY_THROW(graph.addLabelToEdge(0, 1, "ref")); + EXPECT_ANY_THROW(graph.addLabelToEdge(0, 4, "ref")); + EXPECT_ANY_THROW(graph.edgeLabels(0, 1)); +} + +TEST(GettingNodeNeighbors, TypicalNode_SuccessorsFound) +{ + Graph graph(4); + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(0, 3); + graph.addEdge(2, 3); + + const set expected_successors = { 1, 2, 3 }; + ASSERT_EQ(expected_successors, graph.successors(0)); + ASSERT_TRUE(graph.successors(1).empty()); +} + +TEST(GettingNodeNeighbors, LoopAtNode_SuccessorsFound) +{ + Graph graph(4); + graph.addEdge(0, 0); + graph.addEdge(0, 1); + + const set expected_predecessors = { 0, 1 }; + ASSERT_EQ(expected_predecessors, graph.successors(0)); +} + +TEST(GettingNodeNeighbors, TypicalNode_PredecessorsFound) +{ + Graph graph(4); + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(0, 3); + graph.addEdge(2, 3); + + const set expected_predecessors = { 0, 2 }; + ASSERT_EQ(expected_predecessors, graph.predecessors(3)); +} + +TEST(GettingNodeNeighbors, NeighborsOfNonexistingNode_ExceptionRaised) +{ + Graph graph(4); + EXPECT_ANY_THROW(graph.successors(4)); + EXPECT_ANY_THROW(graph.predecessors(-1)); +} diff --git a/thirdparty/graph-tools-master/tests/IntervalBufferTest.cpp b/thirdparty/graph-tools-master/tests/IntervalBufferTest.cpp new file mode 100755 index 0000000..98242fe --- /dev/null +++ b/thirdparty/graph-tools-master/tests/IntervalBufferTest.cpp @@ -0,0 +1,257 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphutils/IntervalBuffer.hh" +#include "gtest/gtest.h" + +#include +#include +#include + +using namespace intervals; + +TEST(IntervalBuffer, TestIntervalBuffer) +{ + IntervalBuffer ib; + + ib.addInterval(10, 20, 0); + ib.addInterval(12, 30, 0); + ib.addInterval(10, 30, 1); + ib.addInterval(32, 35, 1); + ib.addInterval(36, 37, 1); + ib.addInterval(38, 40, 1); + ib.addInterval(42, 45, 1); + + IntervalBuffer ib2 = ib; + + ASSERT_TRUE(ib2.isCovered(15, 16, 0)); + ASSERT_TRUE(ib2.isCovered(15, 21, 0)); + ASSERT_TRUE(ib2.isCovered(11, 21, 0)); + ASSERT_TRUE(!ib2.isCovered(11, 31, 0)); + ASSERT_TRUE(!ib2.isCovered(8, 15, 0)); + ASSERT_TRUE(!ib2.isCovered(8, 9, 0)); + + ASSERT_TRUE(ib2.isCovered(15, 16, 1)); + ASSERT_TRUE(ib2.isCovered(32, 39, 1)); + ASSERT_TRUE(!ib2.isCovered(32, 43, 1)); + + ib.advance(30); + + ASSERT_TRUE(!ib.isCovered(10, 11, 0)); + ASSERT_TRUE(!ib.isCovered(15, 16, 0)); + ASSERT_TRUE(!ib.isCovered(15, 21, 0)); + ASSERT_TRUE(!ib.isCovered(11, 21, 0)); + ASSERT_TRUE(ib.isCovered(30, 30, 0)); + ASSERT_TRUE(!ib.isCovered(8, 15, 0)); + ASSERT_TRUE(!ib.isCovered(8, 9, 0)); + + ASSERT_TRUE(!ib.isCovered(15, 16, 1)); + ASSERT_TRUE(ib.isCovered(32, 39, 1)); + ASSERT_TRUE(!ib.isCovered(32, 43, 1)); +} + +TEST(IntervalBuffer, TestIntervalBufferRandom) +{ + static const int count = 2048; + static const int icount = 20; + static const int tcount = 100; + + for (int k = 0; k < tcount; ++k) + { + bool ivs[count]; + + std::vector> ivlist; + + for (int i = 0; i < count; ++i) + { + ivs[i] = false; + } + + for (int i = 0; i < icount; ++i) + { + int64_t start = rand() % count; + int64_t end = std::min(start + rand() % 100, (int64_t)count - 1); + ivlist.push_back(std::pair(start, end)); + for (int j = (int)start; j <= end; ++j) + { + ivs[j] = true; + } + } + + struct random_end_less + { + inline bool operator()(const std::pair& p, const std::pair& q) const + { + return p.first < q.first; + } + }; + + std::sort(ivlist.begin(), ivlist.end(), random_end_less()); + + IntervalBuffer ib; + + for (auto const& p : ivlist) + { + ib.addInterval(p.first, p.second, 2); + } + + for (int i = 0; i < count; ++i) + { + int start = rand() % count; + int end = std::min(start + rand() % 50, (int)count - 1); + + bool is_covered = true; + for (int j = start; j <= end; ++j) + { + if (!ivs[j]) + { + is_covered = false; + break; + } + } + + if (is_covered != ib.isCovered(start, end, 2)) + { + std::cerr << "(c) Interval " << start << "-" << end << ": ivs say " << is_covered << " ib says " + << ib.isCovered(start, end, 2) << "\n"; + } + + ASSERT_EQ(is_covered, ib.isCovered(start, end, 2)); + } + + // check overlaps + for (int i = 0; i < count; ++i) + { + int start = rand() % count; + int end = std::min(start + rand() % 100, (int)count - 1); + + bool is_covered = false; + for (int j = start; j <= end; ++j) + { + if (ivs[j]) + { + is_covered = true; + break; + } + } + + if (is_covered != ib.hasOverlap(start, end, 2)) + { + std::cerr << "(o) Interval " << start << "-" << end << ": ivs say " << is_covered << " ib says " + << ib.hasOverlap(start, end, 2) << "\n"; + } + + ASSERT_EQ(is_covered, ib.hasOverlap(start, end, 2)); + } + +#ifdef _DEBUG + // remember for debugging + IntervalBuffer ib_before = ib; +#endif + ib.advance(count / 2); + + for (int i = 0; i < count / 2; ++i) + { + ivs[i] = false; + } + + for (int i = 0; i < count; ++i) + { + int start = rand() % count; + int end = std::min(start + rand() % 50, (int)count - 1); + + bool is_covered = true; + for (int j = start; j <= end; ++j) + { + if (!ivs[j]) + { + is_covered = false; + break; + } + } + + if (is_covered != ib.isCovered(start, end, 2)) + { + std::cerr << "(c) Interval " << start << "-" << end << ": ivs say " << is_covered << " ib says " + << ib.isCovered(start, end, 2) << "\n"; + } + + ASSERT_EQ(is_covered, ib.isCovered(start, end, 2)); + } + + // check overlaps + for (int i = 0; i < count; ++i) + { + int start = rand() % count; + int end = std::min(start + rand() % 100, (int)count - 1); + + bool is_covered = false; + for (int j = start; j <= end; ++j) + { + if (ivs[j]) + { + is_covered = true; + break; + } + } + + if (is_covered != ib.hasOverlap(start, end, 2)) + { + std::cerr << "(o) Interval " << start << "-" << end << ": ivs say " << is_covered << " ib says " + << ib.hasOverlap(start, end, 2) << "\n"; + } + + ASSERT_EQ(is_covered, ib.hasOverlap(start, end, 2)); + } + } +} + +TEST(IntervalBuffer, TestIntervalBufferIvmerge) +{ + IntervalBuffer ib; + + ib.addInterval(10, 20, 1); + ib.addInterval(12, 30, 1); + ib.addInterval(10, 30, 1); + ib.addInterval(32, 35, 1); + ib.addInterval(36, 37, 1); + ib.addInterval(38, 40, 1); + ib.addInterval(42, 45, 1); + + const std::list> expected = { { 10, 30 }, { 32, 40 }, { 42, 45 } }; + auto ivlist = ib.getIntervals(1); + ASSERT_EQ(expected.size(), ivlist.size()); + auto it1 = expected.cbegin(), it2 = ivlist.cbegin(); + while (it1 != expected.cend() && it2 != ivlist.cend()) + { + ASSERT_EQ(it1->first, it2->first); + ASSERT_EQ(it1->second, it2->second); + ++it1; + ++it2; + } +} diff --git a/thirdparty/graph-tools-master/tests/IntervalListTest.cpp b/thirdparty/graph-tools-master/tests/IntervalListTest.cpp new file mode 100755 index 0000000..921e85a --- /dev/null +++ b/thirdparty/graph-tools-master/tests/IntervalListTest.cpp @@ -0,0 +1,258 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphutils/IntervalList.hh" +#include "gtest/gtest.h" + +#include +#include +#include +#include + +using namespace intervals; + +template void listcmp(std::list<_t> expected, std::list<_t> actual) +{ + auto i1 = expected.begin(); + auto i2 = actual.begin(); + size_t count = 0; + while (i1 != expected.end() && i2 != actual.end()) + { + ASSERT_EQ(*i1, *i2); + ++count; + ++i1; + ++i2; + } + ASSERT_EQ(count, expected.size()); +} + +template <> void listcmp(std::list> expected, std::list> actual) +{ + auto i1 = expected.begin(); + auto i2 = actual.begin(); + size_t count = 0; + while (i1 != expected.end() && i2 != actual.end()) + { + ASSERT_EQ(i1->first, i2->first); + ASSERT_EQ(i1->second, i2->second); + ++count; + ++i1; + ++i2; + } + ASSERT_EQ(count, expected.size()); +} + +struct ivcount : public interval +{ + ivcount() + : interval() + , count(0) + { + } + ivcount(int64_t _start, int64_t _end, int _count) + : interval(_start, _end) + , count(_count) + { + } + virtual void merge(interval const& rhs) + { + interval::merge(rhs); + count += (dynamic_cast(rhs)).count; + } + + bool operator==(const ivcount& other) const + { + return start == other.start && end == other.end && count == other.count; + } + + bool operator!=(const ivcount& other) const { return !(*this == other); } + + int count; +}; + +std::ostream& operator<<(std::ostream& o, ivcount const& x) +{ + o << x.start << "-" << x.end << ":" << x.count; + return o; +} + +TEST(IntervalList, TestIntervalList) +{ + IntervalList ivl; + + ivl.add(ivcount(10, 20, 1)); + ivl.add(ivcount(12, 30, 1)); + ivl.add(ivcount(32, 35, 1)); + ivl.add(ivcount(36, 37, 1)); + ivl.add(ivcount(38, 40, 1)); + ivl.add(ivcount(42, 45, 1)); + + std::list expected = { ivcount(10, 30, 2), ivcount(32, 40, 3), ivcount(42, 45, 1) }; + + listcmp(expected, ivl.getIntervals()); + + ASSERT_EQ(ivl.query(11, 12).count, 2); + ASSERT_EQ(ivl.query(31, 37).count, 3); + ASSERT_EQ(ivl.query(31, 39).count, 3); + ASSERT_EQ(ivl.query(42, 44).count, 1); + ASSERT_EQ(ivl.query(41, 41).count, 0); + ASSERT_EQ(ivl.query(45, 45).count, 1); + + ivl.keep_only(31, 44); + ASSERT_EQ(ivl.query(11, 12).count, 0); + ASSERT_EQ(ivl.query(31, 37).count, 3); + ASSERT_EQ(ivl.query(31, 39).count, 3); + ASSERT_EQ(ivl.query(42, 44).count, 1); + ASSERT_EQ(ivl.query(41, 41).count, 0); + ASSERT_EQ(ivl.query(45, 45).count, 0); + + expected = { ivcount(32, 40, 3), ivcount(42, 44, 1) }; + + listcmp(expected, ivl.getIntervals()); +} + +TEST(IntervalList, TestIntervalList2) +{ + struct ivlist : public interval + { + ivlist() + : interval() + { + } + ivlist(int64_t _start, int64_t _end) + : interval(_start, _end) + { + contained_ivs.push_back(std::pair(_start, _end)); + } + virtual void merge(interval const& rhs) + { + contained_ivs.insert( + contained_ivs.end(), (dynamic_cast(rhs)).contained_ivs.begin(), + (dynamic_cast(rhs)).contained_ivs.end()); + interval::merge(rhs); + } + + std::list> contained_ivs; + }; + + IntervalList ivl; + + ivl.add(ivlist(10, 20)); + ivl.add(ivlist(12, 30)); + ivl.add(ivlist(32, 35)); + ivl.add(ivlist(36, 37)); + ivl.add(ivlist(38, 40)); + ivl.add(ivlist(42, 45)); + + std::list> expected, actual; + + expected = { std::pair{ 10, 20 }, std::pair{ 12, 30 } }; + actual = ivl.query(11, 12).contained_ivs; + listcmp(expected, actual); + + expected = { std::pair{ 32, 35 }, std::pair{ 36, 37 }, + std::pair{ 38, 40 } }; + actual = ivl.query(31, 37).contained_ivs; + listcmp(expected, actual); +} + +TEST(IntervalList, TestIntervalListRandom) +{ + static const int count = 2048; + static const int icount = 20; + static const int tcount = 300; + + for (int k = 0; k < tcount; ++k) + { + IntervalList ivl; + bool ivs[count]; + std::fill(std::begin(ivs), std::end(ivs), false); + + for (int i = 0; i < icount; ++i) + { + int64_t start = rand() % count; + int64_t end = std::min(start + rand() % 100, (int64_t)count - 1); + std::fill(ivs + start, ivs + end + 1, true); + ivl.add(interval(start, end)); + } + + int64_t iv_start = 0; + int64_t iv_end = count - 1; +#ifdef _DEBUG + // remember for debugging + IntervalList ivl_before = ivl; +#endif + if (k % 3 == 0) + { + iv_end = std::max(0, (rand() % count) - 1); + ivl.remove_to(iv_end); + std::fill(ivs, ivs + iv_end + 1, false); + } + else if (k % 3 == 1) + { + iv_start = std::max(0, (rand() % count) - 1); + ivl.remove_from(iv_start); + std::fill(ivs + iv_start, std::end(ivs), false); + } + + for (int i = 0; i < count; ++i) + { + int64_t start = rand() % count; + int64_t end = std::min(start + rand() % 100, (int64_t)count - 1); + bool iv_ovl = std::any_of(ivs + start, ivs + end + 1, [](bool b) { return b; }); + interval iv = ivl.query(start, end); + bool ivl_ovl = iv.start >= 0 && iv.end >= 0; + if (iv_ovl != ivl_ovl) + { + std::cerr << "IntervalList random test failed" << std::endl; + std::cerr << "Test array:" << std::endl; + std::string ic_str; + ic_str.resize(count); + for (int ic = 0; ic < count; ++ic) + { + if (ivs[ic]) + { + ic_str[ic] = '*'; + } + else + { + ic_str[ic] = '-'; + } + } + std::cerr << ic_str << std::endl; + auto all_intervals = ivl.getIntervals(); + std::cerr << "Intervals: " << all_intervals.size() << std::endl; + for (auto const& iv : all_intervals) + { + std::cerr << "[" << iv.start << ", " << iv.end << "]" << std::endl; + } + } + ASSERT_EQ(iv_ovl, ivl_ovl); + } + } +} diff --git a/thirdparty/graph-tools-master/tests/KmerIndexOperationsTest.cpp b/thirdparty/graph-tools-master/tests/KmerIndexOperationsTest.cpp new file mode 100755 index 0000000..4bcbfae --- /dev/null +++ b/thirdparty/graph-tools-master/tests/KmerIndexOperationsTest.cpp @@ -0,0 +1,47 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/KmerIndexOperations.hh" + +#include "gtest/gtest.h" + +#include "graphcore/Graph.hh" +#include "graphcore/GraphBuilders.hh" +#include "graphutils/SequenceOperations.hh" + +using namespace graphtools; + +TEST(StrandClassification, TypicalSequence_StrandDetermined) +{ + Graph graph = makeStrGraph("AAAACC", "CCG", "ATTT"); + KmerIndex kmer_index(graph, 3); + EXPECT_TRUE(checkIfForwardOriented(kmer_index, "CCGCCGCCGCCG")); + EXPECT_FALSE(checkIfForwardOriented(kmer_index, reverseComplement("CCGCCGCCGCCG"))); + EXPECT_TRUE(checkIfForwardOriented(kmer_index, "CCGACGCCTCCG")); + EXPECT_FALSE(checkIfForwardOriented(kmer_index, reverseComplement("CCGACGCCTCCG"))); +} diff --git a/thirdparty/graph-tools-master/tests/KmerIndexTest.cpp b/thirdparty/graph-tools-master/tests/KmerIndexTest.cpp new file mode 100755 index 0000000..2f24920 --- /dev/null +++ b/thirdparty/graph-tools-master/tests/KmerIndexTest.cpp @@ -0,0 +1,149 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko , +// Peter Krusche +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/KmerIndex.hh" + +#include + +#include "gtest/gtest.h" + +#include "graphcore/GraphBuilders.hh" +#include "graphcore/Path.hh" + +using std::list; +using std::string; +using std::unordered_set; + +using namespace graphtools; + +TEST(KmerIndexInitialization, 1mers_IndexCreated) +{ + Graph graph = makeDeletionGraph("AC", "GG", "CAG"); + + const int32_t kmer_size = 1; + KmerIndex kmer_index(graph, kmer_size); + + const list a_paths = { Path(&graph, 0, { 0 }, 1), Path(&graph, 1, { 2 }, 2) }; + const list c_paths = { Path(&graph, 1, { 0 }, 2), Path(&graph, 0, { 2 }, 1) }; + const list g_paths = { Path(&graph, 0, { 1 }, 1), Path(&graph, 1, { 1 }, 2), Path(&graph, 2, { 2 }, 3) }; + + const StringToPathsMap kmer_to_paths_maps = { { "A", a_paths }, { "C", c_paths }, { "G", g_paths } }; + + KmerIndex expected_kmer_index(kmer_to_paths_maps); + ASSERT_EQ(expected_kmer_index, kmer_index); +} + +TEST(KmerIndexInitialization, 2mers_IndexCreated) +{ + Graph graph = makeDeletionGraph("AK", "GG", "CAG"); + const int32_t kmer_size = 2; + KmerIndex kmer_index(graph, kmer_size); + + const list ag_paths = { Path(&graph, 0, { 0 }, 2), Path(&graph, 1, { 2 }, 3) }; + const list at_paths = { Path(&graph, 0, { 0 }, 2) }; + + const list gg_paths = { Path(&graph, 1, { 0, 1 }, 1), Path(&graph, 0, { 1 }, 2) }; + const list tg_paths = { Path(&graph, 1, { 0, 1 }, 1) }; + + const list gc_paths = { Path(&graph, 1, { 0, 2 }, 1), Path(&graph, 1, { 1, 2 }, 1) }; + const list tc_paths = { Path(&graph, 1, { 0, 2 }, 1) }; + + const list ca_paths = { Path(&graph, 0, { 2 }, 2) }; + + const StringToPathsMap kmer_to_paths_maps + = { { "AG", ag_paths }, { "AT", at_paths }, { "GG", gg_paths }, { "TG", tg_paths }, + { "GC", gc_paths }, { "TC", tc_paths }, { "CA", ca_paths } }; + + KmerIndex expected_kmer_index(kmer_to_paths_maps); + ASSERT_EQ(expected_kmer_index, kmer_index); +} + +TEST(KmerExtraction, TypicalIndex_KmersExtracted) +{ + Graph graph = makeDeletionGraph("AC", "GG", "CAG"); + const int32_t kmer_size = 2; + KmerIndex kmer_index(graph, kmer_size); + const unordered_set expected_kmers = { "AC", "CG", "CC", "GG", "GC", "CA", "AG" }; + ASSERT_EQ(expected_kmers, kmer_index.kmers()); +} + +TEST(PathExtraction, TypicalIndex_PathsExtracted) +{ + Graph graph = makeDoubleSwapGraph("AAA", "TTT", "CCC", "AAA", "TTT", "AAA", "TTT"); + const int32_t kmer_size = 4; + KmerIndex kmer_index(graph, kmer_size); + const list paths = kmer_index.getPaths("AATT"); + const list expected_paths + = { Path(&graph, 1, { 0, 1 }, 2), Path(&graph, 1, { 3, 4 }, 2), Path(&graph, 1, { 5, 6 }, 2) }; + ASSERT_EQ(expected_paths, paths); +} + +TEST(CheckingIfKmersArePresent, TypicalKmers_CheckPerformed) +{ + Graph graph = makeDoubleSwapGraph("AAA", "TTT", "CCC", "AAA", "TTT", "AAA", "TTT"); + const int32_t kmer_size = 6; + KmerIndex kmer_index(graph, kmer_size); + EXPECT_TRUE(kmer_index.contains("AAATTT")); + EXPECT_FALSE(kmer_index.contains("AAATTG")); + EXPECT_FALSE(kmer_index.contains("AAA")); +} + +TEST(CountingNumberOfPathsAssociatedWithKmer, TypicalKmers_PathCountObtained) +{ + Graph graph = makeDoubleSwapGraph("AAA", "TTT", "CCC", "AAA", "TTT", "AAA", "TTT"); + { + const int32_t kmer_size = 6; + KmerIndex kmer_index(graph, kmer_size); + EXPECT_EQ(3u, kmer_index.numPaths("AAATTT")); + EXPECT_EQ(0u, kmer_index.numPaths("AAATTG")); + EXPECT_EQ(1u, kmer_index.numPaths("TTTTTT")); + } + { + const int32_t kmer_size = 1; + KmerIndex kmer_index(graph, kmer_size); + EXPECT_EQ(9u, kmer_index.numPaths("A")); + EXPECT_EQ(3u, kmer_index.numPaths("C")); + EXPECT_EQ(9u, kmer_index.numPaths("T")); + EXPECT_EQ(0u, kmer_index.numPaths("G")); + } +} + +TEST(UniqueKmerCounting, TypicalIndex_UniqueKmersCounted) +{ + Graph graph = makeDeletionGraph("AC", "GG", "ACG"); + + const int32_t kmer_size = 3; + KmerIndex kmer_index(graph, kmer_size); + + EXPECT_EQ(1u, kmer_index.numUniqueKmersOverlappingEdge(0, 1)); + EXPECT_EQ(2u, kmer_index.numUniqueKmersOverlappingEdge(1, 2)); + + EXPECT_EQ(3u, kmer_index.numUniqueKmersOverlappingNode(0)); + EXPECT_EQ(4u, kmer_index.numUniqueKmersOverlappingNode(2)); +} diff --git a/thirdparty/graph-tools-master/tests/LinearAlignmentOperationsTest.cpp b/thirdparty/graph-tools-master/tests/LinearAlignmentOperationsTest.cpp new file mode 100755 index 0000000..5deef1e --- /dev/null +++ b/thirdparty/graph-tools-master/tests/LinearAlignmentOperationsTest.cpp @@ -0,0 +1,240 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/LinearAlignmentOperations.hh" + +#include "gtest/gtest.h" + +#include + +using namespace graphtools; + +using std::list; +using std::make_pair; +using std::string; + +TEST(CheckingConsistencyOfAlignments, ConsistentAlignment_CheckPassed) +{ + // ref: CCCTTCCTTAATT---T---------- + // ||| || || | + // query: ---TTCGNN--TTGGGTCCCCCCCCCC + string reference = "CCCTTCCTTAATTT"; + string query = "TTCGNNTTGGGTCCCCCCCCCC"; + + Alignment alignment(3, "3M1X2N2D2M3I1M10S"); + + EXPECT_TRUE(checkConsistency(alignment, reference, query)); +} + +TEST(CheckingConsistencyOfAlignments, InconsistentAlignment_CheckFailed) +{ + const string query = "AAAT"; + const string reference = "AAAG"; + + EXPECT_FALSE(checkConsistency(Alignment(0, "4M"), reference, query)); + EXPECT_FALSE(checkConsistency(Alignment(0, "3M1X2S"), reference, query)); + EXPECT_FALSE(checkConsistency(Alignment(0, "3M2X"), reference, query)); + EXPECT_FALSE(checkConsistency(Alignment(0, "1M"), reference, query)); +} + +TEST(GettingSequencesForEachOperation, TypicalAlignment_SequencePairs) +{ + // ref: CCCTTCCTTAATT---T---------- + // ||| || || | + // query: ---TTCGNN--TTGGGTCCCCCCCCCC + string reference = "CCCTTCCTTAATTT"; + string query = "TTCGNNTTGGGTCCCCCCCCCC"; + + Alignment alignment(3, "3M1X2N2D2M3I1M10S"); + + list expected_sequence_pieces + = { { "TTC", "TTC" }, { "C", "G" }, { "TT", "NN" }, { "AA", "" }, + { "TT", "TT" }, { "", "GGG" }, { "T", "T" }, { "", "CCCCCCCCCC" } }; + + ASSERT_EQ(expected_sequence_pieces, getSequencesForEachOperation(alignment, reference, query)); +} + +TEST(CheckingIfAlignmentsAreBookended, AdjacentAlignments_AreBookended) +{ + // CCCG--TAG + // || ||| + // ATCGATTAG + Alignment first_alignment(2, "2S2M"); + Alignment second_alignment(4, "2I3M"); + + EXPECT_TRUE(checkIfBookended(first_alignment, second_alignment)); +} + +TEST(CheckingIfAlignmentsAreBookended, NonAdjacentAlignments_NotBookended) +{ + // CCCG--TAG + // || || + // ATCGATTAG + Alignment first_alignment(2, "2S2M"); + Alignment second_alignment(5, "2M"); + + EXPECT_FALSE(checkIfBookended(first_alignment, second_alignment)); +} + +TEST(CheckingIfAlignmentsAreBookended, FirstAlignmentEndsInSoftclip_NotBookended) +{ + Alignment first_alignment(0, "2M2S"); + Alignment second_alignment(2, "2M"); + + EXPECT_FALSE(checkIfBookended(first_alignment, second_alignment)); +} + +TEST(CheckingIfAlignmentsAreBookended, SecondAlignmentStartsInSoftclip_NotBookended) +{ + Alignment first_alignment(0, "2M"); + Alignment second_alignment(2, "2S2M"); + + EXPECT_FALSE(checkIfBookended(first_alignment, second_alignment)); +} + +TEST(CheckingIfAlignmentsAreBookended, OneOfTheAlignmentsEntirelySoftclipped_AreBookended) +{ + { + Alignment first_alignment(0, "2M"); + Alignment second_alignment(2, "4S"); + + EXPECT_TRUE(checkIfBookended(first_alignment, second_alignment)); + } + + { + Alignment first_alignment(0, "2M3S"); + Alignment second_alignment(2, "4S"); + + EXPECT_TRUE(checkIfBookended(first_alignment, second_alignment)); + } + + { + Alignment first_alignment(0, "2S"); + Alignment second_alignment(0, "2M"); + + EXPECT_TRUE(checkIfBookended(first_alignment, second_alignment)); + } + + { + Alignment first_alignment(0, "2S"); + Alignment second_alignment(0, "1S2M"); + + EXPECT_TRUE(checkIfBookended(first_alignment, second_alignment)); + } +} + +TEST(MergingAlignments, NotBookendededAlignments_ExceptionThrown) +{ + Alignment first_alignment(0, "2M"); + Alignment second_alignment(3, "1M"); + EXPECT_ANY_THROW(mergeAlignments(first_alignment, second_alignment)); +} + +TEST(MergingAlignments, AlignmentsWithDifferentBoundaryOperations_Merged) +{ + // CCCG--TAG + // || ||| + // ATCGATTAG + Alignment first_alignment(2, "2S2M"); + Alignment second_alignment(4, "2I3M"); + + Alignment merged_alignment = mergeAlignments(first_alignment, second_alignment); + + Alignment expected_alignment(2, "2S2M2I3M"); + EXPECT_EQ(expected_alignment, merged_alignment); +} + +TEST(MergingAlignments, AlignmentsWithSameBoundaryOperation_Merged) +{ + { + // CCCG---TAG + // || ||| + // ATCGATGTAG + Alignment first_alignment(2, "2S2M1I"); + Alignment second_alignment(4, "2I3M"); + + Alignment merged_alignment = mergeAlignments(first_alignment, second_alignment); + + Alignment expected_alignment(2, "2S2M3I3M"); + EXPECT_EQ(expected_alignment, merged_alignment); + } + + { + Alignment first_alignment(0, "2M3S"); + Alignment second_alignment(2, "4S"); + + Alignment merged_alignment = mergeAlignments(first_alignment, second_alignment); + + Alignment expected_alignment(0, "2M7S"); + EXPECT_EQ(expected_alignment, merged_alignment); + } + + { + + Alignment first_alignment(0, "2S"); + Alignment second_alignment(0, "1S2M"); + + Alignment merged_alignment = mergeAlignments(first_alignment, second_alignment); + + Alignment expected_alignment(0, "3S2M"); + EXPECT_EQ(expected_alignment, merged_alignment); + } +} + +TEST(ScoringAlignment, TypicalAlignment_Scored) +{ + Alignment alignment(3, "2S3M1X2N2D2M3I1M10S"); + const int32_t match_score = 1; + const int32_t mismatch_score = -2; + const int32_t gap_score = -3; + + // 2S 3M 1X 2N 2D 2M 3I 1M 10S + // 2*0 + 3*1 + 1*(-2) + 2*0 + 2*(-3) + 2*1 + 3*(-3) + 1*1 + 10*0 = -11 + const int32_t score = scoreAlignment(alignment, match_score, mismatch_score, gap_score); + + EXPECT_EQ(-11, score); +} + +TEST(PrettyPrintingAlignments, TypicalAlignment_PrettyPrinted) +{ + // ref: CCCTTCCTTAATT---T---------- + // ||| || | + // query: ---TTCGNN--TTGGGTCCCCCCCCCC + string reference = "CCCTTCCTTAATTT"; + string query = "TTCGNNTTGGGTCCCCCCCCCC"; + + Alignment alignment(3, "3M1X2N2D2M3I1M10S"); + + const string alignment_encoding = prettyPrint(alignment, reference, query); + + const string expected_encoding = "TTCCTTAATT---T----------\n" + "||| || | \n" + "TTCGNN--TTGGGTCCCCCCCCCC"; + + ASSERT_EQ(expected_encoding, alignment_encoding); +} diff --git a/thirdparty/graph-tools-master/tests/LinearAlignmentTest.cpp b/thirdparty/graph-tools-master/tests/LinearAlignmentTest.cpp new file mode 100755 index 0000000..1c17060 --- /dev/null +++ b/thirdparty/graph-tools-master/tests/LinearAlignmentTest.cpp @@ -0,0 +1,131 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/LinearAlignment.hh" + +#include "gtest/gtest.h" + +using std::list; +using std::string; + +using namespace graphtools; + +TEST(AlignmentInitialization, TypicalCigarString_AlignmentCreated) +{ + Alignment alignment(3u, "3M1X2N2D2M3I1M10S"); + const list operations = { Operation("3M"), Operation("1X"), Operation("2N"), Operation("2D"), + Operation("2M"), Operation("3I"), Operation("1M"), Operation("10S") }; + + Alignment expected_alignment(3u, operations); + ASSERT_EQ(expected_alignment, alignment); +} + +TEST(GettingAlignmentSpans, TypicalAlignment_QueryAndreferenceSpansObtained) +{ + Alignment alignment(3u, "3M1X2M2D2M3I1M10S"); + EXPECT_EQ(22u, alignment.queryLength()); + EXPECT_EQ(11u, alignment.referenceLength()); +} + +TEST(EncodingAlignment, TypicalAlignment_CigarStringObtained) +{ + const string cigar_string = "3M1X2N2D2M3I1M10S"; + Alignment alignment(3u, cigar_string); + + EXPECT_EQ(cigar_string, alignment.generateCigar()); +} + +TEST(SplittingAlignment, SplitPositionBetweenOperations_PefixAndSuffixAlignments) +{ + // query: -AATTCGTT--TTGGGTCCCCCCCCCC + // ||| || || | + // ref: CCCTTCCNNAATT---T---------- + + const string cigar_string = "2S3M1X2N2D2M3I1M10S"; + Alignment alignment(3, cigar_string); + + Alignment suffix = alignment.splitAtReferencePosition(13); + + Alignment expected_prefix(3, "2S3M1X2N2D2M3I"); + Alignment expected_suffix(13, "1M10S"); + EXPECT_EQ(expected_prefix, alignment); + EXPECT_EQ(expected_suffix, suffix); +} + +TEST(SplittingAlignment, OperationOverlapsSplitPosition_PrefixAndSuffixAlignments) +{ + Alignment alignment(0, "4M1I4M"); + + Alignment suffix = alignment.splitAtReferencePosition(5); + + Alignment expected_prefix(0, "4M1I1M"); + Alignment expected_suffix(5, "3M"); + EXPECT_EQ(alignment, expected_prefix); + EXPECT_EQ(suffix, expected_suffix); +} + +TEST(SplittingAlignment, TypicalAlignments_AlignmentStatsUpdated) +{ + // query: -AATTCGTT--T TGGGTCCCCCCCCCC + // ||| || | | | + // ref: CCCTTCCNNAAT T---T---------- + + Alignment alignment(3, "2S3M1X2M2D2M3I1M10S"); + alignment.splitAtReferencePosition(12); + + EXPECT_EQ(6u, alignment.numMatched()); + EXPECT_EQ(1u, alignment.numMismatched()); + EXPECT_EQ(2u, alignment.numClipped()); + EXPECT_EQ(0u, alignment.numInserted()); + EXPECT_EQ(2u, alignment.numDeleted()); +} + +TEST(SplittingAlignment, InvalidSplitPosition_ExceptionThrown) +{ + Alignment alignment(0, "3M"); + EXPECT_ANY_THROW(alignment.splitAtReferencePosition(0)); + EXPECT_ANY_THROW(alignment.splitAtReferencePosition(3)); + EXPECT_ANY_THROW(alignment.splitAtReferencePosition(4)); +} + +TEST(ReversingAlignment, TypicalAlignment_ReversedAlignment) +{ + // AAC-TCGA + // | || + // TTTTCG-CGCC + Alignment alignment(4, "2S1M1D1I2M1S"); + + const size_t reference_length = 10; + alignment.reverse(reference_length); + + // AGCT-CAA + // || | + // CCGC-GCTTTT + Alignment expected_alignment(2, "1S2M1I1D1M2S"); + EXPECT_EQ(expected_alignment, alignment); +} diff --git a/thirdparty/graph-tools-master/tests/OperationOperationsTest.cpp b/thirdparty/graph-tools-master/tests/OperationOperationsTest.cpp new file mode 100755 index 0000000..894cc61 --- /dev/null +++ b/thirdparty/graph-tools-master/tests/OperationOperationsTest.cpp @@ -0,0 +1,219 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/OperationOperations.hh" + +#include "gtest/gtest.h" + +using namespace graphtools; + +TEST(CheckingConsistency, MatchOperation_ConsistencyChecked) +{ + { + Operation operation("3M"); + EXPECT_TRUE(checkConsistency(operation, "ATC", "ATC")); + } + + { + Operation operation("4M"); + EXPECT_TRUE(checkConsistency(operation, "ATBB", "AtcG")); + } + + { + Operation operation("4M"); + EXPECT_FALSE(checkConsistency(operation, "AYAA", "AAAA")); + } + + { + Operation operation("4M"); + EXPECT_FALSE(checkConsistency(operation, "ATC", "AAAA")); + } + + { + Operation operation("4M"); + EXPECT_FALSE(checkConsistency(operation, "AAA", "AAA")); + } +} + +TEST(CheckingConsistency, MismatchOperation_ConsistencyChecked) +{ + { + Operation operation("2X"); + EXPECT_TRUE(checkConsistency(operation, "TR", "AT")); + } + + { + Operation operation("2X"); + EXPECT_FALSE(checkConsistency(operation, "TT", "AT")); + } + + { + Operation operation("2X"); + EXPECT_FALSE(checkConsistency(operation, "A", "AT")); + } + + { + Operation operation("1X"); + EXPECT_FALSE(checkConsistency(operation, "W", "T")); + } +} + +TEST(CheckingConsistency, InsertionOperation_ConsistencyChecked) +{ + { + Operation operation("4I"); + EXPECT_TRUE(checkConsistency(operation, "", "ATTG")); + } + + { + Operation operation("2I"); + EXPECT_FALSE(checkConsistency(operation, "T", "AA")); + } +} + +TEST(CheckingConsistency, DeletionOperation_ConsistencyChecked) +{ + + { + Operation operation("3D"); + EXPECT_TRUE(checkConsistency(operation, "TRR", "")); + } + + { + Operation operation("4D"); + EXPECT_FALSE(checkConsistency(operation, "", "AAA")); + } + + { + Operation operation("4D"); + EXPECT_FALSE(checkConsistency(operation, "", "")); + } +} + +TEST(CheckingConsistency, MissingBasesOperation_ConsistencyChecked) +{ + + { + Operation operation("3N"); + EXPECT_TRUE(checkConsistency(operation, "AAN", "NNN")); + } + + { + Operation operation("4N"); + EXPECT_FALSE(checkConsistency(operation, "NNN", "NNN")); + } + + { + Operation operation("2N"); + EXPECT_FALSE(checkConsistency(operation, "NT", "NT")); + } + + { + Operation operation("3N"); + EXPECT_FALSE(checkConsistency(operation, "NNN", "NNA")); // Reference N means degenerate base, not missing base + } +} + +TEST(CheckingConsistency, SoftclipOperation_ConsistencyChecked) +{ + + { + Operation operation("2S"); + EXPECT_TRUE(checkConsistency(operation, "", "AA")); + } + + { + Operation operation("2S"); + EXPECT_FALSE(checkConsistency(operation, "", "TTT")); + } + + { + Operation operation("2S"); + EXPECT_FALSE(checkConsistency(operation, "T", "TT")); + } +} + +TEST(SplittingOperations, MatchOperation_Split) +{ + Operation operation("3M"); + OperationPair operation_parts = splitByReferenceLength(operation, 1); + + EXPECT_EQ(Operation("1M"), operation_parts.first); + EXPECT_EQ(Operation("2M"), operation_parts.second); +} + +TEST(SplittingOperations, MismatchOperation_Split) +{ + Operation operation("4X"); + OperationPair operation_parts = splitByReferenceLength(operation, 3); + + EXPECT_EQ(Operation("3X"), operation_parts.first); + EXPECT_EQ(Operation("1X"), operation_parts.second); +} + +TEST(SplittingOperations, MissingBaseOperation_ExceptionThrown) +{ + Operation operation("7N"); + + OperationPair operation_parts = splitByReferenceLength(operation, 4); + + EXPECT_EQ(Operation("4N"), operation_parts.first); + EXPECT_EQ(Operation("3N"), operation_parts.second); +} + +TEST(SplittingOperations, DeletionOperation_Split) +{ + Operation operation("5D"); + OperationPair operation_parts = splitByReferenceLength(operation, 2); + + EXPECT_EQ(Operation("2D"), operation_parts.first); + EXPECT_EQ(Operation("3D"), operation_parts.second); +} + +TEST(SplittingOperations, InsertionOperation_ExceptionThrown) +{ + Operation operation("7I"); + + ASSERT_ANY_THROW(splitByReferenceLength(operation, 2)); +} + +TEST(SplittingOperations, SoftclipOperation_ExceptionThrown) +{ + Operation operation("10S"); + + ASSERT_ANY_THROW(splitByReferenceLength(operation, 2)); +} + +TEST(SplittingOperations, InvalidReferenceLength_ExceptionThrown) +{ + + Operation operation("3M"); + EXPECT_ANY_THROW(splitByReferenceLength(operation, 0)); + EXPECT_ANY_THROW(splitByReferenceLength(operation, 3)); + EXPECT_ANY_THROW(splitByReferenceLength(operation, 4)); +} diff --git a/thirdparty/graph-tools-master/tests/OperationTest.cpp b/thirdparty/graph-tools-master/tests/OperationTest.cpp new file mode 100755 index 0000000..40c346e --- /dev/null +++ b/thirdparty/graph-tools-master/tests/OperationTest.cpp @@ -0,0 +1,101 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/Operation.hh" + +#include "gtest/gtest.h" + +using namespace graphtools; + +TEST(InitializingOperations, TypicalOperations_QueryAndReferenceSpansObtained) +{ + { + Operation operation("3M"); + EXPECT_EQ(OperationType::kMatch, operation.type()); + EXPECT_EQ(3u, operation.queryLength()); + EXPECT_EQ(3u, operation.referenceLength()); + } + { + Operation operation("4X"); + EXPECT_EQ(OperationType::kMismatch, operation.type()); + EXPECT_EQ(4u, operation.queryLength()); + EXPECT_EQ(4u, operation.referenceLength()); + } + { + Operation operation("5D"); + EXPECT_EQ(OperationType::kDeletionFromRef, operation.type()); + EXPECT_EQ(0u, operation.queryLength()); + EXPECT_EQ(5u, operation.referenceLength()); + } + { + Operation operation("7I"); + EXPECT_EQ(OperationType::kInsertionToRef, operation.type()); + EXPECT_EQ(7u, operation.queryLength()); + EXPECT_EQ(0u, operation.referenceLength()); + } + { + Operation operation("10S"); + EXPECT_EQ(OperationType::kSoftclip, operation.type()); + EXPECT_EQ(10u, operation.queryLength()); + EXPECT_EQ(0u, operation.referenceLength()); + } + { + Operation operation("7N"); + EXPECT_EQ(OperationType::kMissingBases, operation.type()); + EXPECT_EQ(7u, operation.queryLength()); + EXPECT_EQ(7u, operation.referenceLength()); + } +} + +TEST(EncodingOperation, TypicalOperations_CigarStringObtained) +{ + { + Operation operation(OperationType::kMatch, 3); + EXPECT_EQ("3M", operation.generateCigar()); + } + { + Operation operation(OperationType::kMismatch, 4); + EXPECT_EQ("4X", operation.generateCigar()); + } + { + Operation operation(OperationType::kDeletionFromRef, 5); + EXPECT_EQ("5D", operation.generateCigar()); + } + { + Operation operation(OperationType::kInsertionToRef, 7); + EXPECT_EQ("7I", operation.generateCigar()); + } + { + Operation operation(OperationType::kSoftclip, 10); + EXPECT_EQ("10S", operation.generateCigar()); + } + { + Operation operation(OperationType::kMissingBases, 7); + EXPECT_EQ("7N", operation.generateCigar()); + } +} diff --git a/thirdparty/graph-tools-master/tests/PathFamilyOperationsTest.cpp b/thirdparty/graph-tools-master/tests/PathFamilyOperationsTest.cpp new file mode 100755 index 0000000..d7ff7f4 --- /dev/null +++ b/thirdparty/graph-tools-master/tests/PathFamilyOperationsTest.cpp @@ -0,0 +1,259 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Felix Schlesinger +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "gtest/gtest.h" + +#include "graphcore/Graph.hh" +#include "graphcore/GraphBuilders.hh" +#include "graphcore/Path.hh" +#include "graphcore/PathFamily.hh" +#include "graphcore/PathFamilyOperations.hh" +#include + +using std::list; +using std::string; + +using namespace graphtools; + +TEST(PathsForPathFamily, GeneratePathsForPathFamily_DisjointPaths) +{ + Graph graph = makeDoubleSwapGraph("AAA", "CCCC", "GGG", "AAAA", "TTTT", "GG", "AA"); + PathFamily family(&graph); + family.addEdge(0, 1); + family.addEdge(1, 3); + family.addEdge(5, 6); + + const list expected_paths{ + Path{ &graph, 0, { 0, 1, 3 }, 4 }, + Path{ &graph, 0, { 5, 6 }, 2 }, + }; + list observed_paths; + ASSERT_TRUE(getMaximalPathsForFamily(family, &observed_paths)); + for (const auto& path : observed_paths) + { + ASSERT_TRUE(family.containsPath(path)); + } + ASSERT_EQ(expected_paths, observed_paths); +} + +TEST(PathsForPathFamily, GeneratePathsForPathFamily_LongPath) +{ + Graph graph = makeDoubleSwapGraph("AAA", "CCCC", "GGG", "AAAA", "TTTT", "GG", "AA"); + PathFamily family(&graph); + family.addEdge(0, 1); + family.addEdge(1, 3); + family.addEdge(3, 4); + family.addEdge(4, 6); + + const list expected_paths{ + Path{ &graph, 0, { 0, 1, 3, 4, 6 }, 2 }, + }; + list observed_paths; + ASSERT_TRUE(getMaximalPathsForFamily(family, &observed_paths)); + for (const auto& path : observed_paths) + { + ASSERT_TRUE(family.containsPath(path)); + } + ASSERT_EQ(expected_paths, observed_paths); +} + +TEST(PathsForPathFamily, GeneratePathsForPathFamily_MultipleExtensions) +{ + Graph graph = makeDoubleSwapGraph("AAA", "CCCC", "GGG", "AAAA", "TTTT", "GG", "AA"); + PathFamily family(&graph); + family.addEdge(1, 3); + family.addEdge(2, 3); + family.addEdge(3, 4); + family.addEdge(3, 5); + family.addEdge(4, 6); + family.addEdge(5, 6); + + const list expected_paths{ + Path{ &graph, 0, { 1, 3, 4, 6 }, 2 }, + Path{ &graph, 0, { 1, 3, 5, 6 }, 2 }, + Path{ &graph, 0, { 2, 3, 4, 6 }, 2 }, + Path{ &graph, 0, { 2, 3, 5, 6 }, 2 }, + }; + list observed_paths; + ASSERT_TRUE(getMaximalPathsForFamily(family, &observed_paths)); + + for (const auto& path : observed_paths) + { + ASSERT_TRUE(family.containsPath(path)); + } + + ASSERT_EQ(expected_paths, observed_paths); +} + +TEST(PathsForPathFamily, GeneratePathsForPathFamily_MultipleExtensionsSingleEdge) +{ + Graph graph(8); + + /* + A E + \ / + C=D + / \ + B F + */ + graph.setNodeName(0, "source"); + graph.setNodeName(1, "A"); + graph.setNodeName(2, "B"); + graph.setNodeName(3, "C"); + graph.setNodeName(4, "D"); + graph.setNodeName(5, "E"); + graph.setNodeName(6, "F"); + graph.setNodeName(7, "sink"); + + graph.setNodeSeq(0, "N"); + graph.setNodeSeq(1, "A"); + graph.setNodeSeq(2, "A"); + graph.setNodeSeq(3, "A"); + graph.setNodeSeq(4, "A"); + graph.setNodeSeq(5, "A"); + graph.setNodeSeq(6, "A"); + graph.setNodeSeq(7, "N"); + + graph.addEdge(0, 1); + graph.addEdge(0, 2); + graph.addEdge(1, 3); + graph.addEdge(2, 3); + graph.addEdge(3, 4); + graph.addEdge(4, 5); + graph.addEdge(4, 6); + graph.addEdge(5, 7); + graph.addEdge(6, 7); + + PathFamily family(&graph); + family.addEdge(3, 4); + + const list expected_paths{ + Path{ &graph, 0, { 3, 4 }, 1 }, + }; + list observed_paths; + ASSERT_TRUE(getMaximalPathsForFamily(family, &observed_paths)); + + for (const auto& path : observed_paths) + { + ASSERT_TRUE(family.containsPath(path)); + } + + ASSERT_EQ(expected_paths, observed_paths); +} + +TEST(PathsForPathFamily, GeneratePathsForPathFamily_LoopGraph) +{ + Graph graph = makeStrGraph("AAA", "TG", "CCC"); + PathFamily family(&graph); + family.addEdge(0, 1); + family.addEdge(1, 1); + family.addEdge(1, 2); + + const list expected_paths{ + Path{ &graph, 0, { 0, 1, 2 }, 3 }, + }; + list observed_paths; + ASSERT_TRUE(getMaximalPathsForFamily(family, &observed_paths, 5)); + for (const auto& path : observed_paths) + { + ASSERT_TRUE(family.containsPath(path)); + } + ASSERT_EQ(expected_paths, observed_paths); +} + +TEST(PathFamilyFromPath, GeneratePathFamilyFromPath_SimplePath) +{ + Graph graph = makeDoubleSwapGraph("A", "C", "T", "A", "G", "C", "T"); + const Path path{ &graph, 0, { 1, 3, 4 }, 0 }; + const PathFamily family = pathToPathFamily(graph, path); + + ASSERT_EQ(2ull, family.edges().size()); + ASSERT_NE(0u, family.edges().count({ 1, 3 })); + ASSERT_NE(0u, family.edges().count({ 3, 4 })); + ASSERT_TRUE(family.containsPath(path)); +} + +TEST(PathFamilyFromGraph, GeneratePathFamilyFromGraph_SimpleGraph) +{ + Graph graph = makeDoubleSwapGraph("A", "C", "T", "A", "G", "T", "C"); + graph.addLabelToEdge(0, 1, "A"); + graph.addLabelToEdge(1, 3, "A"); + graph.addLabelToEdge(3, 5, "A"); + graph.addLabelToEdge(5, 6, "A"); + graph.addLabelToEdge(0, 2, "B"); + graph.addLabelToEdge(2, 3, "B"); + graph.addLabelToEdge(3, 4, "B"); + graph.addLabelToEdge(4, 6, "B"); + + const std::map families = getPathFamiliesFromGraph(graph); + + ASSERT_EQ(2ull, families.size()); + ASSERT_NE(0u, families.count("A")); + ASSERT_NE(0u, families.count("B")); + + const auto a_it = families.find("A"); + const auto b_it = families.find("B"); + ASSERT_EQ(4ull, a_it->second.edges().size()); + ASSERT_EQ(4ull, b_it->second.edges().size()); + + ASSERT_NE(0u, a_it->second.edges().count({ 0, 1 })); + ASSERT_NE(0u, a_it->second.edges().count({ 1, 3 })); + ASSERT_NE(0u, a_it->second.edges().count({ 3, 5 })); + ASSERT_NE(0u, a_it->second.edges().count({ 5, 6 })); + + ASSERT_NE(0u, b_it->second.edges().count({ 0, 2 })); + ASSERT_NE(0u, b_it->second.edges().count({ 2, 3 })); + ASSERT_NE(0u, b_it->second.edges().count({ 3, 4 })); + ASSERT_NE(0u, b_it->second.edges().count({ 4, 6 })); +} + +TEST(PathFamilyFromGraph, GeneratePathFamilyFromGraph_LoopGraph) +{ + Graph graph = makeStrGraph("A", "CT", "G"); + graph.addLabelToEdge(0, 2, "A"); + graph.addLabelToEdge(0, 1, "B"); + graph.addLabelToEdge(1, 1, "B"); + graph.addLabelToEdge(1, 2, "B"); + + const std::map families = getPathFamiliesFromGraph(graph); + + ASSERT_EQ(2ull, families.size()); + ASSERT_NE(0u, families.count("A")); + ASSERT_NE(0u, families.count("B")); + + const auto a_it = families.find("A"); + const auto b_it = families.find("B"); + ASSERT_EQ(1ull, a_it->second.edges().size()); + ASSERT_EQ(3ull, b_it->second.edges().size()); + + ASSERT_NE(0u, a_it->second.edges().count({ 0, 2 })); + + ASSERT_NE(0u, b_it->second.edges().count({ 0, 1 })); + ASSERT_NE(0u, b_it->second.edges().count({ 1, 1 })); + ASSERT_NE(0u, b_it->second.edges().count({ 1, 2 })); +} diff --git a/thirdparty/graph-tools-master/tests/PathFamilyTest.cpp b/thirdparty/graph-tools-master/tests/PathFamilyTest.cpp new file mode 100755 index 0000000..90a7ffd --- /dev/null +++ b/thirdparty/graph-tools-master/tests/PathFamilyTest.cpp @@ -0,0 +1,142 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Felix Schlesinger +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "gtest/gtest.h" + +#include "graphcore/Graph.hh" +#include "graphcore/GraphBuilders.hh" +#include "graphcore/Path.hh" +#include "graphcore/PathFamily.hh" +#include + +using std::string; +using std::vector; + +using namespace graphtools; + +TEST(Creation, AddingEdges_ExpectedSize) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + PathFamily family(&graph); + ASSERT_EQ(0u, family.edges().size()); + family.addEdge(0, 1); + ASSERT_EQ(1u, family.edges().size()); +} + +TEST(Creation, FromLabel_Edgeset) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + graph.addLabelToEdge(0, 2, "foo"); + PathFamily family(&graph, "foo"); + ASSERT_EQ(1u, family.edges().size()); + family.addEdge(1, 2); + family.setLabel("foo"); + PathFamily family2(&graph, "foo"); + ASSERT_EQ(family, family2); +} + +TEST(Creation, CopyConstructor_Independent) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + PathFamily fam1(&graph); + fam1.addEdge(0, 1); + PathFamily fam2(fam1); + ASSERT_EQ(fam1, fam2); + fam1.addEdge(0, 2); + ASSERT_NE(fam1, fam2); +} + +TEST(Paths, ContainsPath_MatchFull) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + PathFamily family(&graph); + family.addEdge(0, 1); + family.addEdge(1, 2); + Path path(&graph, 0, { 0, 1, 2 }, 0); + ASSERT_TRUE(family.containsPath(path)); + Path path2(&graph, 0, { 0, 2 }, 0); + ASSERT_FALSE(family.containsPath(path2)); +} + +TEST(Paths, ContainsPath_MatchPartialPath) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + PathFamily family(&graph); + family.addEdge(0, 1); + family.addEdge(1, 2); + Path path(&graph, 0, { 0, 1 }, 0); + ASSERT_TRUE(family.containsPath(path)); + Path path2(&graph, 0, { 1, 2 }, 0); + ASSERT_TRUE(family.containsPath(path2)); +} + +TEST(Paths, ContainsPath_MatchPartialFamily) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + PathFamily family(&graph); + family.addEdge(0, 1); + Path path(&graph, 0, { 0, 1, 2 }, 0); + ASSERT_TRUE(family.containsPath(path)); + Path path2(&graph, 0, { 1, 2 }, 0); + ASSERT_FALSE(family.containsPath(path2)); +} + +TEST(Paths, ContainsPath_MatchAmbiguous) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + PathFamily family(&graph); + family.addEdge(0, 1); + family.addEdge(0, 2); + Path path2(&graph, 0, { 0, 2 }, 0); + ASSERT_TRUE(family.containsPath(path2)); + Path path(&graph, 0, { 0, 1, 2 }, 0); + ASSERT_FALSE(family.containsPath(path)); +} + +TEST(Compare, AddingEdges_Equality) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + PathFamily family(&graph); + family.addEdge(0, 1); + PathFamily family2(&graph); + family2.addEdge(0, 1); + ASSERT_EQ(family, family2); + family.addEdge(0, 2); + ASSERT_NE(family, family2); +} +TEST(Compare, AddingEdges_Includes) +{ + Graph graph = makeDeletionGraph("AAAA", "TTGG", "TTTT"); + graph.addLabelToEdge(0, 1, "foo"); + PathFamily fam1(&graph, "foo"); + PathFamily fam2(&graph, "foo"); + ASSERT_TRUE(fam1.includes(fam2)); + fam1.addEdge(0, 2); + ASSERT_TRUE(fam1.includes(fam2)); + ASSERT_FALSE(fam2.includes(fam1)); +} \ No newline at end of file diff --git a/thirdparty/graph-tools-master/tests/PathOperationsTest.cpp b/thirdparty/graph-tools-master/tests/PathOperationsTest.cpp new file mode 100755 index 0000000..52387df --- /dev/null +++ b/thirdparty/graph-tools-master/tests/PathOperationsTest.cpp @@ -0,0 +1,684 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphcore/PathOperations.hh" + +#include "gtest/gtest.h" + +#include +#include +#include + +#include "graphcore/Graph.hh" +#include "graphcore/GraphBuilders.hh" +#include "graphcore/Path.hh" + +using std::list; +using std::string; +using std::vector; + +using namespace graphtools; + +TEST(ExtendingPathStarts, TypicalPath_StartExtended) +{ + Graph graph = makeDeletionGraph("AAACC", "TTGGG", "TTAAA"); + + { + Path path(&graph, 4, { 0 }, 4); + list extended_paths = extendPathStart(path, 1); + + list expected_paths = { Path(&graph, 3, { 0 }, 4) }; + ASSERT_EQ(expected_paths, extended_paths); + } + + { + Path path(&graph, 5, { 0, 2 }, 0); + list extended_paths = extendPathStart(path, 2); + + list expected_paths = { Path(&graph, 3, { 0, 2 }, 0) }; + ASSERT_EQ(expected_paths, extended_paths); + } + + { + Path path(&graph, 0, { 2 }, 0); + list extended_paths = extendPathStart(path, 2); + + list expected_paths = { Path(&graph, 3, { 0, 2 }, 0), Path(&graph, 3, { 1, 2 }, 0) }; + ASSERT_EQ(expected_paths, extended_paths); + } +} + +TEST(ExtendingPathEnds, TypicalPath_EndExtended) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + + Path path(&graph, 0, { 0 }, 1); + const list path_extensions = extendPathEnd(path, 6); + + const list expected_path_extensions + = { Path(&graph, 0, { 0, 1, 1 }, 2), Path(&graph, 0, { 0, 1, 2 }, 2), Path(&graph, 0, { 0, 2 }, 4) }; + ASSERT_EQ(expected_path_extensions, path_extensions); +} + +TEST(ExtendingPathsByGivenLength, TypicalPathInStrGraph_PathExtended) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + + Path path(&graph, 0, { 1 }, 2); + const int32_t start_extension = 1; + const int32_t end_extension = 1; + const list path_extensions = extendPath(path, start_extension, end_extension); + + const list expected_path_extensions = { Path(&graph, 2, { 0, 1, 1 }, 1), Path(&graph, 2, { 0, 1, 2 }, 1), + Path(&graph, 1, { 1, 1, 1 }, 1), Path(&graph, 1, { 1, 1, 2 }, 1) }; + ASSERT_EQ(expected_path_extensions, path_extensions); +} + +TEST(ExtendingPathsByGivenLength, TypicalPathInHomopolymerGraph_PathExtended) +{ + Graph graph = makeStrGraph("T", "A", "C"); + + Path path(&graph, 0, { 1 }, 0); + const int32_t start_extension = 3; + const int32_t end_extension = 3; + const list path_extensions = extendPath(path, start_extension, end_extension); + + const list expected_path_extensions + = { Path(&graph, 0, { 0, 1, 1, 1, 1, 1 }, 1), Path(&graph, 0, { 0, 1, 1, 1, 1, 2 }, 1), + Path(&graph, 0, { 1, 1, 1, 1, 1, 1 }, 1), Path(&graph, 0, { 1, 1, 1, 1, 1, 2 }, 1) }; + ASSERT_EQ(expected_path_extensions, path_extensions); +} + +TEST(ExtendingPathsMatching, TypicalPath_ExtendedWithinNode) +{ + Graph graph = makeDeletionGraph("AAACC", "TTGGG", "TTAAA"); + + const Path path(&graph, 2, { 1 }, 2); + const string query = "TTGGG"; + + { + size_t qpos = 2; + const Path extended_path = extendPathStartMatching(path, query, qpos); + const Path expected_path(&graph, 0, { 1 }, 2); + ASSERT_EQ(expected_path, extended_path); + ASSERT_EQ(0ull, qpos); + } + + { + const size_t qpos = 2; + const Path extended_path = extendPathEndMatching(path, query, qpos); + const Path expected_path(&graph, 2, { 1 }, 5); + ASSERT_EQ(expected_path, extended_path); + ASSERT_EQ(2ull, qpos); + } + + { + size_t qpos = 2; + const Path extended_path = extendPathMatching(path, query, qpos); + const Path expected_path(&graph, 0, { 1 }, 5); + ASSERT_EQ(expected_path, extended_path); + ASSERT_EQ(0ull, qpos); + } +} + +TEST(ExtendingPathsMatching, TypicalPath_ExtendedAcrossNodes) +{ + Graph graph = makeDeletionGraph("AAACC", "TTGGG", "TTAAA"); + + const Path path(&graph, 2, { 1 }, 2); + const string query = "CTTGGGT"; + + { + size_t qpos = 3; + const Path extended_path = extendPathStartMatching(path, query, qpos); + const Path expected_path(&graph, 4, { 0, 1 }, 2); + ASSERT_EQ(expected_path, extended_path); + ASSERT_EQ(0ull, qpos); + } + + { + const size_t qpos = 3; + const Path extended_path = extendPathEndMatching(path, query, qpos); + const Path expected_path(&graph, 2, { 1, 2 }, 1); + ASSERT_EQ(expected_path, extended_path); + ASSERT_EQ(3ull, qpos); + } + + { + size_t qpos = 3; + const Path extended_path = extendPathMatching(path, query, qpos); + const Path expected_path(&graph, 4, { 0, 1, 2 }, 1); + ASSERT_EQ(expected_path, extended_path); + ASSERT_EQ(0ull, qpos); + } +} + +TEST(ExtendingPathsMatching, TypicalPath_ExtendedWhenUniqMatch) +{ + { + Graph graph = makeDeletionGraph("AAACC", "TTGGG", "TTAAA"); + const Path path(&graph, 4, { 0 }, 4); + + { + const string query = "CTTGG"; + size_t qpos = 0; + const Path extended_path = extendPathMatching(path, query, qpos); + const Path expected_path(&graph, 4, { 0, 1 }, 4); + ASSERT_EQ(expected_path, extended_path); + ASSERT_EQ(0ull, qpos); + } + + { + const string query = "CTTAA"; + size_t qpos = 0; + const Path extended_path = extendPathMatching(path, query, qpos); + const Path expected_path(&graph, 4, { 0, 2 }, 4); + ASSERT_EQ(expected_path, extended_path); + ASSERT_EQ(qpos, 0ull); + } + } + + { + Graph graph = makeDeletionGraph("AAACC", "ATGCC", "TTAAA"); + const Path path(&graph, 0, { 2 }, 0); + + { + const string query = "TGCCT"; + size_t qpos = 4; + const Path extended_path = extendPathMatching(path, query, qpos); + const Path expected_path(&graph, 1, { 1, 2 }, 1); + ASSERT_EQ(expected_path, extended_path); + ASSERT_EQ(0ull, qpos); + } + + { + const string query = "AACCT"; + size_t qpos = 4; + const Path extended_path = extendPathMatching(path, query, qpos); + const Path expected_path(&graph, 1, { 0, 2 }, 1); + ASSERT_EQ(expected_path, extended_path); + ASSERT_EQ(0ull, qpos); + } + } +} + +TEST(ExtendingPathsMatching, TypicalPath_NotExtendedWhenNonUniqMatch) +{ + { + Graph graph = makeDeletionGraph("AAACC", "TTGGG", "TTAAA"); + const Path path(&graph, 4, { 0 }, 4); + const string query = "CTT"; + size_t qpos = 0; + const Path extended_path = extendPathMatching(path, query, qpos); + const Path expected_path(&graph, 4, { 0 }, 5); + ASSERT_EQ(expected_path, extended_path); + ASSERT_EQ(0ull, qpos); + } + + { + Graph graph = makeDeletionGraph("AAACC", "ATGCC", "TTAAA"); + const Path path(&graph, 0, { 2 }, 0); + const string query = "CCT"; + size_t qpos = 2; + const Path extended_path = extendPathMatching(path, query, qpos); + const Path expected_path(&graph, 0, { 2 }, 1); + ASSERT_EQ(expected_path, extended_path); + ASSERT_EQ(2ull, qpos); + } + + { + Graph graph = makeSwapGraph("AAAG", "AGCC", "A", "GTTT"); + const Path path(&graph, 0, { 0 }, 2); + const string query = "AAAGAG"; + size_t qpos = 0; + const Path extended_path = extendPathEndMatching(path, query, qpos); + const Path expected_path(&graph, 0, { 0 }, 4); + ASSERT_EQ(expected_path, extended_path); + ASSERT_EQ(0ull, qpos); // extending to right doesn't move qpos + } +} + +TEST(SplittingSequenceByPath, SequenceOfDifferentLength_ExceptionRaised) +{ + Graph graph = makeDeletionGraph("AAAACC", "TTTGG", "ATTT"); + Path path(&graph, 3, { 0, 1 }, 2); + const string sequence = "AA"; + EXPECT_ANY_THROW(splitSequenceByPath(path, sequence)); +} + +TEST(SplittingSequenceByPath, SingleNodePath_SequenceSplit) +{ + Graph graph = makeDeletionGraph("AAAACC", "TTTGG", "ATTT"); + Path path(&graph, 1, { 1 }, 4); + const string sequence = "AAT"; + const vector expected_pieces = { sequence }; + EXPECT_EQ(expected_pieces, splitSequenceByPath(path, sequence)); +} + +TEST(SplittingSequenceByPath, MultiNodePath_SequenceSplit) +{ + Graph graph = makeDeletionGraph("AAAACC", "TTTGG", "ATTT"); + { + Path path(&graph, 1, { 0, 1 }, 4); + const string sequence = "AAAAAGGGG"; + const vector expected_pieces = { "AAAAA", "GGGG" }; + EXPECT_EQ(expected_pieces, splitSequenceByPath(path, sequence)); + } + + { + Path path(&graph, 3, { 0, 2 }, 2); + const string sequence = "AAACC"; + const vector expected_pieces = { "AAA", "CC" }; + EXPECT_EQ(expected_pieces, splitSequenceByPath(path, sequence)); + } + + { + Path path(&graph, 3, { 0, 1, 2 }, 2); + const string sequence = "AAAGGGGGCC"; + const vector expected_pieces = { "AAA", "GGGGG", "CC" }; + EXPECT_EQ(expected_pieces, splitSequenceByPath(path, sequence)); + } +} + +TEST(GraphPathOperations, GraphPathsOverlapDetected) +{ + Graph swap = makeSwapGraph("AAAA", "TTTT", "CCCC", "GGGG"); + { + const Path p1(&swap, 0, { 0, 1 }, 3); + const Path p2(&swap, 0, { 1, 3 }, 3); + + ASSERT_TRUE(checkPathPrefixSuffixOverlap(p1, p2)); + ASSERT_TRUE(checkPathPrefixSuffixOverlap(p2, p1)); + + const Path expected_merge(&swap, 0, { 0, 1, 3 }, 3); + ASSERT_EQ(mergePaths(p1, p2), expected_merge); + ASSERT_EQ(mergePaths(p2, p1), expected_merge); + } + + { + const Path p1(&swap, 2, { 0, 1, 3 }, 2); + const Path p2(&swap, 0, { 1, 3 }, 3); + + ASSERT_TRUE(checkPathPrefixSuffixOverlap(p1, p2)); + ASSERT_TRUE(checkPathPrefixSuffixOverlap(p2, p1)); + + const Path expected_merge(&swap, 2, { 0, 1, 3 }, 3); + ASSERT_EQ(mergePaths(p1, p2), expected_merge); + ASSERT_EQ(mergePaths(p2, p1), expected_merge); + } + { + const Path p1(&swap, 2, { 0, 2 }, 1); + const Path p2(&swap, 1, { 2 }, 3); + + ASSERT_TRUE(checkPathPrefixSuffixOverlap(p1, p2)); + ASSERT_TRUE(checkPathPrefixSuffixOverlap(p2, p1)); + + const Path expected_merge(&swap, 2, { 0, 2 }, 3); + ASSERT_EQ(mergePaths(p1, p2), expected_merge); + ASSERT_EQ(mergePaths(p2, p1), expected_merge); + } +} + +TEST(GraphPathOperations, GraphPathsAdjacencyDetected) +{ + Graph graph = makeDoubleSwapGraph("AAAA", "TTTT", "CCCC", "GGGG", "TTTT", "CCCC", "AAAA"); + + { + // p1 ends just before p2 begins + const Path p1(&graph, 0, { 0, 1 }, 1); + const Path p2(&graph, 2, { 1, 3 }, 3); + + ASSERT_TRUE(checkIfPathsAdjacent(p1, p2)); + ASSERT_TRUE(checkIfPathsAdjacent(p2, p1)); + + const Path expected_merge(&graph, 0, { 0, 1, 3 }, 3); + ASSERT_EQ(mergePaths(p1, p2), expected_merge); + ASSERT_EQ(mergePaths(p2, p1), expected_merge); + } + + { + // p1 ends too far before p2 begins + const Path p1(&graph, 0, { 0, 1 }, 0); + const Path p2(&graph, 2, { 1, 3 }, 3); + + ASSERT_FALSE(checkIfPathsAdjacent(p1, p2)); + ASSERT_FALSE(checkIfPathsAdjacent(p2, p1)); + } + { + // p1 ends just before p2 begins + const Path p1(&graph, 0, { 0, 1 }, 3); + const Path p2(&graph, 0, { 3 }, 3); + + ASSERT_TRUE(checkIfPathsAdjacent(p1, p2)); + ASSERT_TRUE(checkIfPathsAdjacent(p2, p1)); + const Path expected_merge(&graph, 0, { 0, 1, 3 }, 3); + ASSERT_EQ(mergePaths(p1, p2), expected_merge); + ASSERT_EQ(mergePaths(p2, p1), expected_merge); + } + + { + // p1 ends too far before p2 begins + const Path p1(&graph, 0, { 0, 1 }, 2); + const Path p2(&graph, 0, { 3 }, 3); + + ASSERT_FALSE(checkIfPathsAdjacent(p1, p2)); + ASSERT_FALSE(checkIfPathsAdjacent(p2, p1)); + } + + { + // p1 ends too far before p2 begins + const Path p1(&graph, 0, { 0, 1 }, 2); + const Path p2(&graph, 0, { 4 }, 3); + + ASSERT_FALSE(checkIfPathsAdjacent(p1, p2)); + ASSERT_FALSE(checkIfPathsAdjacent(p2, p1)); + } +} + +TEST(GraphPathOperations, GraphPathsNoOverlapDetected) +{ + Graph swap = makeSwapGraph("AAAA", "TTTT", "CCCC", "GGGG"); + { + // p1 ends before p2 begins + const Path p1(&swap, 0, { 0, 1 }, 1); + const Path p2(&swap, 2, { 1, 3 }, 3); + + ASSERT_FALSE(checkPathPrefixSuffixOverlap(p1, p2)); + ASSERT_FALSE(checkPathPrefixSuffixOverlap(p2, p1)); + } + + { + // no shared nodes + const Path p1(&swap, 0, { 0 }, 3); + const Path p2(&swap, 2, { 1, 3 }, 3); + + ASSERT_FALSE(checkPathPrefixSuffixOverlap(p1, p2)); + ASSERT_FALSE(checkPathPrefixSuffixOverlap(p2, p1)); + } + + { + // incompatible + const Path p1(&swap, 0, { 0, 1, 3 }, 3); + const Path p2(&swap, 2, { 0, 2, 3 }, 3); + + ASSERT_FALSE(checkPathPrefixSuffixOverlap(p1, p2)); + ASSERT_FALSE(checkPathPrefixSuffixOverlap(p2, p1)); + } + { + // incompatible 2 + const Path p1(&swap, 0, { 0, 1 }, 3); + const Path p2(&swap, 2, { 2, 3 }, 3); + + ASSERT_FALSE(checkPathPrefixSuffixOverlap(p1, p2)); + ASSERT_FALSE(checkPathPrefixSuffixOverlap(p2, p1)); + } +} + +TEST(GraphPathOperations, PathsMergedExhaustively) +{ + Graph swap = makeDoubleSwapGraph("AAAA", "TTTT", "CCCC", "GGGG", "TTTT", "CCCC", "AAAA"); + + { + const Path p0(&swap, 0, { 1, 3 }, 3); + const Path p1(&swap, 0, { 2, 3 }, 3); + const Path p2(&swap, 0, { 3, 4 }, 3); + const Path p3(&swap, 0, { 3, 5 }, 3); + + const list expected_merged_path{ + Path(&swap, 0, { 1, 3, 4 }, 3), + Path(&swap, 0, { 2, 3, 5 }, 3), + Path(&swap, 0, { 2, 3, 4 }, 3), + Path(&swap, 0, { 1, 3, 5 }, 3), + }; + list merged_path{ p0, p1, p2, p3 }; + graphtools::exhaustiveMerge(merged_path); + + ASSERT_EQ(merged_path, expected_merged_path); + } +} + +TEST(GraphPathOperations, IntersectPaths_NoIntersection) +{ + Graph swap = makeDoubleSwapGraph("AAAA", "TTTT", "CCCC", "GGGG", "TTTT", "CCCC", "AAAA"); + + // no shared nodes + { + const Path p0(&swap, 0, { 1 }, 3); + const Path p1(&swap, 0, { 2 }, 3); + + const auto intersection = graphtools::intersectPaths(p0, p1); + ASSERT_TRUE(intersection.empty()); + + const auto intersection_r = graphtools::intersectPaths(p1, p0); + ASSERT_TRUE(intersection_r.empty()); + } + + // one shared node, but no shared sequence + { + const Path p0(&swap, 0, { 1, 3 }, 1); + const Path p1(&swap, 2, { 3, 4 }, 3); + + const auto intersection = graphtools::intersectPaths(p0, p1); + ASSERT_TRUE(intersection.empty()); + + const auto intersection_r = graphtools::intersectPaths(p1, p0); + ASSERT_TRUE(intersection_r.empty()); + } +} + +TEST(GraphPathOperations, IntersectPaths_SimpleIntersection) +{ + Graph swap = makeDoubleSwapGraph("AAAA", "TTTT", "CCCC", "GGGG", "TTTT", "CCCC", "AAAA"); + + // full node is shared + { + const Path p0(&swap, 0, { 1, 3, 5 }, 4); + const Path p1(&swap, 0, { 2, 3, 4 }, 4); + + std::list expected{ + Path(&swap, 0, { 3 }, 4), + + }; + + const auto intersection = graphtools::intersectPaths(p0, p1); + const auto intersection_r = graphtools::intersectPaths(p1, p0); + ASSERT_EQ(expected, intersection); + ASSERT_EQ(expected, intersection_r); + } + + // partial node is shared + { + const Path p0(&swap, 0, { 1, 3 }, 2); + const Path p1(&swap, 1, { 3, 4 }, 3); + + std::list expected{ + Path(&swap, 1, { 3 }, 2), + + }; + + const auto intersection = graphtools::intersectPaths(p0, p1); + const auto intersection_r = graphtools::intersectPaths(p1, p0); + ASSERT_EQ(expected, intersection); + ASSERT_EQ(expected, intersection_r); + } +} + +TEST(GraphPathOperations, IntersectPaths_ComplexIntersection) +{ + Graph swap = makeDoubleSwapGraph("AAAA", "TTTT", "CCCC", "GGGG", "TTTT", "CCCC", "AAAA"); + + // multiple full nodes shared, two resulting paths + { + const Path p0(&swap, 0, { 1, 3, 5, 6 }, 4); + const Path p1(&swap, 0, { 2, 3, 4, 6 }, 4); + + std::list expected{ + Path(&swap, 0, { 3 }, 4), + Path(&swap, 0, { 6 }, 4), + }; + + const auto intersection = graphtools::intersectPaths(p0, p1); + const auto intersection_r = graphtools::intersectPaths(p1, p0); + ASSERT_EQ(expected, intersection); + ASSERT_EQ(expected, intersection_r); + } + + // complex subpath match + { + const Path p0(&swap, 0, { 1, 3, 4 }, 2); + const Path p1(&swap, 0, { 2, 3, 4, 6 }, 3); + + std::list expected{ + Path(&swap, 0, { 3, 4 }, 2), + }; + + const auto intersection = graphtools::intersectPaths(p0, p1); + const auto intersection_r = graphtools::intersectPaths(p1, p0); + ASSERT_EQ(expected, intersection); + ASSERT_EQ(expected, intersection_r); + } + + // complex subpath match + { + const Path p0(&swap, 0, { 1, 3, 4 }, 2); + const Path p1(&swap, 2, { 3, 4, 6 }, 3); + + std::list expected{ + Path(&swap, 2, { 3, 4 }, 2), + }; + + const auto intersection = graphtools::intersectPaths(p0, p1); + const auto intersection_r = graphtools::intersectPaths(p1, p0); + ASSERT_EQ(expected, intersection); + ASSERT_EQ(expected, intersection_r); + } +} + +TEST(GeneratingSubpathForEachNode, TypicalPaths_Split) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + + { + const Path path(&graph, 0, { 0 }, 1); + const list expected_subpaths = { path }; + EXPECT_EQ(expected_subpaths, generateSubpathForEachNode(path)); + } + + { + const Path path(&graph, 3, { 0, 1, 2 }, 0); + const list expected_subpaths + = { Path(&graph, 3, { 0 }, 3), Path(&graph, 0, { 1 }, 2), Path(&graph, 0, { 2 }, 0) }; + EXPECT_EQ(expected_subpaths, generateSubpathForEachNode(path)); + } + + { + const Path path(&graph, 1, { 0, 1, 1, 1, 2 }, 2); + const list expected_subpaths + = { Path(&graph, 1, { 0 }, 3), Path(&graph, 0, { 1 }, 2), Path(&graph, 0, { 1 }, 2), + Path(&graph, 0, { 1 }, 2), Path(&graph, 0, { 2 }, 2) }; + EXPECT_EQ(expected_subpaths, generateSubpathForEachNode(path)); + } +} + +TEST(CheckingIfPathsAreBookended, AdjacentPathsWithEndsOnSameNode_CheckPassed) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + const Path first_path(&graph, 0, { 0, 1 }, 1); + const Path second_path(&graph, 1, { 1, 2 }, 1); + + ASSERT_TRUE(checkIfBookended(first_path, second_path)); +} + +TEST(CheckingIfPathsAreBookended, AdjacentPathsThatEndOnDifferentNodes_CheckPassed) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + const Path first_path(&graph, 0, { 0 }, 3); + const Path second_path(&graph, 0, { 1, 2 }, 1); + + ASSERT_TRUE(checkIfBookended(first_path, second_path)); +} + +TEST(CheckingIfPathsAreBookended, NonadjacentPathsWithEndsOnSameNode_CheckFailed) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + const Path first_path(&graph, 0, { 0, 1 }, 0); + const Path second_path(&graph, 1, { 1, 2 }, 1); + + ASSERT_FALSE(checkIfBookended(first_path, second_path)); +} + +TEST(CheckingIfPathsAreBookended, NonadjacentPathsThatEndOnNeighboringNodes_CheckFailed) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + const Path first_path(&graph, 0, { 0 }, 2); + const Path second_path(&graph, 0, { 1, 2 }, 1); + + ASSERT_FALSE(checkIfBookended(first_path, second_path)); +} + +TEST(CheckingIfPathsAreBookended, NonadjacentPathsThatEndOnNonneighboringNodes_CheckFailed) +{ + Graph graph = makeSwapGraph("TTT", "AT", "CAT", "CCCCC"); + const Path first_path(&graph, 0, { 1 }, 2); + const Path second_path(&graph, 0, { 2 }, 3); + + ASSERT_FALSE(checkIfBookended(first_path, second_path)); +} + +TEST(MergingBookendedPaths, PathsThatAreNotBookended_ExceptionThrown) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + const Path first_path(&graph, 0, { 0 }, 2); + const Path second_path(&graph, 0, { 1, 2 }, 1); + + EXPECT_ANY_THROW(concatenatePaths(first_path, second_path)); +} + +TEST(MergingBookendedPaths, AdjacentPathsWithEndsOnSameNode_Merged) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + const Path first_path(&graph, 0, { 0, 1 }, 1); + const Path second_path(&graph, 1, { 1, 2 }, 1); + + Path merged_path = concatenatePaths(first_path, second_path); + + Path expected_path(&graph, 0, { 0, 1, 2 }, 1); + EXPECT_EQ(expected_path, merged_path); +} + +TEST(MergingBookendedPaths, AdjacentPathsThatEndOnDifferentNodes_Merged) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + const Path first_path(&graph, 0, { 0 }, 3); + const Path second_path(&graph, 0, { 1, 2 }, 1); + + Path merged_path = concatenatePaths(first_path, second_path); + + Path expected_path(&graph, 0, { 0, 1, 2 }, 1); + EXPECT_EQ(expected_path, merged_path); +} diff --git a/thirdparty/graph-tools-master/tests/PathTest.cpp b/thirdparty/graph-tools-master/tests/PathTest.cpp new file mode 100755 index 0000000..7f4c2b0 --- /dev/null +++ b/thirdparty/graph-tools-master/tests/PathTest.cpp @@ -0,0 +1,592 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphcore/Path.hh" + +#include "gtest/gtest.h" + +#include "graphcore/Graph.hh" +#include "graphcore/GraphBuilders.hh" + +using std::list; +using std::string; +using std::vector; + +using namespace graphtools; + +TEST(CreatingPath, WellFormedPath_NoExceptionThrown) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + ASSERT_NO_THROW(Path(&graph, 1, { 0, 1, 1, 2 }, 0)); +} + +TEST(CreatingPath, ZeroLengthPathSpanningAnEdge_NoExceptionThrown) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + ASSERT_NO_THROW(Path(&graph, 3, { 0, 1, 1, 2 }, 0)); +} + +#ifdef _DEBUG + +TEST(CreatingPath, PathWithUnorderedNodes_ExceptionThrown) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + ASSERT_ANY_THROW(Path(&graph, 1, { 2, 1 }, 1)); +} + +TEST(CreatingPath, PathStartingOutsideOfNodesequence_ExceptionThrown) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + ASSERT_ANY_THROW(Path(&graph, 4, { 0, 1, 2 }, 1)); +} + +TEST(CreatingPath, PathEndingOutsideOfNodesequence_ExceptionThrown) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + ASSERT_ANY_THROW(Path(&graph, 3, { 0, 1, 2 }, 10)); +} + +TEST(CreatingPath, PathWithEndBeforeStart_ExceptionThrown) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + ASSERT_ANY_THROW(Path(&graph, 3, { 0 }, 1)); +} + +TEST(CreatingPath, DisconnectedPath_ExceptionThrown) +{ + Graph graph = makeSwapGraph("TTT", "AT", "GG", "CCCCC"); + ASSERT_ANY_THROW(Path(&graph, 0, { 0, 3 }, 0)); +} + +#endif + +TEST(TraversingPath, TypicalPath_NodeIdsTraversed) +{ + Graph graph = makeDeletionGraph("AAAACC", "TTTGG", "ATTT"); + Path path(&graph, 3, { 1, 2 }, 1); + + vector node_ids; + for (NodeId node_id : path) + { + node_ids.push_back(node_id); + } + + ASSERT_EQ(path.nodeIds(), node_ids); +} + +TEST(GettingPathsequence, TypicalPathOnDeletionGraph_SequenceReturned) +{ + Graph graph = makeDeletionGraph("AAAACC", "TTTGG", "ATTT"); + + { + Path path(&graph, 3, { 0 }, 3); + EXPECT_EQ("", path.seq()); + } + + { + Path path(&graph, 3, { 1 }, 4); + EXPECT_EQ("G", path.seq()); + } + + { + Path path(&graph, 3, { 0, 1, 2 }, 1); + EXPECT_EQ("ACCTTTGGA", path.seq()); + } +} + +TEST(GettingPathsequence, TypicalPathOnStrGraph_sequenceReturned) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + Path path(&graph, 1, { 0, 1, 1, 2 }, 0); + EXPECT_EQ("TTATAT", path.seq()); +} + +TEST(CheckingIfPathOverlapsNode, TypicalPath_OverlapChecked) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + Path path(&graph, 1, { 1, 1, 2 }, 0); + EXPECT_TRUE(path.checkOverlapWithNode(1)); + EXPECT_TRUE(path.checkOverlapWithNode(2)); + EXPECT_FALSE(path.checkOverlapWithNode(0)); +} + +TEST(GettingPathBoundsOnNodeByIndex, TypicalPath_BoundsComputed) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + + { + Path path(&graph, 1, { 0 }, 2); + EXPECT_EQ(1, path.getStartPositionOnNodeByIndex(0)); + EXPECT_EQ(2, path.getEndPositionOnNodeByIndex(0)); + } + + { + Path path(&graph, 1, { 1, 1, 2 }, 3); + EXPECT_EQ(1, path.getStartPositionOnNodeByIndex(0)); + EXPECT_EQ(2, path.getEndPositionOnNodeByIndex(0)); + + EXPECT_EQ(0, path.getStartPositionOnNodeByIndex(1)); + EXPECT_EQ(2, path.getEndPositionOnNodeByIndex(1)); + + EXPECT_EQ(0, path.getStartPositionOnNodeByIndex(2)); + EXPECT_EQ(3, path.getEndPositionOnNodeByIndex(2)); + + EXPECT_ANY_THROW(path.getStartPositionOnNodeByIndex(-1)); + EXPECT_ANY_THROW(path.getEndPositionOnNodeByIndex(-1)); + + EXPECT_ANY_THROW(path.getStartPositionOnNodeByIndex(3)); + EXPECT_ANY_THROW(path.getEndPositionOnNodeByIndex(3)); + } +} + +TEST(GettingLengthOfPathOverEachNode, TypicalPathOnStrGraph_LengthReturned) +{ + { + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + + Path path(&graph, 2, { 0, 1, 1 }, 0); + + EXPECT_EQ(1ul, path.getNodeOverlapLengthByIndex(0)); + EXPECT_EQ(2ul, path.getNodeOverlapLengthByIndex(1)); + EXPECT_EQ(0ul, path.getNodeOverlapLengthByIndex(2)); + } + + { + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + + Path path(&graph, 3, { 0, 1, 1, 2 }, 5); + + EXPECT_EQ(0ul, path.getNodeOverlapLengthByIndex(0)); + EXPECT_EQ(2ul, path.getNodeOverlapLengthByIndex(1)); + EXPECT_EQ(2ul, path.getNodeOverlapLengthByIndex(2)); + EXPECT_EQ(5ul, path.getNodeOverlapLengthByIndex(3)); + } +} + +TEST(GettingLengthOfPathOverEachNode, IndexOutOfBounds_ExceptionRaised) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + + Path path(&graph, 2, { 0, 1, 1 }, 0); + + EXPECT_ANY_THROW(path.getNodeOverlapLengthByIndex(-1)); + EXPECT_ANY_THROW(path.getNodeOverlapLengthByIndex(3)); +} + +TEST(GettingPathLength, TypicalPathOnStrGraph_LengthReturned) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + + { + Path path(&graph, 2, { 0 }, 2); + EXPECT_EQ(0ul, path.length()); + } + + { + Path path(&graph, 0, { 1 }, 1); + EXPECT_EQ(1ul, path.length()); + } + + { + Path path(&graph, 2, { 0, 1, 1 }, 0); + EXPECT_EQ(3ul, path.length()); + } + + { + Path path(&graph, 3, { 0, 1, 1 }, 0); + EXPECT_EQ(2ul, path.length()); + } +} + +TEST(GettingPathsequenceOnNode, TypicalPathOnStrGraph_sequenceReturned) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + + { + Path path(&graph, 1, { 0, 1, 1, 2 }, 0); + EXPECT_EQ("TT", path.getNodeSeq(0)); + EXPECT_EQ("AT", path.getNodeSeq(1)); + EXPECT_EQ("AT", path.getNodeSeq(2)); + EXPECT_EQ("", path.getNodeSeq(3)); + } + + { + Path path(&graph, 1, { 1, 1 }, 1); + EXPECT_EQ("T", path.getNodeSeq(0)); + EXPECT_EQ("A", path.getNodeSeq(1)); + } +} + +TEST(EncodingPaths, TypicalPath_EncodedAsString) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + + { + Path path(&graph, 0, { 0 }, 1); + ASSERT_EQ("(0@0)-(0@1)", path.encode()); + } + + { + Path path(&graph, 1, { 0, 1, 1, 2 }, 0); + ASSERT_EQ("(0@1)-(1)-(1)-(2@0)", path.encode()); + } +} + +TEST(MovePathAlongNode, TypicalPath_StartPositionMoved) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + Path shorter_path(&graph, 3, { 0, 1 }, 1); + Path longer_path(&graph, 0, { 0, 1 }, 1); + + { + Path path(shorter_path); + path.shiftStartAlongNode(3); + EXPECT_EQ(longer_path, path); + } + + { + Path path(longer_path); + path.shiftStartAlongNode(-3); + EXPECT_EQ(shorter_path, path); + } +} + +TEST(MovePathAlongNode, TypicalPath_EndPositionMoved) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + Path shorter_path(&graph, 1, { 0, 1, 1 }, 0); + Path longer_path(&graph, 1, { 0, 1, 1 }, 1); + + { + Path path(shorter_path); + path.shiftEndAlongNode(1); + EXPECT_EQ(longer_path, path); + } + + { + Path path(longer_path); + path.shiftEndAlongNode(-1); + EXPECT_EQ(shorter_path, path); + } +} + +TEST(MovePathAlongNode, ExtensionPastNodeBoundaries_ExceptionRaised) +{ + Graph graph = makeStrGraph("TTT", "AT", "CCCCC"); + + { + Path path(&graph, 2, { 0, 1 }, 1); + EXPECT_ANY_THROW(path.shiftStartAlongNode(3)); + } + + { + Path path(&graph, 2, { 0, 1 }, 1); + EXPECT_ANY_THROW(path.shiftStartAlongNode(-2)); + } + + { + Path path(&graph, 2, { 0, 1 }, 1); + EXPECT_ANY_THROW(path.shiftEndAlongNode(2)); + } + + { + Path path(&graph, 2, { 0, 1 }, 1); + EXPECT_ANY_THROW(path.shiftEndAlongNode(-2)); + } +} + +TEST(ExtendingPathToNode, TypicalPathInSwapGraph_StartPositionMoved) +{ + Graph graph = makeSwapGraph("TTT", "AT", "GG", "CCCCC"); + + { + Path path(&graph, 1, { 1, 3 }, 2); + path.extendStartToNode(0); + Path expected_path(&graph, 3, { 0, 1, 3 }, 2); + EXPECT_EQ(expected_path, path); + } + + { + Path path(&graph, 1, { 1, 3 }, 2); + path.removeStartNode(); + Path expected_path(&graph, 0, { 3 }, 2); + EXPECT_EQ(expected_path, path); + } + + { + Path path(&graph, 1, { 1, 3 }, 2); + path.extendStartToIncludeNode(0); + Path expected_path(&graph, 0, { 0, 1, 3 }, 2); + EXPECT_EQ(expected_path, path); + } +} + +TEST(ExtendingPathToNode, TypicalPathInSwapGraph_EndPositionMoved) +{ + Graph graph = makeSwapGraph("TTT", "AT", "GG", "CCCCC"); + + { + Path path(&graph, 1, { 0, 2 }, 1); + path.extendEndToNode(3); + Path expected_path(&graph, 1, { 0, 2, 3 }, 0); + EXPECT_EQ(expected_path, path); + } + + { + Path path(&graph, 1, { 0, 2 }, 1); + path.removeEndNode(); + Path expected_path(&graph, 1, { 0 }, 3); + EXPECT_EQ(expected_path, path); + } + + { + Path path(&graph, 1, { 0, 2 }, 1); + path.extendEndToIncludeNode(3); + Path expected_path(&graph, 1, { 0, 2, 3 }, 5); + EXPECT_EQ(expected_path, path); + } +} + +TEST(ExtendingPathToNode, ExtendingPathToNonadjacentNode_ExceptionThrown) +{ + Graph graph = makeSwapGraph("TTT", "AT", "GG", "CCCCC"); + + { + Path path(&graph, 1, { 2, 3 }, 1); + EXPECT_ANY_THROW(path.extendStartToNode(1)); + } + + { + Path path(&graph, 1, { 0 }, 2); + EXPECT_ANY_THROW(path.extendEndToNode(3)); + } +} + +TEST(RemovingZeroLengthStarts, TypicalPaths_StartRemovedIfAppropriate) +{ + Graph graph = makeStrGraph("ATAT", "C", "CCTT"); + + { + Path path(&graph, 4, { 0, 1, 2 }, 2); + path.removeZeroLengthStart(); + + Path expected_path(&graph, 0, { 1, 2 }, 2); + + ASSERT_EQ(expected_path, path); + } + + { + Path path(&graph, 3, { 0, 1, 2 }, 2); + path.removeZeroLengthStart(); + + Path expected_path(&graph, 3, { 0, 1, 2 }, 2); + + ASSERT_EQ(expected_path, path); + } + + { + Path path(&graph, 4, { 0 }, 4); + path.removeZeroLengthStart(); + + Path expected_path(&graph, 4, { 0 }, 4); + + ASSERT_EQ(expected_path, path); + } +} + +TEST(RemovingZeroLengthEnds, TypicalPaths_EndRemovedIfAppropriate) +{ + Graph graph = makeStrGraph("ATAT", "C", "CCTT"); + + { + Path path(&graph, 0, { 0, 1, 2 }, 0); + path.removeZeroLengthEnd(); + + Path expected_path(&graph, 0, { 0, 1 }, 1); + + ASSERT_EQ(expected_path, path); + } + + { + Path path(&graph, 0, { 0, 1, 2 }, 1); + path.removeZeroLengthEnd(); + + Path expected_path(&graph, 0, { 0, 1, 2 }, 1); + + ASSERT_EQ(expected_path, path); + } + + { + Path path(&graph, 4, { 0 }, 4); + path.removeZeroLengthEnd(); + + Path expected_path(&graph, 4, { 0 }, 4); + + ASSERT_EQ(expected_path, path); + } +} + +TEST(ShrinkingStartOfPath, TypicalPathInStrGraph_StartShrank) +{ + Graph graph = makeStrGraph("ATAT", "C", "CCTT"); + + { + Path path(&graph, 2, { 0, 1, 2 }, 2); + path.shrinkStartBy(2); + + Path expected_path(&graph, 0, { 1, 2 }, 2); + ASSERT_EQ(expected_path, path); + } + + { + Path path(&graph, 2, { 0, 1 }, 1); + path.shrinkStartBy(3); + + Path expected_path(&graph, 1, { 1 }, 1); + ASSERT_EQ(expected_path, path); + } + + { + Path path(&graph, 4, { 0, 1 }, 1); + path.shrinkStartBy(1); + + Path expected_path(&graph, 1, { 1 }, 1); + ASSERT_EQ(expected_path, path); + } + + { + Path path(&graph, 4, { 0, 1 }, 1); + path.shrinkStartBy(0); + + Path expected_path(&graph, 0, { 1 }, 1); + ASSERT_EQ(expected_path, path); + } +} + +TEST(ShrinkingEndOfPath, TypicalPathInStrGraph_EndShrank) +{ + Graph graph = makeStrGraph("ATAT", "C", "CCTT"); + + { + Path path(&graph, 2, { 0, 1, 2 }, 2); + path.shrinkEndBy(3); + + Path expected_path(&graph, 2, { 0 }, 4); + ASSERT_EQ(expected_path, path); + } + + { + Path path(&graph, 0, { 1, 2 }, 2); + path.shrinkEndBy(3); + + Path expected_path(&graph, 0, { 1 }, 0); + ASSERT_EQ(expected_path, path); + } + + { + Path path(&graph, 0, { 1, 2 }, 0); + path.shrinkEndBy(1); + + Path expected_path(&graph, 0, { 1 }, 0); + ASSERT_EQ(expected_path, path); + } + + { + Path path(&graph, 0, { 1, 2 }, 0); + path.shrinkEndBy(0); + + Path expected_path(&graph, 0, { 1 }, 1); + ASSERT_EQ(expected_path, path); + } +} + +TEST(ShrinkingPathEnds, PathWithLoop_PathShrank) +{ + Graph graph = makeStrGraph("ATA", "CG", "TATTTTTTTTT"); + + Path path(&graph, 1, { 0, 1, 1, 1, 2 }, 3); + path.shrinkEndBy(5); + + Path expected_path(&graph, 1, { 0, 1, 1 }, 2); + ASSERT_EQ(expected_path, path); +} + +TEST(ShrinkingPathsByGivenLength, TypicalPathInStrGraph_PathShrank) +{ + Graph graph = makeStrGraph("TTT", "AC", "CCC"); + + Path path(&graph, 1, { 0, 1, 1, 2 }, 2); + const int32_t start_shrink_len = 5; + const int32_t end_shrink_len = 3; + path.shrinkBy(start_shrink_len, end_shrink_len); + + const Path expected_path(&graph, 1, { 1 }, 1); + ASSERT_EQ(expected_path, path); +} + +TEST(ComputingPathDistance, DistanceFromStart_DistanceReturned) +{ + Graph graph = makeDeletionGraph("TTT", "AC", "CCC"); + + Path path(&graph, 1, { 0, 1, 2 }, 2); + ASSERT_EQ(0, path.getDistanceFromPathStart(0, 1)); + ASSERT_EQ(4, path.getDistanceFromPathStart(2, 0)); + ASSERT_EQ(3, path.getDistanceFromPathStart(1, 1)); +} + +TEST(ComputingPathDistance, DistanceFromStart_ExceptionWhenNodeNotInPath) +{ + Graph graph = makeDeletionGraph("TTT", "AC", "CCCC"); + + Path path(&graph, 1, { 0, 2 }, 2); + + ASSERT_THROW(path.getDistanceFromPathStart(0, 0), std::logic_error); + ASSERT_THROW(path.getDistanceFromPathStart(1, 0), std::logic_error); + ASSERT_THROW(path.getDistanceFromPathStart(2, 3), std::logic_error); +} + +TEST(ComparingPaths, TypicalPaths_Compared) +{ + Graph graph = makeStrGraph("TTT", "AC", "CCC"); + + { + Path path_a(&graph, 1, { 0, 1, 2 }, 1); + Path path_b(&graph, 1, { 0, 1, 2 }, 2); + EXPECT_TRUE(path_a < path_b); + EXPECT_FALSE(path_b < path_a); + EXPECT_FALSE(path_a == path_b); + } + + { + Path path_a(&graph, 0, { 0, 1, 1 }, 1); + Path path_b(&graph, 0, { 0, 1, 2 }, 1); + EXPECT_TRUE(path_a < path_b); + EXPECT_FALSE(path_b < path_a); + EXPECT_FALSE(path_a == path_b); + } +} diff --git a/thirdparty/graph-tools-master/tests/PinnedAlignerTest.cpp b/thirdparty/graph-tools-master/tests/PinnedAlignerTest.cpp new file mode 100755 index 0000000..e501bc5 --- /dev/null +++ b/thirdparty/graph-tools-master/tests/PinnedAlignerTest.cpp @@ -0,0 +1,110 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/PinnedAligner.hh" + +#include "gtest/gtest.h" + +using std::string; + +using namespace graphtools; + +TEST(PopulatingTracebackMatrix, CoreBases_TracebackMatrixPopulated) +{ + const int32_t match_score = 1; + const int32_t mismatch_score = -1; + const int32_t gap_penalty = -2; + PinnedAligner aligner(match_score, mismatch_score, gap_penalty); + + const string query = "AAAC"; + const string reference = "AGC"; + TracebackMatrix matrix = aligner.populateTracebackMatrix(reference, query); + + TracebackMatrix expected_matrix("S/0 D/-2 D/-4 D/-6\n" + "I/-2 M/1 D/-1 D/-3\n" + "I/-4 M/-1 X/0 X/-2\n" + "I/-6 M/-3 X/-2 X/-1\n" + "I/-8 I/-5 X/-4 M/-1"); + EXPECT_EQ(expected_matrix, matrix); +} + +TEST(PerformingPrefixAlignment, CoreBases_Aligned) +{ + // query: TAACTTTTGGG + // | ||||| + // reference: TG-CTTTTAA + + const string query = "TAACTTTTGGG"; + const string reference = "TGCTTTTAA"; + + PinnedAligner aligner(1, -1, -2); + Alignment alignment = aligner.prefixAlign(reference, query); + + Alignment expected_alignment(0, "1M1I1X5M3S"); + EXPECT_EQ(expected_alignment, alignment); +} + +TEST(PerformingPrefixAlignment, NoBasesAlign_SoftclipAlignment) +{ + const string query = "AAAAA"; + const string reference = "TGCTTTT"; + + PinnedAligner aligner(1, -1, -2); + Alignment alignment = aligner.prefixAlign(reference, query); + + Alignment expected_alignment(0, "5S"); + EXPECT_EQ(expected_alignment, alignment); +} + +TEST(PerformingSuffixAlignment, CoreBases_Aligned) +{ + // TCACG-GAGA + // ||| ||| + // TACGAGAG- + + const string query = "TCACGGAGA"; + const string reference = "TACGAGAG"; + + PinnedAligner aligner(5, -4, -8); + Alignment alignment = aligner.suffixAlign(reference, query); + + Alignment expected_alignment(1, "2S3M1D3M1I"); + EXPECT_EQ(expected_alignment, alignment); +} + +TEST(PerformingSuffixAlignment, NoBasesAlign_SoftclipAlignment) +{ + const string query = "CGCGCG"; + const string reference = "TATATATA"; + + PinnedAligner aligner(5, -4, -8); + Alignment alignment = aligner.suffixAlign(reference, query); + + Alignment expected_alignment(8, "6S"); + EXPECT_EQ(expected_alignment, alignment); +} diff --git a/thirdparty/graph-tools-master/tests/PinnedDagAlignerTest.cpp b/thirdparty/graph-tools-master/tests/PinnedDagAlignerTest.cpp new file mode 100755 index 0000000..1ea7c05 --- /dev/null +++ b/thirdparty/graph-tools-master/tests/PinnedDagAlignerTest.cpp @@ -0,0 +1,83 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Roman Petrovski +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include + +#include "graphalign/DagAlignerAffine.hh" +#include "graphalign/GappedAligner.hh" +#include "graphalign/PinnedDagAligner.hh" +#include "graphcore/Graph.hh" +#include "graphcore/GraphBuilders.hh" +#include "graphcore/Path.hh" + +#include "gtest/gtest.h" + +using std::string; +using namespace testing; +using namespace graphtools; +using namespace graphalign; +using namespace graphalign::dagAligner; + +template std::string toString(const T& obj) +{ + std::stringstream ss; + ss << obj; + return ss.str(); +} + +TEST(SimpleGraph, AlignSuffix) +{ + Graph graph = makeSwapGraph("AAAA", "C", "T", "GGGG"); + graph.addEdge(1, 1); + Path seed( + &graph, 1, + { + 0, + }, + 3); + PinnedDagAligner dag_pinned_aligner(1, -1, 0, -2); + int32_t top_dag_score = INT32_MIN; + std::list> res = dag_pinned_aligner.prefixAlign(seed, "ACGG", 8, top_dag_score); + EXPECT_EQ(4, top_dag_score); + EXPECT_EQ("(0@1)-(1)-(3@2)", toString(res.front().first)); + EXPECT_EQ("4M", res.front().second.generateCigar()); +} + +TEST(SimpleGraph, AlignPrefix) +{ + Graph graph = makeSwapGraph("AAAA", "C", "T", "GGGG"); + graph.addEdge(1, 1); + Path seed(&graph, 0, { 2, 3 }, 2); + PinnedDagAligner dag_pinned_aligner(1, -1, 0, -2); + int32_t top_dag_score = INT32_MIN; + std::list> res = dag_pinned_aligner.suffixAlign(seed, "AA", 8, top_dag_score); + EXPECT_EQ(2, top_dag_score); + EXPECT_EQ("(0@2)-(2)-(3@2)", toString(res.front().first)); + EXPECT_EQ("2M", res.front().second.generateCigar()); +} diff --git a/thirdparty/graph-tools-master/tests/SequenceOperationsTest.cpp b/thirdparty/graph-tools-master/tests/SequenceOperationsTest.cpp new file mode 100755 index 0000000..71d86ea --- /dev/null +++ b/thirdparty/graph-tools-master/tests/SequenceOperationsTest.cpp @@ -0,0 +1,100 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphutils/SequenceOperations.hh" + +#include +#include +#include + +#include "gtest/gtest.h" + +using std::map; +using std::string; +using std::vector; + +using namespace graphtools; + +TEST(CheckingSequenceComposition, TypicalSequences_CompositionDetermined) +{ + const string reference_nucleotide_sequence = "ACTG"; + const string reference_sequence = "ACWG"; + const string nonreference_sequence = "ZZZZ"; + + EXPECT_TRUE(checkIfNucleotideReferenceSequence(reference_nucleotide_sequence)); + EXPECT_FALSE(checkIfNucleotideReferenceSequence(reference_sequence)); + + EXPECT_TRUE(checkIfReferenceSequence(reference_nucleotide_sequence)); + EXPECT_TRUE(checkIfReferenceSequence(reference_sequence)); + EXPECT_FALSE(checkIfReferenceSequence(nonreference_sequence)); +} + +TEST(ExpandingDegenerateSymbols, TypicalSymbol_SymbolExpanded) +{ + map kSymbolExpansion + = { { 'A', "A" }, { 'C', "C" }, { 'T', "T" }, { 'G', "G" }, { 'R', "AG" }, { 'Y', "CT" }, + { 'K', "GT" }, { 'M', "AC" }, { 'S', "CG" }, { 'W', "AT" }, { 'B', "CGT" }, { 'D', "AGT" }, + { 'H', "ACT" }, { 'V', "ACG" }, { 'N', "ACGT" }, { 'X', "X" } }; + + for (const auto& symbol_expansion : kSymbolExpansion) + { + EXPECT_EQ(symbol_expansion.second, expandReferenceSymbol(symbol_expansion.first)); + } +} + +TEST(ExpandingDegenerateSymbols, NonReferenceSymbol_ExceptionThrown) { ASSERT_ANY_THROW(expandReferenceSymbol('a')); } + +TEST(ExpandingDegenerateSequences, SequenceWithDegenerateBases_SequenceExpanded) +{ + string sequence = "RAK"; + const vector expected_expansion = { "AAG", "GAG", "AAT", "GAT" }; + vector observed_expansion = { "AAG", "GAG", "AAT", "GAT" }; + expandReferenceSequence(sequence, observed_expansion); + ASSERT_EQ(expected_expansion, observed_expansion); +} + +TEST(SplittingStrings, WordsDelimitedBySpaces_StringVector) +{ + const string composite_string = "abc /+= ##"; + const vector expected_words = { "abc", "/+=", "##" }; + ASSERT_EQ(expected_words, splitStringByWhitespace(composite_string)); +} + +TEST(SplittingStrings, WordsDelimitedBySlashes_StringVector) +{ + const string string_with_words = "a/b/cd"; + const vector expected_words = { "a", "b", "cd" }; + ASSERT_EQ(expected_words, splitStringByDelimiter(string_with_words, '/')); +} + +TEST(ReverseComplementingSequences, TypicalQueryAndReferenceSequences_ReverseComplemented) +{ + EXPECT_EQ("AAGGCGAT", reverseComplement("ATCGCCTT")); + EXPECT_EQ("aaggcgat", reverseComplement("atcgcctt")); + EXPECT_EQ("RYKMSWBDHVN", reverseComplement("NBDHVWSKMRY")); +} diff --git a/thirdparty/graph-tools-master/tests/TracebackMatrixTest.cpp b/thirdparty/graph-tools-master/tests/TracebackMatrixTest.cpp new file mode 100755 index 0000000..8d2876c --- /dev/null +++ b/thirdparty/graph-tools-master/tests/TracebackMatrixTest.cpp @@ -0,0 +1,92 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/TracebackMatrix.hh" + +#include + +#include "gtest/gtest.h" + +using std::string; + +using namespace graphtools; + +TEST(TracebackMatrixInitialization, DefaultInitialization_UnirectedMatrixOfZeros) +{ + TracebackMatrix traceback_matrix(2, 3); + + for (size_t row_index = 0; row_index != 2; ++row_index) + { + for (size_t col_index = 0; col_index != 3; ++col_index) + { + EXPECT_EQ(INT32_MIN, traceback_matrix.score(row_index, col_index)); + EXPECT_EQ(TracebackStep::kStop, traceback_matrix.tracebackStep(row_index, col_index)); + } + } +} + +TEST(TracebackMatrixInitialization, TypicalEncoding_MatrixInitialized) +{ + const std::string encoding = "S/0 D/-2 D/-4\n" + "I/0 M/-1 I/-4"; + + TracebackMatrix traceback_matrix(encoding); + + TracebackMatrix expected_matrix(2, 3); + expected_matrix.setScore(0, 0, 0); + expected_matrix.setTracebackStep(0, 0, TracebackStep::kStop); + + expected_matrix.setScore(0, 1, -2); + expected_matrix.setTracebackStep(0, 1, TracebackStep::kLeft); + + expected_matrix.setScore(0, 2, -4); + expected_matrix.setTracebackStep(0, 2, TracebackStep::kLeft); + + expected_matrix.setScore(1, 0, 0); + expected_matrix.setTracebackStep(1, 0, TracebackStep::kTop); + + expected_matrix.setScore(1, 1, -1); + expected_matrix.setTracebackStep(1, 1, TracebackStep::kDiagonalMatch); + + expected_matrix.setScore(1, 2, -4); + expected_matrix.setTracebackStep(1, 2, TracebackStep::kTop); + + ASSERT_EQ(expected_matrix, traceback_matrix); +} + +TEST(LocatingTopScoringCell, TypicalMatrix_CellLocated) +{ + TracebackMatrix matrix("S/0 S/2 S/10\n" + "S/-1 S/3 S/-1"); + + size_t row_index; + size_t col_index; + matrix.locateTopScoringCell(row_index, col_index); + EXPECT_EQ(0u, row_index); + EXPECT_EQ(2u, col_index); +} diff --git a/thirdparty/graph-tools-master/tests/TracebackRunnerTest.cpp b/thirdparty/graph-tools-master/tests/TracebackRunnerTest.cpp new file mode 100755 index 0000000..68bb089 --- /dev/null +++ b/thirdparty/graph-tools-master/tests/TracebackRunnerTest.cpp @@ -0,0 +1,78 @@ +// +// GraphTools library +// Copyright (c) 2018 Illumina, Inc. +// All rights reserved. +// +// Author: Egor Dolzhenko +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: + +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "graphalign/TracebackRunner.hh" + +#include "gtest/gtest.h" + +using std::string; + +using namespace graphtools; + +TEST(PerformingTraceback, NeedlemanWunschMatrixFromCoreBaseAlignment_Traced) +{ + TracebackMatrix matrix("S/0 D/-2 D/-4 D/-6\n" + "I/-2 M/1 D/-1 D/-3\n" + "I/-4 M/-1 X/0 M/-2\n" + "I/-6 M/-3 I/-2 M/-1\n" + "I/-8 I/-5 M/-4 M/-1"); + + const string query = "AAAC"; + const string reference = "AGC"; + + TracebackRunner traceback_runner(matrix); + Alignment alignment = traceback_runner.runTraceback(4, 3); + + Alignment expected_alignment(0, "1M1X1I1M"); + EXPECT_EQ(expected_alignment, alignment); +} + +TEST(PerformingTraceback, LocalAlignmentOfCoreBases_Traced) +{ + // GGAT-CGAA + // || | + // CATAC + TracebackMatrix matrix("S/0 S/0 S/0 S/0 S/0 S/0\n" + "S/0 S/0 S/0 S/0 S/0 S/0\n" + "S/0 S/0 S/0 S/0 S/0 S/0\n" + "S/0 S/0 M/5 D/1 M/5 D/1\n" + "S/0 S/0 I/1 M/10 D/6 D/2\n" + "S/0 M/5 D/1 I/6 M/7 M/11\n" + "S/0 I/1 M/2 I/2 M/3 I/7\n" + "S/0 S/0 M/5 D/1 M/7 I/3\n" + "S/0 S/0 M/5 M/2 M/2 M/4"); + + const string query = "GGATCGAA"; + const string reference = "CATAC"; + + TracebackRunner traceback_runner(matrix); + Alignment alignment = traceback_runner.runTraceback(5, 5); + + Alignment expected_alignment(1, "2S2M1D1M3S"); + EXPECT_EQ(expected_alignment, alignment); +} diff --git a/thirdparty/intervaltree/IntervalTree.h b/thirdparty/intervaltree/IntervalTree.h new file mode 100755 index 0000000..1bf2c20 --- /dev/null +++ b/thirdparty/intervaltree/IntervalTree.h @@ -0,0 +1,337 @@ +#ifndef __INTERVAL_TREE_H +#define __INTERVAL_TREE_H + +#include +#include +#include +#include +#include + +template +class Interval { +public: + Scalar start; + Scalar stop; + Value value; + Interval(const Scalar& s, const Scalar& e, const Value& v) + : start(std::min(s, e)) + , stop(std::max(s, e)) + , value(v) + {} +}; + +template +Value intervalStart(const Interval& i) { + return i.start; +} + +template +Value intervalStop(const Interval& i) { + return i.stop; +} + +template +std::ostream& operator<<(std::ostream& out, const Interval& i) { + out << "Interval(" << i.start << ", " << i.stop << "): " << i.value; + return out; +} + +template +class IntervalTree { +public: + typedef Interval interval; + typedef std::vector interval_vector; + + + struct IntervalStartCmp { + bool operator()(const interval& a, const interval& b) { + return a.start < b.start; + } + }; + + struct IntervalStopCmp { + bool operator()(const interval& a, const interval& b) { + return a.stop < b.stop; + } + }; + + IntervalTree() + : left(nullptr) + , right(nullptr) + , center(0) + {} + + ~IntervalTree() = default; + + std::unique_ptr clone() const { + return std::unique_ptr(new IntervalTree(*this)); + } + + IntervalTree(const IntervalTree& other) + : intervals(other.intervals), + left(other.left ? other.left->clone() : nullptr), + right(other.right ? other.right->clone() : nullptr), + center(other.center) + {} + + IntervalTree& operator=(IntervalTree&&) = default; + IntervalTree(IntervalTree&&) = default; + + IntervalTree& operator=(const IntervalTree& other) { + center = other.center; + intervals = other.intervals; + left = other.left ? other.left->clone() : nullptr; + right = other.right ? other.right->clone() : nullptr; + return *this; + } + + IntervalTree( + interval_vector&& ivals, + std::size_t depth = 16, + std::size_t minbucket = 64, + std::size_t maxbucket = 512, + Scalar leftextent = 0, + Scalar rightextent = 0) + : left(nullptr) + , right(nullptr) + { + --depth; + const auto minmaxStop = std::minmax_element(ivals.begin(), ivals.end(), + IntervalStopCmp()); + const auto minmaxStart = std::minmax_element(ivals.begin(), ivals.end(), + IntervalStartCmp()); + if (!ivals.empty()) { + center = (minmaxStart.first->start + minmaxStop.second->stop) / 2; + } + if (leftextent == 0 && rightextent == 0) { + // sort intervals by start + std::sort(ivals.begin(), ivals.end(), IntervalStartCmp()); + } else { + assert(std::is_sorted(ivals.begin(), ivals.end(), IntervalStartCmp())); + } + if (depth == 0 || (ivals.size() < minbucket && ivals.size() < maxbucket)) { + std::sort(ivals.begin(), ivals.end(), IntervalStartCmp()); + intervals = std::move(ivals); + assert(is_valid().first); + return; + } else { + Scalar leftp = 0; + Scalar rightp = 0; + + if (leftextent || rightextent) { + leftp = leftextent; + rightp = rightextent; + } else { + leftp = ivals.front().start; + rightp = std::max_element(ivals.begin(), ivals.end(), + IntervalStopCmp())->stop; + } + + interval_vector lefts; + interval_vector rights; + + for (typename interval_vector::const_iterator i = ivals.begin(); + i != ivals.end(); ++i) { + const interval& interval = *i; + if (interval.stop < center) { + lefts.push_back(interval); + } else if (interval.start > center) { + rights.push_back(interval); + } else { + assert(interval.start <= center); + assert(center <= interval.stop); + intervals.push_back(interval); + } + } + + if (!lefts.empty()) { + left.reset(new IntervalTree(std::move(lefts), + depth, minbucket, maxbucket, + leftp, center)); + } + if (!rights.empty()) { + right.reset(new IntervalTree(std::move(rights), + depth, minbucket, maxbucket, + center, rightp)); + } + } + assert(is_valid().first); + } + + // Call f on all intervals near the range [start, stop]: + template + void visit_near(const Scalar& start, const Scalar& stop, UnaryFunction f) const { + if (!intervals.empty() && ! (stop < intervals.front().start)) { + for (auto & i : intervals) { + f(i); + } + } + if (left && start <= center) { + left->visit_near(start, stop, f); + } + if (right && stop >= center) { + right->visit_near(start, stop, f); + } + } + + // Call f on all intervals crossing pos + template + void visit_overlapping(const Scalar& pos, UnaryFunction f) const { + visit_overlapping(pos, pos, f); + } + + // Call f on all intervals overlapping [start, stop] + template + void visit_overlapping(const Scalar& start, const Scalar& stop, UnaryFunction f) const { + auto filterF = [&](const interval& interval) { + if (interval.stop >= start && interval.start <= stop) { + // Only apply f if overlapping + f(interval); + } + }; + visit_near(start, stop, filterF); + } + + // Call f on all intervals contained within [start, stop] + template + void visit_contained(const Scalar& start, const Scalar& stop, UnaryFunction f) const { + auto filterF = [&](const interval& interval) { + if (start <= interval.start && interval.stop <= stop) { + f(interval); + } + }; + visit_near(start, stop, filterF); + } + + interval_vector findOverlapping(const Scalar& start, const Scalar& stop) const { + interval_vector result; + visit_overlapping(start, stop, + [&](const interval& interval) { + result.emplace_back(interval); + }); + return result; + } + + interval_vector findContained(const Scalar& start, const Scalar& stop) const { + interval_vector result; + visit_contained(start, stop, + [&](const interval& interval) { + result.push_back(interval); + }); + return result; + } + bool empty() const { + if (left && !left->empty()) { + return false; + } + if (!intervals.empty()) { + return false; + } + if (right && !right->empty()) { + return false; + } + return true; + } + + template + void visit_all(UnaryFunction f) const { + if (left) { + left->visit_all(f); + } + std::for_each(intervals.begin(), intervals.end(), f); + if (right) { + right->visit_all(f); + } + } + + std::pair extentBruitForce() const { + struct Extent { + std::pair x = {std::numeric_limits::max(), + std::numeric_limits::min() }; + void operator()(const interval & interval) { + x.first = std::min(x.first, interval.start); + x.second = std::max(x.second, interval.stop); + } + }; + Extent extent; + + visit_all([&](const interval & interval) { extent(interval); }); + return extent.x; + } + + // Check all constraints. + // If first is false, second is invalid. + std::pair> is_valid() const { + const auto minmaxStop = std::minmax_element(intervals.begin(), intervals.end(), + IntervalStopCmp()); + const auto minmaxStart = std::minmax_element(intervals.begin(), intervals.end(), + IntervalStartCmp()); + + std::pair> result = {true, { std::numeric_limits::max(), + std::numeric_limits::min() }}; + if (!intervals.empty()) { + result.second.first = std::min(result.second.first, minmaxStart.first->start); + result.second.second = std::min(result.second.second, minmaxStop.second->stop); + } + if (left) { + auto valid = left->is_valid(); + result.first &= valid.first; + result.second.first = std::min(result.second.first, valid.second.first); + result.second.second = std::min(result.second.second, valid.second.second); + if (!result.first) { return result; } + if (valid.second.second >= center) { + result.first = false; + return result; + } + } + if (right) { + auto valid = right->is_valid(); + result.first &= valid.first; + result.second.first = std::min(result.second.first, valid.second.first); + result.second.second = std::min(result.second.second, valid.second.second); + if (!result.first) { return result; } + if (valid.second.first <= center) { + result.first = false; + return result; + } + } + if (!std::is_sorted(intervals.begin(), intervals.end(), IntervalStartCmp())) { + result.first = false; + } + return result; + } + + friend std::ostream& operator<<(std::ostream& os, const IntervalTree& itree) { + return writeOut(os, itree); + } + + friend std::ostream& writeOut(std::ostream& os, const IntervalTree& itree, + std::size_t depth = 0) { + auto pad = [&]() { for (std::size_t i = 0; i != depth; ++i) { os << ' '; } }; + pad(); os << "center: " << itree.center << '\n'; + for (const interval & inter : itree.intervals) { + pad(); os << inter << '\n'; + } + if (itree.left) { + pad(); os << "left:\n"; + writeOut(os, *itree.left, depth + 1); + } else { + pad(); os << "left: nullptr\n"; + } + if (itree.right) { + pad(); os << "right:\n"; + writeOut(os, *itree.right, depth + 1); + } else { + pad(); os << "right: nullptr\n"; + } + return os; + } + +private: + interval_vector intervals; + std::unique_ptr left; + std::unique_ptr right; + Scalar center; +}; + +#endif diff --git a/third_party/json/json.hpp b/thirdparty/json/json.hpp old mode 100644 new mode 100755 similarity index 100% rename from third_party/json/json.hpp rename to thirdparty/json/json.hpp diff --git a/thirdparty/spdlog/async_logger.h b/thirdparty/spdlog/async_logger.h new file mode 100755 index 0000000..e9fcd5f --- /dev/null +++ b/thirdparty/spdlog/async_logger.h @@ -0,0 +1,82 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// Very fast asynchronous logger (millions of logs per second on an average desktop) +// Uses pre allocated lockfree queue for maximum throughput even under large number of threads. +// Creates a single back thread to pop messages from the queue and log them. +// +// Upon each log write the logger: +// 1. Checks if its log level is enough to log the message +// 2. Push a new copy of the message to a queue (or block the caller until space is available in the queue) +// 3. will throw spdlog_ex upon log exceptions +// Upon destruction, logs all remaining messages in the queue before destructing.. + +#include "common.h" +#include "logger.h" + +#include +#include +#include +#include + +namespace spdlog +{ + +namespace details +{ +class async_log_helper; +} + +class async_logger SPDLOG_FINAL :public logger +{ +public: + template + async_logger(const std::string& name, + const It& begin, + const It& end, + size_t queue_size, + const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, + const std::function& worker_warmup_cb = nullptr, + const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), + const std::function& worker_teardown_cb = nullptr); + + async_logger(const std::string& logger_name, + sinks_init_list sinks, + size_t queue_size, + const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, + const std::function& worker_warmup_cb = nullptr, + const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), + const std::function& worker_teardown_cb = nullptr); + + async_logger(const std::string& logger_name, + sink_ptr single_sink, + size_t queue_size, + const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, + const std::function& worker_warmup_cb = nullptr, + const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), + const std::function& worker_teardown_cb = nullptr); + + //Wait for the queue to be empty, and flush synchronously + //Warning: this can potentially last forever as we wait it to complete + void flush() override; + + // Error handler + virtual void set_error_handler(log_err_handler) override; + virtual log_err_handler error_handler() override; + +protected: + void _sink_it(details::log_msg& msg) override; + void _set_formatter(spdlog::formatter_ptr msg_formatter) override; + void _set_pattern(const std::string& pattern, pattern_time_type pattern_time) override; + +private: + std::unique_ptr _async_log_helper; +}; +} + + +#include "details/async_logger_impl.h" diff --git a/thirdparty/spdlog/common.h b/thirdparty/spdlog/common.h new file mode 100755 index 0000000..ea0b056 --- /dev/null +++ b/thirdparty/spdlog/common.h @@ -0,0 +1,161 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) +#include +#include +#endif + +#include "details/null_mutex.h" + +//visual studio upto 2013 does not support noexcept nor constexpr +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#define SPDLOG_NOEXCEPT throw() +#define SPDLOG_CONSTEXPR +#else +#define SPDLOG_NOEXCEPT noexcept +#define SPDLOG_CONSTEXPR constexpr +#endif + +// final keyword support. On by default. See tweakme.h +#if defined(SPDLOG_NO_FINAL) +#define SPDLOG_FINAL +#else +#define SPDLOG_FINAL final +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define SPDLOG_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +#define SPDLOG_DEPRECATED __declspec(deprecated) +#else +#define SPDLOG_DEPRECATED +#endif + +#include "fmt/fmt.h" + +namespace spdlog +{ + +class formatter; + +namespace sinks +{ +class sink; +} + +using log_clock = std::chrono::system_clock; +using sink_ptr = std::shared_ptr < sinks::sink >; +using sinks_init_list = std::initializer_list < sink_ptr >; +using formatter_ptr = std::shared_ptr; +#if defined(SPDLOG_NO_ATOMIC_LEVELS) +using level_t = details::null_atomic_int; +#else +using level_t = std::atomic; +#endif + +using log_err_handler = std::function; + +//Log level enum +namespace level +{ +typedef enum +{ + trace = 0, + debug = 1, + info = 2, + warn = 3, + err = 4, + critical = 5, + off = 6 +} level_enum; + +#if !defined(SPDLOG_LEVEL_NAMES) +#define SPDLOG_LEVEL_NAMES { "trace", "debug", "info", "warning", "error", "critical", "off" } +#endif +static const char* level_names[] SPDLOG_LEVEL_NAMES; + +static const char* short_level_names[] { "T", "D", "I", "W", "E", "C", "O" }; + +inline const char* to_str(spdlog::level::level_enum l) +{ + return level_names[l]; +} + +inline const char* to_short_str(spdlog::level::level_enum l) +{ + return short_level_names[l]; +} +} //level + + +// +// Async overflow policy - block by default. +// +enum class async_overflow_policy +{ + block_retry, // Block / yield / sleep until message can be enqueued + discard_log_msg // Discard the message it enqueue fails +}; + +// +// Pattern time - specific time getting to use for pattern_formatter. +// local time by default +// +enum class pattern_time_type +{ + local, // log localtime + utc // log utc +}; + +// +// Log exception +// +namespace details +{ +namespace os +{ +std::string errno_str(int err_num); +} +} +class spdlog_ex: public std::exception +{ +public: + spdlog_ex(const std::string& msg):_msg(msg) + {} + spdlog_ex(const std::string& msg, int last_errno) + { + _msg = msg + ": " + details::os::errno_str(last_errno); + } + const char* what() const SPDLOG_NOEXCEPT override + { + return _msg.c_str(); + } +private: + std::string _msg; + +}; + +// +// wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined) +// +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) +using filename_t = std::wstring; +#else +using filename_t = std::string; +#endif + + +} //spdlog diff --git a/thirdparty/spdlog/details/async_log_helper.h b/thirdparty/spdlog/details/async_log_helper.h new file mode 100755 index 0000000..7c3dcf4 --- /dev/null +++ b/thirdparty/spdlog/details/async_log_helper.h @@ -0,0 +1,399 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +// async log helper : +// Process logs asynchronously using a back thread. +// +// If the internal queue of log messages reaches its max size, +// then the client call will block until there is more room. +// + +#pragma once + +#include "../common.h" +#include "../sinks/sink.h" +#include "../details/mpmc_bounded_q.h" +#include "../details/log_msg.h" +#include "../details/os.h" +#include "../formatter.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spdlog +{ +namespace details +{ + +class async_log_helper +{ + // Async msg to move to/from the queue + // Movable only. should never be copied + enum class async_msg_type + { + log, + flush, + terminate + }; + struct async_msg + { + std::string logger_name; + level::level_enum level; + log_clock::time_point time; + size_t thread_id; + std::string txt; + async_msg_type msg_type; + size_t msg_id; + + async_msg() = default; + ~async_msg() = default; + + +async_msg(async_msg&& other) SPDLOG_NOEXCEPT: + logger_name(std::move(other.logger_name)), + level(std::move(other.level)), + time(std::move(other.time)), + thread_id(other.thread_id), + txt(std::move(other.txt)), + msg_type(std::move(other.msg_type)), + msg_id(other.msg_id) + {} + + async_msg(async_msg_type m_type): + level(level::info), + thread_id(0), + msg_type(m_type), + msg_id(0) + {} + + async_msg& operator=(async_msg&& other) SPDLOG_NOEXCEPT + { + logger_name = std::move(other.logger_name); + level = other.level; + time = std::move(other.time); + thread_id = other.thread_id; + txt = std::move(other.txt); + msg_type = other.msg_type; + msg_id = other.msg_id; + return *this; + } + + // never copy or assign. should only be moved.. + async_msg(const async_msg&) = delete; + async_msg& operator=(const async_msg& other) = delete; + + // construct from log_msg + async_msg(const details::log_msg& m): + level(m.level), + time(m.time), + thread_id(m.thread_id), + txt(m.raw.data(), m.raw.size()), + msg_type(async_msg_type::log), + msg_id(m.msg_id) + { +#ifndef SPDLOG_NO_NAME + logger_name = *m.logger_name; +#endif + } + + + // copy into log_msg + void fill_log_msg(log_msg &msg) + { + msg.logger_name = &logger_name; + msg.level = level; + msg.time = time; + msg.thread_id = thread_id; + msg.raw << txt; + msg.msg_id = msg_id; + } + }; + +public: + + using item_type = async_msg; + using q_type = details::mpmc_bounded_queue; + + using clock = std::chrono::steady_clock; + + + async_log_helper(formatter_ptr formatter, + const std::vector& sinks, + size_t queue_size, + const log_err_handler err_handler, + const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, + const std::function& worker_warmup_cb = nullptr, + const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), + const std::function& worker_teardown_cb = nullptr); + + void log(const details::log_msg& msg); + + // stop logging and join the back thread + ~async_log_helper(); + + void set_formatter(formatter_ptr); + + void flush(bool wait_for_q); + + void set_error_handler(spdlog::log_err_handler err_handler); + +private: + formatter_ptr _formatter; + std::vector> _sinks; + + // queue of messages to log + q_type _q; + + log_err_handler _err_handler; + + bool _flush_requested; + + bool _terminate_requested; + + + // overflow policy + const async_overflow_policy _overflow_policy; + + // worker thread warmup callback - one can set thread priority, affinity, etc + const std::function _worker_warmup_cb; + + // auto periodic sink flush parameter + const std::chrono::milliseconds _flush_interval_ms; + + // worker thread teardown callback + const std::function _worker_teardown_cb; + + // worker thread + std::thread _worker_thread; + + void push_msg(async_msg&& new_msg); + + // worker thread main loop + void worker_loop(); + + // pop next message from the queue and process it. will set the last_pop to the pop time + // return false if termination of the queue is required + bool process_next_msg(log_clock::time_point& last_pop, log_clock::time_point& last_flush); + + void handle_flush_interval(log_clock::time_point& now, log_clock::time_point& last_flush); + + // sleep,yield or return immediately using the time passed since last message as a hint + static void sleep_or_yield(const spdlog::log_clock::time_point& now, const log_clock::time_point& last_op_time); + + // wait until the queue is empty + void wait_empty_q(); + +}; +} +} + +/////////////////////////////////////////////////////////////////////////////// +// async_sink class implementation +/////////////////////////////////////////////////////////////////////////////// +inline spdlog::details::async_log_helper::async_log_helper( + formatter_ptr formatter, + const std::vector& sinks, + size_t queue_size, + log_err_handler err_handler, + const async_overflow_policy overflow_policy, + const std::function& worker_warmup_cb, + const std::chrono::milliseconds& flush_interval_ms, + const std::function& worker_teardown_cb): + _formatter(formatter), + _sinks(sinks), + _q(queue_size), + _err_handler(err_handler), + _flush_requested(false), + _terminate_requested(false), + _overflow_policy(overflow_policy), + _worker_warmup_cb(worker_warmup_cb), + _flush_interval_ms(flush_interval_ms), + _worker_teardown_cb(worker_teardown_cb), + _worker_thread(&async_log_helper::worker_loop, this) +{} + +// Send to the worker thread termination message(level=off) +// and wait for it to finish gracefully +inline spdlog::details::async_log_helper::~async_log_helper() +{ + try + { + push_msg(async_msg(async_msg_type::terminate)); + _worker_thread.join(); + } + catch (...) // don't crash in destructor + { + } +} + + +//Try to push and block until succeeded (if the policy is not to discard when the queue is full) +inline void spdlog::details::async_log_helper::log(const details::log_msg& msg) +{ + push_msg(async_msg(msg)); +} + +inline void spdlog::details::async_log_helper::push_msg(details::async_log_helper::async_msg&& new_msg) +{ + if (!_q.enqueue(std::move(new_msg)) && _overflow_policy != async_overflow_policy::discard_log_msg) + { + auto last_op_time = details::os::now(); + auto now = last_op_time; + do + { + now = details::os::now(); + sleep_or_yield(now, last_op_time); + } + while (!_q.enqueue(std::move(new_msg))); + } +} + +// optionally wait for the queue be empty and request flush from the sinks +inline void spdlog::details::async_log_helper::flush(bool wait_for_q) +{ + push_msg(async_msg(async_msg_type::flush)); + if (wait_for_q) + wait_empty_q(); //return when queue is empty +} + +inline void spdlog::details::async_log_helper::worker_loop() +{ + if (_worker_warmup_cb) _worker_warmup_cb(); + auto last_pop = details::os::now(); + auto last_flush = last_pop; + auto active = true; + while (active) + { + try + { + active = process_next_msg(last_pop, last_flush); + } + catch (const std::exception &ex) + { + _err_handler(ex.what()); + } + catch(...) + { + _err_handler("Unknown exeption in async logger worker loop."); + } + } + if (_worker_teardown_cb) _worker_teardown_cb(); + + +} + +// process next message in the queue +// return true if this thread should still be active (while no terminate msg was received) +inline bool spdlog::details::async_log_helper::process_next_msg(log_clock::time_point& last_pop, log_clock::time_point& last_flush) +{ + async_msg incoming_async_msg; + + if (_q.dequeue(incoming_async_msg)) + { + last_pop = details::os::now(); + switch (incoming_async_msg.msg_type) + { + case async_msg_type::flush: + _flush_requested = true; + break; + + case async_msg_type::terminate: + _flush_requested = true; + _terminate_requested = true; + break; + + default: + log_msg incoming_log_msg; + incoming_async_msg.fill_log_msg(incoming_log_msg); + _formatter->format(incoming_log_msg); + for (auto &s : _sinks) + { + if (s->should_log(incoming_log_msg.level)) + { + s->log(incoming_log_msg); + } + } + } + return true; + } + + // Handle empty queue.. + // This is the only place where the queue can terminate or flush to avoid losing messages already in the queue + else + { + auto now = details::os::now(); + handle_flush_interval(now, last_flush); + sleep_or_yield(now, last_pop); + return !_terminate_requested; + } +} + +// flush all sinks if _flush_interval_ms has expired +inline void spdlog::details::async_log_helper::handle_flush_interval(log_clock::time_point& now, log_clock::time_point& last_flush) +{ + auto should_flush = _flush_requested || (_flush_interval_ms != std::chrono::milliseconds::zero() && now - last_flush >= _flush_interval_ms); + if (should_flush) + { + for (auto &s : _sinks) + s->flush(); + now = last_flush = details::os::now(); + _flush_requested = false; + } +} + +inline void spdlog::details::async_log_helper::set_formatter(formatter_ptr msg_formatter) +{ + _formatter = msg_formatter; +} + + +// spin, yield or sleep. use the time passed since last message as a hint +inline void spdlog::details::async_log_helper::sleep_or_yield(const spdlog::log_clock::time_point& now, const spdlog::log_clock::time_point& last_op_time) +{ + using namespace std::this_thread; + using std::chrono::milliseconds; + using std::chrono::microseconds; + + auto time_since_op = now - last_op_time; + + // spin upto 50 micros + if (time_since_op <= microseconds(50)) + return; + + // yield upto 150 micros + if (time_since_op <= microseconds(100)) + return std::this_thread::yield(); + + // sleep for 20 ms upto 200 ms + if (time_since_op <= milliseconds(200)) + return sleep_for(milliseconds(20)); + + // sleep for 500 ms + return sleep_for(milliseconds(500)); +} + +// wait for the queue to be empty +inline void spdlog::details::async_log_helper::wait_empty_q() +{ + auto last_op = details::os::now(); + while (!_q.is_empty()) + { + sleep_or_yield(details::os::now(), last_op); + } +} + +inline void spdlog::details::async_log_helper::set_error_handler(spdlog::log_err_handler err_handler) +{ + _err_handler = err_handler; +} + + + diff --git a/thirdparty/spdlog/details/async_logger_impl.h b/thirdparty/spdlog/details/async_logger_impl.h new file mode 100755 index 0000000..8373211 --- /dev/null +++ b/thirdparty/spdlog/details/async_logger_impl.h @@ -0,0 +1,107 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// Async Logger implementation +// Use an async_sink (queue per logger) to perform the logging in a worker thread + +#include "../details/async_log_helper.h" +#include "../async_logger.h" + +#include +#include +#include +#include + +template +inline spdlog::async_logger::async_logger(const std::string& logger_name, + const It& begin, + const It& end, + size_t queue_size, + const async_overflow_policy overflow_policy, + const std::function& worker_warmup_cb, + const std::chrono::milliseconds& flush_interval_ms, + const std::function& worker_teardown_cb) : + logger(logger_name, begin, end), + _async_log_helper(new details::async_log_helper(_formatter, _sinks, queue_size, _err_handler, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb)) +{ +} + +inline spdlog::async_logger::async_logger(const std::string& logger_name, + sinks_init_list sinks_list, + size_t queue_size, + const async_overflow_policy overflow_policy, + const std::function& worker_warmup_cb, + const std::chrono::milliseconds& flush_interval_ms, + const std::function& worker_teardown_cb) : + async_logger(logger_name, sinks_list.begin(), sinks_list.end(), queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb) {} + +inline spdlog::async_logger::async_logger(const std::string& logger_name, + sink_ptr single_sink, + size_t queue_size, + const async_overflow_policy overflow_policy, + const std::function& worker_warmup_cb, + const std::chrono::milliseconds& flush_interval_ms, + const std::function& worker_teardown_cb) : + async_logger(logger_name, +{ + single_sink +}, queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb) {} + + +inline void spdlog::async_logger::flush() +{ + _async_log_helper->flush(true); +} + +// Error handler +inline void spdlog::async_logger::set_error_handler(spdlog::log_err_handler err_handler) +{ + _err_handler = err_handler; + _async_log_helper->set_error_handler(err_handler); + +} +inline spdlog::log_err_handler spdlog::async_logger::error_handler() +{ + return _err_handler; +} + + +inline void spdlog::async_logger::_set_formatter(spdlog::formatter_ptr msg_formatter) +{ + _formatter = msg_formatter; + _async_log_helper->set_formatter(_formatter); +} + +inline void spdlog::async_logger::_set_pattern(const std::string& pattern, pattern_time_type pattern_time) +{ + _formatter = std::make_shared(pattern, pattern_time); + _async_log_helper->set_formatter(_formatter); +} + + +inline void spdlog::async_logger::_sink_it(details::log_msg& msg) +{ + try + { +#if defined(SPDLOG_ENABLE_MESSAGE_COUNTER) + _incr_msg_counter(msg); +#endif + _async_log_helper->log(msg); + if (_should_flush_on(msg)) + _async_log_helper->flush(false); // do async flush + } + catch (const std::exception &ex) + { + _err_handler(ex.what()); + } + catch(...) + { + _err_handler("Unknown exception in logger " + _name); + throw; + } + +} diff --git a/thirdparty/spdlog/details/file_helper.h b/thirdparty/spdlog/details/file_helper.h new file mode 100755 index 0000000..72fc093 --- /dev/null +++ b/thirdparty/spdlog/details/file_helper.h @@ -0,0 +1,145 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// Helper class for file sink +// When failing to open a file, retry several times(5) with small delay between the tries(10 ms) +// Throw spdlog_ex exception on errors + +#include "../details/os.h" +#include "../details/log_msg.h" + +#include +#include +#include +#include +#include +#include + +namespace spdlog +{ +namespace details +{ + +class file_helper +{ + +public: + const int open_tries = 5; + const int open_interval = 10; + + explicit file_helper() : + _fd(nullptr) + {} + + file_helper(const file_helper&) = delete; + file_helper& operator=(const file_helper&) = delete; + + ~file_helper() + { + close(); + } + + + void open(const filename_t& fname, bool truncate = false) + { + + close(); + auto *mode = truncate ? SPDLOG_FILENAME_T("wb") : SPDLOG_FILENAME_T("ab"); + _filename = fname; + for (int tries = 0; tries < open_tries; ++tries) + { + if (!os::fopen_s(&_fd, fname, mode)) + return; + + std::this_thread::sleep_for(std::chrono::milliseconds(open_interval)); + } + + throw spdlog_ex("Failed opening file " + os::filename_to_str(_filename) + " for writing", errno); + } + + void reopen(bool truncate) + { + if (_filename.empty()) + throw spdlog_ex("Failed re opening file - was not opened before"); + open(_filename, truncate); + + } + + void flush() + { + std::fflush(_fd); + } + + void close() + { + if (_fd) + { + std::fclose(_fd); + _fd = nullptr; + } + } + + void write(const log_msg& msg) + { + size_t msg_size = msg.formatted.size(); + auto data = msg.formatted.data(); + if (std::fwrite(data, 1, msg_size, _fd) != msg_size) + throw spdlog_ex("Failed writing to file " + os::filename_to_str(_filename), errno); + } + + size_t size() const + { + if (!_fd) + throw spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(_filename)); + return os::filesize(_fd); + } + + const filename_t& filename() const + { + return _filename; + } + + static bool file_exists(const filename_t& fname) + { + return os::file_exists(fname); + } + + // + // return file path and its extension: + // + // "mylog.txt" => ("mylog", ".txt") + // "mylog" => ("mylog", "") + // "mylog." => ("mylog.", "") + // "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt") + // + // the starting dot in filenames is ignored (hidden files): + // + // ".mylog" => (".mylog". "") + // "my_folder/.mylog" => ("my_folder/.mylog", "") + // "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") + static std::tuple split_by_extenstion(const spdlog::filename_t& fname) + { + auto ext_index = fname.rfind('.'); + + // no valid extension found - return whole path and empty string as extension + if (ext_index == filename_t::npos || ext_index == 0 || ext_index == fname.size() - 1) + return std::make_tuple(fname, spdlog::filename_t()); + + // treat casese like "/etc/rc.d/somelogfile or "/abc/.hiddenfile" + auto folder_index = fname.rfind(details::os::folder_sep); + if (folder_index != fname.npos && folder_index >= ext_index - 1) + return std::make_tuple(fname, spdlog::filename_t()); + + // finally - return a valid base and extension tuple + return std::make_tuple(fname.substr(0, ext_index), fname.substr(ext_index)); + } +private: + FILE* _fd; + filename_t _filename; +}; +} +} diff --git a/thirdparty/spdlog/details/log_msg.h b/thirdparty/spdlog/details/log_msg.h new file mode 100755 index 0000000..a9fe920 --- /dev/null +++ b/thirdparty/spdlog/details/log_msg.h @@ -0,0 +1,50 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "../common.h" +#include "../details/os.h" + + +#include +#include + +namespace spdlog +{ +namespace details +{ +struct log_msg +{ + log_msg() = default; + log_msg(const std::string *loggers_name, level::level_enum lvl) : + logger_name(loggers_name), + level(lvl), + msg_id(0) + { +#ifndef SPDLOG_NO_DATETIME + time = os::now(); +#endif + +#ifndef SPDLOG_NO_THREAD_ID + thread_id = os::thread_id(); +#endif + } + + log_msg(const log_msg& other) = delete; + log_msg& operator=(log_msg&& other) = delete; + log_msg(log_msg&& other) = delete; + + + const std::string *logger_name; + level::level_enum level; + log_clock::time_point time; + size_t thread_id; + fmt::MemoryWriter raw; + fmt::MemoryWriter formatted; + size_t msg_id; +}; +} +} diff --git a/thirdparty/spdlog/details/logger_impl.h b/thirdparty/spdlog/details/logger_impl.h new file mode 100755 index 0000000..8804474 --- /dev/null +++ b/thirdparty/spdlog/details/logger_impl.h @@ -0,0 +1,373 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "../logger.h" +#include "../sinks/stdout_sinks.h" + +#include +#include + +// create logger with given name, sinks and the default pattern formatter +// all other ctors will call this one +template +inline spdlog::logger::logger(const std::string& logger_name, const It& begin, const It& end): + _name(logger_name), + _sinks(begin, end), + _formatter(std::make_shared("%+")), + _level(level::info), + _flush_level(level::off), + _last_err_time(0), + _msg_counter(1) // message counter will start from 1. 0-message id will be reserved for controll messages +{ + _err_handler = [this](const std::string &msg) + { + this->_default_err_handler(msg); + }; +} + +// ctor with sinks as init list +inline spdlog::logger::logger(const std::string& logger_name, sinks_init_list sinks_list): + logger(logger_name, sinks_list.begin(), sinks_list.end()) +{} + + +// ctor with single sink +inline spdlog::logger::logger(const std::string& logger_name, spdlog::sink_ptr single_sink): + logger(logger_name, +{ + single_sink +}) +{} + + +inline spdlog::logger::~logger() = default; + + +inline void spdlog::logger::set_formatter(spdlog::formatter_ptr msg_formatter) +{ + _set_formatter(msg_formatter); +} + +inline void spdlog::logger::set_pattern(const std::string& pattern, pattern_time_type pattern_time) +{ + _set_pattern(pattern, pattern_time); +} + +template +inline void spdlog::logger::log(level::level_enum lvl, const char* fmt, const Args&... args) +{ + if (!should_log(lvl)) return; + + try + { + details::log_msg log_msg(&_name, lvl); + +#if defined(SPDLOG_FMT_PRINTF) + fmt::printf(log_msg.raw, fmt, args...); +#else + log_msg.raw.write(fmt, args...); +#endif + _sink_it(log_msg); + } + catch (const std::exception &ex) + { + _err_handler(ex.what()); + } + catch(...) + { + _err_handler("Unknown exception in logger " + _name); + throw; + } +} + +template +inline void spdlog::logger::log(level::level_enum lvl, const char* msg) +{ + if (!should_log(lvl)) return; + try + { + details::log_msg log_msg(&_name, lvl); + log_msg.raw << msg; + _sink_it(log_msg); + } + catch (const std::exception &ex) + { + _err_handler(ex.what()); + } + catch (...) + { + _err_handler("Unknown exception in logger " + _name); + throw; + } +} + +template +inline void spdlog::logger::log(level::level_enum lvl, const T& msg) +{ + if (!should_log(lvl)) return; + try + { + details::log_msg log_msg(&_name, lvl); + log_msg.raw << msg; + _sink_it(log_msg); + } + catch (const std::exception &ex) + { + _err_handler(ex.what()); + } + catch (...) + { + _err_handler("Unknown exception in logger " + _name); + throw; + } +} + + +template +inline void spdlog::logger::trace(const char* fmt, const Arg1 &arg1, const Args&... args) +{ + log(level::trace, fmt, arg1, args...); +} + +template +inline void spdlog::logger::debug(const char* fmt, const Arg1 &arg1, const Args&... args) +{ + log(level::debug, fmt, arg1, args...); +} + +template +inline void spdlog::logger::info(const char* fmt, const Arg1 &arg1, const Args&... args) +{ + log(level::info, fmt, arg1, args...); +} + +template +inline void spdlog::logger::warn(const char* fmt, const Arg1 &arg1, const Args&... args) +{ + log(level::warn, fmt, arg1, args...); +} + +template +inline void spdlog::logger::error(const char* fmt, const Arg1 &arg1, const Args&... args) +{ + log(level::err, fmt, arg1, args...); +} + +template +inline void spdlog::logger::critical(const char* fmt, const Arg1 &arg1, const Args&... args) +{ + log(level::critical, fmt, arg1, args...); +} + + +template +inline void spdlog::logger::trace(const T& msg) +{ + log(level::trace, msg); +} + +template +inline void spdlog::logger::debug(const T& msg) +{ + log(level::debug, msg); +} + + +template +inline void spdlog::logger::info(const T& msg) +{ + log(level::info, msg); +} + + +template +inline void spdlog::logger::warn(const T& msg) +{ + log(level::warn, msg); +} + +template +inline void spdlog::logger::error(const T& msg) +{ + log(level::err, msg); +} + +template +inline void spdlog::logger::critical(const T& msg) +{ + log(level::critical, msg); +} + + + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT +#include +#include + +template +inline void spdlog::logger::log(level::level_enum lvl, const wchar_t* msg) +{ + std::wstring_convert > conv; + + log(lvl, conv.to_bytes(msg)); +} + +template +inline void spdlog::logger::log(level::level_enum lvl, const wchar_t* fmt, const Args&... args) +{ + fmt::WMemoryWriter wWriter; + + wWriter.write(fmt, args...); + log(lvl, wWriter.c_str()); +} + +template +inline void spdlog::logger::trace(const wchar_t* fmt, const Args&... args) +{ + log(level::trace, fmt, args...); +} + +template +inline void spdlog::logger::debug(const wchar_t* fmt, const Args&... args) +{ + log(level::debug, fmt, args...); +} + +template +inline void spdlog::logger::info(const wchar_t* fmt, const Args&... args) +{ + log(level::info, fmt, args...); +} + + +template +inline void spdlog::logger::warn(const wchar_t* fmt, const Args&... args) +{ + log(level::warn, fmt, args...); +} + +template +inline void spdlog::logger::error(const wchar_t* fmt, const Args&... args) +{ + log(level::err, fmt, args...); +} + +template +inline void spdlog::logger::critical(const wchar_t* fmt, const Args&... args) +{ + log(level::critical, fmt, args...); +} + +#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT + + + +// +// name and level +// +inline const std::string& spdlog::logger::name() const +{ + return _name; +} + +inline void spdlog::logger::set_level(spdlog::level::level_enum log_level) +{ + _level.store(log_level); +} + +inline void spdlog::logger::set_error_handler(spdlog::log_err_handler err_handler) +{ + _err_handler = err_handler; +} + +inline spdlog::log_err_handler spdlog::logger::error_handler() +{ + return _err_handler; +} + + +inline void spdlog::logger::flush_on(level::level_enum log_level) +{ + _flush_level.store(log_level); +} + +inline spdlog::level::level_enum spdlog::logger::level() const +{ + return static_cast(_level.load(std::memory_order_relaxed)); +} + +inline bool spdlog::logger::should_log(spdlog::level::level_enum msg_level) const +{ + return msg_level >= _level.load(std::memory_order_relaxed); +} + +// +// protected virtual called at end of each user log call (if enabled) by the line_logger +// +inline void spdlog::logger::_sink_it(details::log_msg& msg) +{ +#if defined(SPDLOG_ENABLE_MESSAGE_COUNTER) + _incr_msg_counter(msg); +#endif + _formatter->format(msg); + for (auto &sink : _sinks) + { + if( sink->should_log( msg.level)) + { + sink->log(msg); + } + } + + if(_should_flush_on(msg)) + flush(); +} + +inline void spdlog::logger::_set_pattern(const std::string& pattern, pattern_time_type pattern_time) +{ + _formatter = std::make_shared(pattern, pattern_time); +} +inline void spdlog::logger::_set_formatter(formatter_ptr msg_formatter) +{ + _formatter = msg_formatter; +} + +inline void spdlog::logger::flush() +{ + for (auto& sink : _sinks) + sink->flush(); +} + +inline void spdlog::logger::_default_err_handler(const std::string &msg) +{ + auto now = time(nullptr); + if (now - _last_err_time < 60) + return; + auto tm_time = details::os::localtime(now); + char date_buf[100]; + std::strftime(date_buf, sizeof(date_buf), "%Y-%m-%d %H:%M:%S", &tm_time); + details::log_msg err_msg; + err_msg.formatted.write("[*** LOG ERROR ***] [{}] [{}] [{}]{}", name(), msg, date_buf, details::os::eol); + sinks::stderr_sink_mt::instance()->log(err_msg); + _last_err_time = now; +} + +inline bool spdlog::logger::_should_flush_on(const details::log_msg &msg) +{ + const auto flush_level = _flush_level.load(std::memory_order_relaxed); + return (msg.level >= flush_level) && (msg.level != level::off); +} + +inline void spdlog::logger::_incr_msg_counter(details::log_msg &msg) +{ + msg.msg_id = _msg_counter.fetch_add(1, std::memory_order_relaxed); +} + +inline const std::vector& spdlog::logger::sinks() const +{ + return _sinks; +} + diff --git a/thirdparty/spdlog/details/mpmc_bounded_q.h b/thirdparty/spdlog/details/mpmc_bounded_q.h new file mode 100755 index 0000000..102df85 --- /dev/null +++ b/thirdparty/spdlog/details/mpmc_bounded_q.h @@ -0,0 +1,176 @@ +/* +A modified version of Bounded MPMC queue by Dmitry Vyukov. + +Original code from: +http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue + +licensed by Dmitry Vyukov under the terms below: + +Simplified BSD license + +Copyright (c) 2010-2011 Dmitry Vyukov. All rights reserved. +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this list of +conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list +of conditions and the following disclaimer in the documentation and/or other materials +provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY DMITRY VYUKOV "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL DMITRY VYUKOV OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those of the authors and +should not be interpreted as representing official policies, either expressed or implied, of Dmitry Vyukov. +*/ + +/* +The code in its current form adds the license below: + +Copyright(c) 2015 Gabi Melman. +Distributed under the MIT License (http://opensource.org/licenses/MIT) + +*/ + +#pragma once + +#include "../common.h" + +#include +#include + +namespace spdlog +{ +namespace details +{ + +template +class mpmc_bounded_queue +{ +public: + + using item_type = T; + mpmc_bounded_queue(size_t buffer_size) + :max_size_(buffer_size), + buffer_(new cell_t[buffer_size]), + buffer_mask_(buffer_size - 1) + { + //queue size must be power of two + if (!((buffer_size >= 2) && ((buffer_size & (buffer_size - 1)) == 0))) + throw spdlog_ex("async logger queue size must be power of two"); + + for (size_t i = 0; i != buffer_size; i += 1) + buffer_[i].sequence_.store(i, std::memory_order_relaxed); + enqueue_pos_.store(0, std::memory_order_relaxed); + dequeue_pos_.store(0, std::memory_order_relaxed); + } + + ~mpmc_bounded_queue() + { + delete[] buffer_; + } + + + bool enqueue(T&& data) + { + cell_t* cell; + size_t pos = enqueue_pos_.load(std::memory_order_relaxed); + for (;;) + { + cell = &buffer_[pos & buffer_mask_]; + size_t seq = cell->sequence_.load(std::memory_order_acquire); + intptr_t dif = static_cast(seq) - static_cast(pos); + if (dif == 0) + { + if (enqueue_pos_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed)) + break; + } + else if (dif < 0) + { + return false; + } + else + { + pos = enqueue_pos_.load(std::memory_order_relaxed); + } + } + cell->data_ = std::move(data); + cell->sequence_.store(pos + 1, std::memory_order_release); + return true; + } + + bool dequeue(T& data) + { + cell_t* cell; + size_t pos = dequeue_pos_.load(std::memory_order_relaxed); + for (;;) + { + cell = &buffer_[pos & buffer_mask_]; + size_t seq = + cell->sequence_.load(std::memory_order_acquire); + intptr_t dif = static_cast(seq) - static_cast(pos + 1); + if (dif == 0) + { + if (dequeue_pos_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed)) + break; + } + else if (dif < 0) + return false; + else + pos = dequeue_pos_.load(std::memory_order_relaxed); + } + data = std::move(cell->data_); + cell->sequence_.store(pos + buffer_mask_ + 1, std::memory_order_release); + return true; + } + + bool is_empty() + { + size_t front, front1, back; + // try to take a consistent snapshot of front/tail. + do + { + front = enqueue_pos_.load(std::memory_order_acquire); + back = dequeue_pos_.load(std::memory_order_acquire); + front1 = enqueue_pos_.load(std::memory_order_relaxed); + } + while (front != front1); + return back == front; + } + +private: + struct cell_t + { + std::atomic sequence_; + T data_; + }; + + size_t const max_size_; + + static size_t const cacheline_size = 64; + typedef char cacheline_pad_t[cacheline_size]; + + cacheline_pad_t pad0_; + cell_t* const buffer_; + size_t const buffer_mask_; + cacheline_pad_t pad1_; + std::atomic enqueue_pos_; + cacheline_pad_t pad2_; + std::atomic dequeue_pos_; + cacheline_pad_t pad3_; + + mpmc_bounded_queue(mpmc_bounded_queue const&) = delete; + void operator= (mpmc_bounded_queue const&) = delete; +}; + +} // ns details +} // ns spdlog diff --git a/thirdparty/spdlog/details/null_mutex.h b/thirdparty/spdlog/details/null_mutex.h new file mode 100755 index 0000000..67b0aee --- /dev/null +++ b/thirdparty/spdlog/details/null_mutex.h @@ -0,0 +1,45 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include +// null, no cost dummy "mutex" and dummy "atomic" int + +namespace spdlog +{ +namespace details +{ +struct null_mutex +{ + void lock() {} + void unlock() {} + bool try_lock() + { + return true; + } +}; + +struct null_atomic_int +{ + int value; + null_atomic_int() = default; + + null_atomic_int(int val):value(val) + {} + + int load(std::memory_order) const + { + return value; + } + + void store(int val) + { + value = val; + } +}; + +} +} diff --git a/thirdparty/spdlog/details/os.h b/thirdparty/spdlog/details/os.h new file mode 100755 index 0000000..aaf949e --- /dev/null +++ b/thirdparty/spdlog/details/os.h @@ -0,0 +1,479 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// +#pragma once + +#include "../common.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + +#ifndef NOMINMAX +#define NOMINMAX //prevent windows redefining min/max +#endif + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include // _get_pid support +#include // _get_osfhandle and _isatty support + +#ifdef __MINGW32__ +#include +#endif + +#else // unix + +#include +#include + +#ifdef __linux__ +#include //Use gettid() syscall under linux to get thread id + +#elif __FreeBSD__ +#include //Use thr_self() syscall under FreeBSD to get thread id +#endif + +#endif //unix + +#ifndef __has_feature // Clang - feature checking macros. +#define __has_feature(x) 0 // Compatibility with non-clang compilers. +#endif + + +namespace spdlog +{ +namespace details +{ +namespace os +{ + +inline spdlog::log_clock::time_point now() +{ + +#if defined __linux__ && defined SPDLOG_CLOCK_COARSE + timespec ts; + ::clock_gettime(CLOCK_REALTIME_COARSE, &ts); + return std::chrono::time_point( + std::chrono::duration_cast( + std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec))); + + +#else + return log_clock::now(); +#endif + +} +inline std::tm localtime(const std::time_t &time_tt) +{ + +#ifdef _WIN32 + std::tm tm; + localtime_s(&tm, &time_tt); +#else + std::tm tm; + localtime_r(&time_tt, &tm); +#endif + return tm; +} + +inline std::tm localtime() +{ + std::time_t now_t = time(nullptr); + return localtime(now_t); +} + + +inline std::tm gmtime(const std::time_t &time_tt) +{ + +#ifdef _WIN32 + std::tm tm; + gmtime_s(&tm, &time_tt); +#else + std::tm tm; + gmtime_r(&time_tt, &tm); +#endif + return tm; +} + +inline std::tm gmtime() +{ + std::time_t now_t = time(nullptr); + return gmtime(now_t); +} +inline bool operator==(const std::tm& tm1, const std::tm& tm2) +{ + return (tm1.tm_sec == tm2.tm_sec && + tm1.tm_min == tm2.tm_min && + tm1.tm_hour == tm2.tm_hour && + tm1.tm_mday == tm2.tm_mday && + tm1.tm_mon == tm2.tm_mon && + tm1.tm_year == tm2.tm_year && + tm1.tm_isdst == tm2.tm_isdst); +} + +inline bool operator!=(const std::tm& tm1, const std::tm& tm2) +{ + return !(tm1 == tm2); +} + +// eol definition +#if !defined (SPDLOG_EOL) +#ifdef _WIN32 +#define SPDLOG_EOL "\r\n" +#else +#define SPDLOG_EOL "\n" +#endif +#endif + +SPDLOG_CONSTEXPR static const char* eol = SPDLOG_EOL; +SPDLOG_CONSTEXPR static int eol_size = sizeof(SPDLOG_EOL) - 1; + + + +// folder separator +#ifdef _WIN32 +SPDLOG_CONSTEXPR static const char folder_sep = '\\'; +#else +SPDLOG_CONSTEXPR static const char folder_sep = '/'; +#endif + + +inline void prevent_child_fd(FILE *f) +{ +#ifdef _WIN32 + auto file_handle = (HANDLE)_get_osfhandle(_fileno(f)); + if (!::SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0)) + throw spdlog_ex("SetHandleInformation failed", errno); +#else + auto fd = fileno(f); + if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) + throw spdlog_ex("fcntl with FD_CLOEXEC failed", errno); +#endif +} + + +//fopen_s on non windows for writing +inline int fopen_s(FILE** fp, const filename_t& filename, const filename_t& mode) +{ +#ifdef _WIN32 +#ifdef SPDLOG_WCHAR_FILENAMES + *fp = _wfsopen((filename.c_str()), mode.c_str(), _SH_DENYWR); +#else + *fp = _fsopen((filename.c_str()), mode.c_str(), _SH_DENYWR); +#endif +#else //unix + *fp = fopen((filename.c_str()), mode.c_str()); +#endif + +#ifdef SPDLOG_PREVENT_CHILD_FD + if (*fp != nullptr) + prevent_child_fd(*fp); +#endif + return *fp == nullptr; +} + + +inline int remove(const filename_t &filename) +{ +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) + return _wremove(filename.c_str()); +#else + return std::remove(filename.c_str()); +#endif +} + +inline int rename(const filename_t& filename1, const filename_t& filename2) +{ +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) + return _wrename(filename1.c_str(), filename2.c_str()); +#else + return std::rename(filename1.c_str(), filename2.c_str()); +#endif +} + + +//Return if file exists +inline bool file_exists(const filename_t& filename) +{ +#ifdef _WIN32 +#ifdef SPDLOG_WCHAR_FILENAMES + auto attribs = GetFileAttributesW(filename.c_str()); +#else + auto attribs = GetFileAttributesA(filename.c_str()); +#endif + return (attribs != INVALID_FILE_ATTRIBUTES && !(attribs & FILE_ATTRIBUTE_DIRECTORY)); +#else //common linux/unix all have the stat system call + struct stat buffer; + return (stat(filename.c_str(), &buffer) == 0); +#endif +} + + + + +//Return file size according to open FILE* object +inline size_t filesize(FILE *f) +{ + if (f == nullptr) + throw spdlog_ex("Failed getting file size. fd is null"); +#if defined ( _WIN32) && !defined(__CYGWIN__) + int fd = _fileno(f); +#if _WIN64 //64 bits + struct _stat64 st; + if (_fstat64(fd, &st) == 0) + return st.st_size; + +#else //windows 32 bits + long ret = _filelength(fd); + if (ret >= 0) + return static_cast(ret); +#endif + +#else // unix + int fd = fileno(f); + //64 bits(but not in osx or cygwin, where fstat64 is deprecated) +#if !defined(__FreeBSD__) && !defined(__APPLE__) && (defined(__x86_64__) || defined(__ppc64__)) && !defined(__CYGWIN__) + struct stat64 st; + if (fstat64(fd, &st) == 0) + return static_cast(st.st_size); +#else // unix 32 bits or cygwin + struct stat st; + if (fstat(fd, &st) == 0) + return static_cast(st.st_size); +#endif +#endif + throw spdlog_ex("Failed getting file size from fd", errno); +} + + + + +//Return utc offset in minutes or throw spdlog_ex on failure +inline int utc_minutes_offset(const std::tm& tm = details::os::localtime()) +{ + +#ifdef _WIN32 +#if _WIN32_WINNT < _WIN32_WINNT_WS08 + TIME_ZONE_INFORMATION tzinfo; + auto rv = GetTimeZoneInformation(&tzinfo); +#else + DYNAMIC_TIME_ZONE_INFORMATION tzinfo; + auto rv = GetDynamicTimeZoneInformation(&tzinfo); +#endif + if (rv == TIME_ZONE_ID_INVALID) + throw spdlog::spdlog_ex("Failed getting timezone info. ", errno); + + int offset = -tzinfo.Bias; + if (tm.tm_isdst) + offset -= tzinfo.DaylightBias; + else + offset -= tzinfo.StandardBias; + return offset; +#else + +#if defined(sun) || defined(__sun) + // 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris + struct helper + { + static long int calculate_gmt_offset(const std::tm & localtm = details::os::localtime(), const std::tm & gmtm = details::os::gmtime()) + { + int local_year = localtm.tm_year + (1900 - 1); + int gmt_year = gmtm.tm_year + (1900 - 1); + + long int days = ( + // difference in day of year + localtm.tm_yday - gmtm.tm_yday + + // + intervening leap days + + ((local_year >> 2) - (gmt_year >> 2)) + - (local_year / 100 - gmt_year / 100) + + ((local_year / 100 >> 2) - (gmt_year / 100 >> 2)) + + // + difference in years * 365 */ + + (long int)(local_year - gmt_year) * 365 + ); + + long int hours = (24 * days) + (localtm.tm_hour - gmtm.tm_hour); + long int mins = (60 * hours) + (localtm.tm_min - gmtm.tm_min); + long int secs = (60 * mins) + (localtm.tm_sec - gmtm.tm_sec); + + return secs; + } + }; + + long int offset_seconds = helper::calculate_gmt_offset(tm); +#else + long int offset_seconds = tm.tm_gmtoff; +#endif + + return static_cast(offset_seconds / 60); +#endif +} + +//Return current thread id as size_t +//It exists because the std::this_thread::get_id() is much slower(especially under VS 2013) +inline size_t _thread_id() +{ +#ifdef _WIN32 + return static_cast(::GetCurrentThreadId()); +#elif __linux__ +# if defined(__ANDROID__) && defined(__ANDROID_API__) && (__ANDROID_API__ < 21) +# define SYS_gettid __NR_gettid +# endif + return static_cast(syscall(SYS_gettid)); +#elif __FreeBSD__ + long tid; + thr_self(&tid); + return static_cast(tid); +#elif __APPLE__ + uint64_t tid; + pthread_threadid_np(nullptr, &tid); + return static_cast(tid); +#else //Default to standard C++11 (other Unix) + return static_cast(std::hash()(std::this_thread::get_id())); +#endif +} + +//Return current thread id as size_t (from thread local storage) +inline size_t thread_id() +{ +#if defined(SPDLOG_DISABLE_TID_CACHING) || (defined(_MSC_VER) && (_MSC_VER < 1900)) || (defined(__clang__) && !__has_feature(cxx_thread_local)) + return _thread_id(); +#else // cache thread id in tls + static thread_local const size_t tid = _thread_id(); + return tid; +#endif + + +} + + +// wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined) +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) +#define SPDLOG_FILENAME_T(s) L ## s +inline std::string filename_to_str(const filename_t& filename) +{ + std::wstring_convert, wchar_t> c; + return c.to_bytes(filename); +} +#else +#define SPDLOG_FILENAME_T(s) s +inline std::string filename_to_str(const filename_t& filename) +{ + return filename; +} +#endif + +inline std::string errno_to_string(char[256], char* res) +{ + return std::string(res); +} + +inline std::string errno_to_string(char buf[256], int res) +{ + if (res == 0) + { + return std::string(buf); + } + else + { + return "Unknown error"; + } +} + +// Return errno string (thread safe) +inline std::string errno_str(int err_num) +{ + char buf[256]; + SPDLOG_CONSTEXPR auto buf_size = sizeof(buf); + +#ifdef _WIN32 + if (strerror_s(buf, buf_size, err_num) == 0) + return std::string(buf); + else + return "Unknown error"; + +#elif defined(__FreeBSD__) || defined(__APPLE__) || defined(ANDROID) || defined(__SUNPRO_CC) || \ + ((_POSIX_C_SOURCE >= 200112L) && ! defined(_GNU_SOURCE)) // posix version + + if (strerror_r(err_num, buf, buf_size) == 0) + return std::string(buf); + else + return "Unknown error"; + +#else // gnu version (might not use the given buf, so its retval pointer must be used) + auto err = strerror_r(err_num, buf, buf_size); // let compiler choose type + return errno_to_string(buf, err); // use overloading to select correct stringify function +#endif +} + +inline int pid() +{ + +#ifdef _WIN32 + return ::_getpid(); +#else + return static_cast(::getpid()); +#endif + +} + + +// Determine if the terminal supports colors +// Source: https://github.com/agauniyal/rang/ +inline bool is_color_terminal() +{ +#ifdef _WIN32 + return true; +#else + static constexpr const char* Terms[] = + { + "ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", + "linux", "msys", "putty", "rxvt", "screen", "vt100", "xterm" + }; + + const char *env_p = std::getenv("TERM"); + if (env_p == nullptr) + { + return false; + } + + static const bool result = std::any_of( + std::begin(Terms), std::end(Terms), [&](const char* term) + { + return std::strstr(env_p, term) != nullptr; + }); + return result; +#endif +} + + +// Detrmine if the terminal attached +// Source: https://github.com/agauniyal/rang/ +inline bool in_terminal(FILE* file) +{ + +#ifdef _WIN32 + return _isatty(_fileno(file)) ? true : false; +#else + return isatty(fileno(file)) ? true : false; +#endif +} +} //os +} //details +} //spdlog diff --git a/thirdparty/spdlog/details/pattern_formatter_impl.h b/thirdparty/spdlog/details/pattern_formatter_impl.h new file mode 100755 index 0000000..a73f5de --- /dev/null +++ b/thirdparty/spdlog/details/pattern_formatter_impl.h @@ -0,0 +1,686 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "../formatter.h" +#include "../details/log_msg.h" +#include "../details/os.h" +#include "../fmt/fmt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spdlog +{ +namespace details +{ +class flag_formatter +{ +public: + virtual ~flag_formatter() + {} + virtual void format(details::log_msg& msg, const std::tm& tm_time) = 0; +}; + +/////////////////////////////////////////////////////////////////////// +// name & level pattern appenders +/////////////////////////////////////////////////////////////////////// +namespace +{ +class name_formatter:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << *msg.logger_name; + } +}; +} + +// log level appender +class level_formatter:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << level::to_str(msg.level); + } +}; + +// short log level appender +class short_level_formatter:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << level::to_short_str(msg.level); + } +}; + +/////////////////////////////////////////////////////////////////////// +// Date time pattern appenders +/////////////////////////////////////////////////////////////////////// + +static const char* ampm(const tm& t) +{ + return t.tm_hour >= 12 ? "PM" : "AM"; +} + +static int to12h(const tm& t) +{ + return t.tm_hour > 12 ? t.tm_hour - 12 : t.tm_hour; +} + +//Abbreviated weekday name +static const std::string days[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; +class a_formatter:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << days[tm_time.tm_wday]; + } +}; + +//Full weekday name +static const std::string full_days[] { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; +class A_formatter:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << full_days[tm_time.tm_wday]; + } +}; + +//Abbreviated month +static const std::string months[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec" }; +class b_formatter:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << months[tm_time.tm_mon]; + } +}; + +//Full month name +static const std::string full_months[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; +class B_formatter:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << full_months[tm_time.tm_mon]; + } +}; + + +//write 2 ints separated by sep with padding of 2 +static fmt::MemoryWriter& pad_n_join(fmt::MemoryWriter& w, int v1, int v2, char sep) +{ + w << fmt::pad(v1, 2, '0') << sep << fmt::pad(v2, 2, '0'); + return w; +} + +//write 3 ints separated by sep with padding of 2 +static fmt::MemoryWriter& pad_n_join(fmt::MemoryWriter& w, int v1, int v2, int v3, char sep) +{ + w << fmt::pad(v1, 2, '0') << sep << fmt::pad(v2, 2, '0') << sep << fmt::pad(v3, 2, '0'); + return w; +} + + +//Date and time representation (Thu Aug 23 15:35:46 2014) +class c_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << days[tm_time.tm_wday] << ' ' << months[tm_time.tm_mon] << ' ' << tm_time.tm_mday << ' '; + pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, ':') << ' ' << tm_time.tm_year + 1900; + } +}; + + +// year - 2 digit +class C_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << fmt::pad(tm_time.tm_year % 100, 2, '0'); + } +}; + + + +// Short MM/DD/YY date, equivalent to %m/%d/%y 08/23/01 +class D_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + pad_n_join(msg.formatted, tm_time.tm_mon + 1, tm_time.tm_mday, tm_time.tm_year % 100, '/'); + } +}; + + +// year - 4 digit +class Y_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << tm_time.tm_year + 1900; + } +}; + +// month 1-12 +class m_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << fmt::pad(tm_time.tm_mon + 1, 2, '0'); + } +}; + +// day of month 1-31 +class d_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << fmt::pad(tm_time.tm_mday, 2, '0'); + } +}; + +// hours in 24 format 0-23 +class H_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << fmt::pad(tm_time.tm_hour, 2, '0'); + } +}; + +// hours in 12 format 1-12 +class I_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << fmt::pad(to12h(tm_time), 2, '0'); + } +}; + +// minutes 0-59 +class M_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << fmt::pad(tm_time.tm_min, 2, '0'); + } +}; + +// seconds 0-59 +class S_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << fmt::pad(tm_time.tm_sec, 2, '0'); + } +}; + +// milliseconds +class e_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + auto duration = msg.time.time_since_epoch(); + auto millis = std::chrono::duration_cast(duration).count() % 1000; + msg.formatted << fmt::pad(static_cast(millis), 3, '0'); + } +}; + +// microseconds +class f_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + auto duration = msg.time.time_since_epoch(); + auto micros = std::chrono::duration_cast(duration).count() % 1000000; + msg.formatted << fmt::pad(static_cast(micros), 6, '0'); + } +}; + +// nanoseconds +class F_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + auto duration = msg.time.time_since_epoch(); + auto ns = std::chrono::duration_cast(duration).count() % 1000000000; + msg.formatted << fmt::pad(static_cast(ns), 9, '0'); + } +}; + +class E_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + auto duration = msg.time.time_since_epoch(); + auto seconds = std::chrono::duration_cast(duration).count(); + msg.formatted << seconds; + } +}; + +// AM/PM +class p_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + msg.formatted << ampm(tm_time); + } +}; + + +// 12 hour clock 02:55:02 pm +class r_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + pad_n_join(msg.formatted, to12h(tm_time), tm_time.tm_min, tm_time.tm_sec, ':') << ' ' << ampm(tm_time); + } +}; + +// 24-hour HH:MM time, equivalent to %H:%M +class R_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, ':'); + } +}; + +// ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S +class T_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { + pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, ':'); + } +}; + +// ISO 8601 offset from UTC in timezone (+-HH:MM) +class z_formatter SPDLOG_FINAL:public flag_formatter +{ +public: + const std::chrono::seconds cache_refresh = std::chrono::seconds(5); + + z_formatter():_last_update(std::chrono::seconds(0)), _offset_minutes(0) + {} + z_formatter(const z_formatter&) = delete; + z_formatter& operator=(const z_formatter&) = delete; + + void format(details::log_msg& msg, const std::tm& tm_time) override + { +#ifdef _WIN32 + int total_minutes = get_cached_offset(msg, tm_time); +#else + // No need to chache under gcc, + // it is very fast (already stored in tm.tm_gmtoff) + int total_minutes = os::utc_minutes_offset(tm_time); +#endif + bool is_negative = total_minutes < 0; + char sign; + if (is_negative) + { + total_minutes = -total_minutes; + sign = '-'; + } + else + { + sign = '+'; + } + + int h = total_minutes / 60; + int m = total_minutes % 60; + msg.formatted << sign; + pad_n_join(msg.formatted, h, m, ':'); + } +private: + log_clock::time_point _last_update; + int _offset_minutes; + std::mutex _mutex; + + int get_cached_offset(const log_msg& msg, const std::tm& tm_time) + { + using namespace std::chrono; + std::lock_guard l(_mutex); + if (msg.time - _last_update >= cache_refresh) + { + _offset_minutes = os::utc_minutes_offset(tm_time); + _last_update = msg.time; + } + return _offset_minutes; + } +}; + + + +// Thread id +class t_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << msg.thread_id; + } +}; + +// Current pid +class pid_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << details::os::pid(); + } +}; + +// message counter formatter +class i_formatter SPDLOG_FINAL :public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << fmt::pad(msg.msg_id, 6, '0'); + } +}; + +class v_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << fmt::StringRef(msg.raw.data(), msg.raw.size()); + } +}; + +class ch_formatter SPDLOG_FINAL:public flag_formatter +{ +public: + explicit ch_formatter(char ch): _ch(ch) + {} + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << _ch; + } +private: + char _ch; +}; + + +//aggregate user chars to display as is +class aggregate_formatter SPDLOG_FINAL:public flag_formatter +{ +public: + aggregate_formatter() + {} + void add_ch(char ch) + { + _str += ch; + } + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << _str; + } +private: + std::string _str; +}; + +// Full info formatter +// pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v +class full_formatter SPDLOG_FINAL:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm& tm_time) override + { +#ifndef SPDLOG_NO_DATETIME + auto duration = msg.time.time_since_epoch(); + auto millis = std::chrono::duration_cast(duration).count() % 1000; + + /* Slower version(while still very fast - about 3.2 million lines/sec under 10 threads), + msg.formatted.write("[{:d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}.{:03d}] [{}] [{}] {} ", + tm_time.tm_year + 1900, + tm_time.tm_mon + 1, + tm_time.tm_mday, + tm_time.tm_hour, + tm_time.tm_min, + tm_time.tm_sec, + static_cast(millis), + msg.logger_name, + level::to_str(msg.level), + msg.raw.str());*/ + + + // Faster (albeit uglier) way to format the line (5.6 million lines/sec under 10 threads) + msg.formatted << '[' << static_cast(tm_time.tm_year + 1900) << '-' + << fmt::pad(static_cast(tm_time.tm_mon + 1), 2, '0') << '-' + << fmt::pad(static_cast(tm_time.tm_mday), 2, '0') << ' ' + << fmt::pad(static_cast(tm_time.tm_hour), 2, '0') << ':' + << fmt::pad(static_cast(tm_time.tm_min), 2, '0') << ':' + << fmt::pad(static_cast(tm_time.tm_sec), 2, '0') << '.' + << fmt::pad(static_cast(millis), 3, '0') << "] "; + + //no datetime needed +#else + (void)tm_time; +#endif + +#ifndef SPDLOG_NO_NAME + msg.formatted << '[' << *msg.logger_name << "] "; +#endif + + msg.formatted << '[' << level::to_str(msg.level) << "] "; + msg.formatted << fmt::StringRef(msg.raw.data(), msg.raw.size()); + } +}; + + + +} +} +/////////////////////////////////////////////////////////////////////////////// +// pattern_formatter inline impl +/////////////////////////////////////////////////////////////////////////////// +inline spdlog::pattern_formatter::pattern_formatter(const std::string& pattern, pattern_time_type pattern_time) + : _pattern_time(pattern_time) +{ + compile_pattern(pattern); +} + +inline void spdlog::pattern_formatter::compile_pattern(const std::string& pattern) +{ + auto end = pattern.end(); + std::unique_ptr user_chars; + for (auto it = pattern.begin(); it != end; ++it) + { + if (*it == '%') + { + if (user_chars) //append user chars found so far + _formatters.push_back(std::move(user_chars)); + + if (++it != end) + handle_flag(*it); + else + break; + } + else // chars not following the % sign should be displayed as is + { + if (!user_chars) + user_chars = std::unique_ptr(new details::aggregate_formatter()); + user_chars->add_ch(*it); + } + } + if (user_chars) //append raw chars found so far + { + _formatters.push_back(std::move(user_chars)); + } + +} +inline void spdlog::pattern_formatter::handle_flag(char flag) +{ + switch (flag) + { + // logger name + case 'n': + _formatters.push_back(std::unique_ptr(new details::name_formatter())); + break; + + case 'l': + _formatters.push_back(std::unique_ptr(new details::level_formatter())); + break; + + case 'L': + _formatters.push_back(std::unique_ptr(new details::short_level_formatter())); + break; + + case('t'): + _formatters.push_back(std::unique_ptr(new details::t_formatter())); + break; + + case('v'): + _formatters.push_back(std::unique_ptr(new details::v_formatter())); + break; + + case('a'): + _formatters.push_back(std::unique_ptr(new details::a_formatter())); + break; + + case('A'): + _formatters.push_back(std::unique_ptr(new details::A_formatter())); + break; + + case('b'): + case('h'): + _formatters.push_back(std::unique_ptr(new details::b_formatter())); + break; + + case('B'): + _formatters.push_back(std::unique_ptr(new details::B_formatter())); + break; + case('c'): + _formatters.push_back(std::unique_ptr(new details::c_formatter())); + break; + + case('C'): + _formatters.push_back(std::unique_ptr(new details::C_formatter())); + break; + + case('Y'): + _formatters.push_back(std::unique_ptr(new details::Y_formatter())); + break; + + case('D'): + case('x'): + + _formatters.push_back(std::unique_ptr(new details::D_formatter())); + break; + + case('m'): + _formatters.push_back(std::unique_ptr(new details::m_formatter())); + break; + + case('d'): + _formatters.push_back(std::unique_ptr(new details::d_formatter())); + break; + + case('H'): + _formatters.push_back(std::unique_ptr(new details::H_formatter())); + break; + + case('I'): + _formatters.push_back(std::unique_ptr(new details::I_formatter())); + break; + + case('M'): + _formatters.push_back(std::unique_ptr(new details::M_formatter())); + break; + + case('S'): + _formatters.push_back(std::unique_ptr(new details::S_formatter())); + break; + + case('e'): + _formatters.push_back(std::unique_ptr(new details::e_formatter())); + break; + + case('f'): + _formatters.push_back(std::unique_ptr(new details::f_formatter())); + break; + case('F'): + _formatters.push_back(std::unique_ptr(new details::F_formatter())); + break; + + case('E'): + _formatters.push_back(std::unique_ptr(new details::E_formatter())); + break; + + case('p'): + _formatters.push_back(std::unique_ptr(new details::p_formatter())); + break; + + case('r'): + _formatters.push_back(std::unique_ptr(new details::r_formatter())); + break; + + case('R'): + _formatters.push_back(std::unique_ptr(new details::R_formatter())); + break; + + case('T'): + case('X'): + _formatters.push_back(std::unique_ptr(new details::T_formatter())); + break; + + case('z'): + _formatters.push_back(std::unique_ptr(new details::z_formatter())); + break; + + case ('+'): + _formatters.push_back(std::unique_ptr(new details::full_formatter())); + break; + + case ('P'): + _formatters.push_back(std::unique_ptr(new details::pid_formatter())); + break; + + + case ('i'): + _formatters.push_back(std::unique_ptr(new details::i_formatter())); + break; + + default: //Unknown flag appears as is + _formatters.push_back(std::unique_ptr(new details::ch_formatter('%'))); + _formatters.push_back(std::unique_ptr(new details::ch_formatter(flag))); + break; + } +} + +inline std::tm spdlog::pattern_formatter::get_time(details::log_msg& msg) +{ + if (_pattern_time == pattern_time_type::local) + return details::os::localtime(log_clock::to_time_t(msg.time)); + else + return details::os::gmtime(log_clock::to_time_t(msg.time)); +} + +inline void spdlog::pattern_formatter::format(details::log_msg& msg) +{ + +#ifndef SPDLOG_NO_DATETIME + auto tm_time = get_time(msg); +#else + std::tm tm_time; +#endif + for (auto &f : _formatters) + { + f->format(msg, tm_time); + } + //write eol + msg.formatted.write(details::os::eol, details::os::eol_size); +} diff --git a/thirdparty/spdlog/details/registry.h b/thirdparty/spdlog/details/registry.h new file mode 100755 index 0000000..b68b9f5 --- /dev/null +++ b/thirdparty/spdlog/details/registry.h @@ -0,0 +1,225 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// Loggers registy of unique name->logger pointer +// An attempt to create a logger with an already existing name will be ignored +// If user requests a non existing logger, nullptr will be returned +// This class is thread safe + +#include "../details/null_mutex.h" +#include "../logger.h" +#include "../async_logger.h" +#include "../common.h" + +#include +#include +#include +#include +#include +#include + +namespace spdlog +{ +namespace details +{ +template class registry_t +{ +public: + + void register_logger(std::shared_ptr logger) + { + std::lock_guard lock(_mutex); + auto logger_name = logger->name(); + throw_if_exists(logger_name); + _loggers[logger_name] = logger; + } + + + std::shared_ptr get(const std::string& logger_name) + { + std::lock_guard lock(_mutex); + auto found = _loggers.find(logger_name); + return found == _loggers.end() ? nullptr : found->second; + } + + template + std::shared_ptr create(const std::string& logger_name, const It& sinks_begin, const It& sinks_end) + { + std::lock_guard lock(_mutex); + throw_if_exists(logger_name); + std::shared_ptr new_logger; + if (_async_mode) + new_logger = std::make_shared(logger_name, sinks_begin, sinks_end, _async_q_size, _overflow_policy, _worker_warmup_cb, _flush_interval_ms, _worker_teardown_cb); + else + new_logger = std::make_shared(logger_name, sinks_begin, sinks_end); + + if (_formatter) + new_logger->set_formatter(_formatter); + + if (_err_handler) + new_logger->set_error_handler(_err_handler); + + new_logger->set_level(_level); + new_logger->flush_on(_flush_level); + + + //Add to registry + _loggers[logger_name] = new_logger; + return new_logger; + } + + template + std::shared_ptr create_async(const std::string& logger_name, size_t queue_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb, const It& sinks_begin, const It& sinks_end) + { + std::lock_guard lock(_mutex); + throw_if_exists(logger_name); + auto new_logger = std::make_shared(logger_name, sinks_begin, sinks_end, queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb); + + if (_formatter) + new_logger->set_formatter(_formatter); + + if (_err_handler) + new_logger->set_error_handler(_err_handler); + + new_logger->set_level(_level); + new_logger->flush_on(_flush_level); + + //Add to registry + _loggers[logger_name] = new_logger; + return new_logger; + } + + void apply_all(std::function)> fun) + { + std::lock_guard lock(_mutex); + for (auto &l : _loggers) + fun(l.second); + } + + void drop(const std::string& logger_name) + { + std::lock_guard lock(_mutex); + _loggers.erase(logger_name); + } + + void drop_all() + { + std::lock_guard lock(_mutex); + _loggers.clear(); + } + std::shared_ptr create(const std::string& logger_name, sinks_init_list sinks) + { + return create(logger_name, sinks.begin(), sinks.end()); + } + + std::shared_ptr create(const std::string& logger_name, sink_ptr sink) + { + return create(logger_name, { sink }); + } + + std::shared_ptr create_async(const std::string& logger_name, size_t queue_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb, sinks_init_list sinks) + { + return create_async(logger_name, queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb, sinks.begin(), sinks.end()); + } + + std::shared_ptr create_async(const std::string& logger_name, size_t queue_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb, sink_ptr sink) + { + return create_async(logger_name, queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb, { sink }); + } + + void formatter(formatter_ptr f) + { + std::lock_guard lock(_mutex); + _formatter = f; + for (auto& l : _loggers) + l.second->set_formatter(_formatter); + } + + void set_pattern(const std::string& pattern) + { + std::lock_guard lock(_mutex); + _formatter = std::make_shared(pattern); + for (auto& l : _loggers) + l.second->set_formatter(_formatter); + } + + void set_level(level::level_enum log_level) + { + std::lock_guard lock(_mutex); + for (auto& l : _loggers) + l.second->set_level(log_level); + _level = log_level; + } + + void flush_on(level::level_enum log_level) + { + std::lock_guard lock(_mutex); + for (auto& l : _loggers) + l.second->flush_on(log_level); + _flush_level = log_level; + } + + void set_error_handler(log_err_handler handler) + { + for (auto& l : _loggers) + l.second->set_error_handler(handler); + _err_handler = handler; + } + + void set_async_mode(size_t q_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb) + { + std::lock_guard lock(_mutex); + _async_mode = true; + _async_q_size = q_size; + _overflow_policy = overflow_policy; + _worker_warmup_cb = worker_warmup_cb; + _flush_interval_ms = flush_interval_ms; + _worker_teardown_cb = worker_teardown_cb; + } + + void set_sync_mode() + { + std::lock_guard lock(_mutex); + _async_mode = false; + } + + static registry_t& instance() + { + static registry_t s_instance; + return s_instance; + } + +private: + registry_t() {} + registry_t(const registry_t&) = delete; + registry_t& operator=(const registry_t&) = delete; + + void throw_if_exists(const std::string &logger_name) + { + if (_loggers.find(logger_name) != _loggers.end()) + throw spdlog_ex("logger with name '" + logger_name + "' already exists"); + } + Mutex _mutex; + std::unordered_map > _loggers; + formatter_ptr _formatter; + level::level_enum _level = level::info; + level::level_enum _flush_level = level::off; + log_err_handler _err_handler; + bool _async_mode = false; + size_t _async_q_size = 0; + async_overflow_policy _overflow_policy = async_overflow_policy::block_retry; + std::function _worker_warmup_cb = nullptr; + std::chrono::milliseconds _flush_interval_ms; + std::function _worker_teardown_cb = nullptr; +}; +#ifdef SPDLOG_NO_REGISTRY_MUTEX +typedef registry_t registry; +#else +typedef registry_t registry; +#endif +} +} diff --git a/thirdparty/spdlog/details/spdlog_impl.h b/thirdparty/spdlog/details/spdlog_impl.h new file mode 100755 index 0000000..25c7b7e --- /dev/null +++ b/thirdparty/spdlog/details/spdlog_impl.h @@ -0,0 +1,268 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// +// Global registry functions +// +#include "../spdlog.h" +#include "../details/registry.h" +#include "../sinks/file_sinks.h" +#include "../sinks/stdout_sinks.h" +#ifdef SPDLOG_ENABLE_SYSLOG +#include "../sinks/syslog_sink.h" +#endif + +#ifdef _WIN32 +#include "../sinks/wincolor_sink.h" +#else +#include "../sinks/ansicolor_sink.h" +#endif + + +#ifdef __ANDROID__ +#include "../sinks/android_sink.h" +#endif + +#include +#include +#include +#include + +inline void spdlog::register_logger(std::shared_ptr logger) +{ + return details::registry::instance().register_logger(logger); +} + +inline std::shared_ptr spdlog::get(const std::string& name) +{ + return details::registry::instance().get(name); +} + +inline void spdlog::drop(const std::string &name) +{ + details::registry::instance().drop(name); +} + +// Create multi/single threaded simple file logger +inline std::shared_ptr spdlog::basic_logger_mt(const std::string& logger_name, const filename_t& filename, bool truncate) +{ + return create(logger_name, filename, truncate); +} + +inline std::shared_ptr spdlog::basic_logger_st(const std::string& logger_name, const filename_t& filename, bool truncate) +{ + return create(logger_name, filename, truncate); +} + +// Create multi/single threaded rotating file logger +inline std::shared_ptr spdlog::rotating_logger_mt(const std::string& logger_name, const filename_t& filename, size_t max_file_size, size_t max_files) +{ + return create(logger_name, filename, max_file_size, max_files); +} + +inline std::shared_ptr spdlog::rotating_logger_st(const std::string& logger_name, const filename_t& filename, size_t max_file_size, size_t max_files) +{ + return create(logger_name, filename, max_file_size, max_files); +} + +// Create file logger which creates new file at midnight): +inline std::shared_ptr spdlog::daily_logger_mt(const std::string& logger_name, const filename_t& filename, int hour, int minute) +{ + return create(logger_name, filename, hour, minute); +} + +inline std::shared_ptr spdlog::daily_logger_st(const std::string& logger_name, const filename_t& filename, int hour, int minute) +{ + return create(logger_name, filename, hour, minute); +} + + +// +// stdout/stderr loggers +// +inline std::shared_ptr spdlog::stdout_logger_mt(const std::string& logger_name) +{ + return spdlog::details::registry::instance().create(logger_name, spdlog::sinks::stdout_sink_mt::instance()); +} + +inline std::shared_ptr spdlog::stdout_logger_st(const std::string& logger_name) +{ + return spdlog::details::registry::instance().create(logger_name, spdlog::sinks::stdout_sink_st::instance()); +} + +inline std::shared_ptr spdlog::stderr_logger_mt(const std::string& logger_name) +{ + return spdlog::details::registry::instance().create(logger_name, spdlog::sinks::stderr_sink_mt::instance()); +} + +inline std::shared_ptr spdlog::stderr_logger_st(const std::string& logger_name) +{ + return spdlog::details::registry::instance().create(logger_name, spdlog::sinks::stderr_sink_st::instance()); +} + +// +// stdout/stderr color loggers +// +#ifdef _WIN32 +inline std::shared_ptr spdlog::stdout_color_mt(const std::string& logger_name) +{ + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); +} + +inline std::shared_ptr spdlog::stdout_color_st(const std::string& logger_name) +{ + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); +} + +inline std::shared_ptr spdlog::stderr_color_mt(const std::string& logger_name) +{ + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); +} + + +inline std::shared_ptr spdlog::stderr_color_st(const std::string& logger_name) +{ + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); +} + +#else //ansi terminal colors + +inline std::shared_ptr spdlog::stdout_color_mt(const std::string& logger_name) +{ + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); +} + +inline std::shared_ptr spdlog::stdout_color_st(const std::string& logger_name) +{ + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); +} + +inline std::shared_ptr spdlog::stderr_color_mt(const std::string& logger_name) +{ + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); +} + +inline std::shared_ptr spdlog::stderr_color_st(const std::string& logger_name) +{ + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); +} +#endif + +#ifdef SPDLOG_ENABLE_SYSLOG +// Create syslog logger +inline std::shared_ptr spdlog::syslog_logger(const std::string& logger_name, const std::string& syslog_ident, int syslog_option, int syslog_facility) +{ + return create(logger_name, syslog_ident, syslog_option, syslog_facility); +} +#endif + +#ifdef __ANDROID__ +inline std::shared_ptr spdlog::android_logger(const std::string& logger_name, const std::string& tag) +{ + return create(logger_name, tag); +} +#endif + +// Create and register a logger a single sink +inline std::shared_ptr spdlog::create(const std::string& logger_name, const spdlog::sink_ptr& sink) +{ + return details::registry::instance().create(logger_name, sink); +} + +//Create logger with multiple sinks + +inline std::shared_ptr spdlog::create(const std::string& logger_name, spdlog::sinks_init_list sinks) +{ + return details::registry::instance().create(logger_name, sinks); +} + + +template +inline std::shared_ptr spdlog::create(const std::string& logger_name, Args... args) +{ + sink_ptr sink = std::make_shared(args...); + return details::registry::instance().create(logger_name, { sink }); +} + + +template +inline std::shared_ptr spdlog::create(const std::string& logger_name, const It& sinks_begin, const It& sinks_end) +{ + return details::registry::instance().create(logger_name, sinks_begin, sinks_end); +} + +// Create and register an async logger with a single sink +inline std::shared_ptr spdlog::create_async(const std::string& logger_name, const sink_ptr& sink, size_t queue_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb) +{ + return details::registry::instance().create_async(logger_name, queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb, sink); +} + +// Create and register an async logger with multiple sinks +inline std::shared_ptr spdlog::create_async(const std::string& logger_name, sinks_init_list sinks, size_t queue_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb ) +{ + return details::registry::instance().create_async(logger_name, queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb, sinks); +} + +template +inline std::shared_ptr spdlog::create_async(const std::string& logger_name, const It& sinks_begin, const It& sinks_end, size_t queue_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb) +{ + return details::registry::instance().create_async(logger_name, queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb, sinks_begin, sinks_end); +} + +inline void spdlog::set_formatter(spdlog::formatter_ptr f) +{ + details::registry::instance().formatter(f); +} + +inline void spdlog::set_pattern(const std::string& format_string) +{ + return details::registry::instance().set_pattern(format_string); +} + +inline void spdlog::set_level(level::level_enum log_level) +{ + return details::registry::instance().set_level(log_level); +} + +inline void spdlog::flush_on(level::level_enum log_level) +{ + return details::registry::instance().flush_on(log_level); +} + +inline void spdlog::set_error_handler(log_err_handler handler) +{ + return details::registry::instance().set_error_handler(handler); +} + + +inline void spdlog::set_async_mode(size_t queue_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb) +{ + details::registry::instance().set_async_mode(queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb); +} + +inline void spdlog::set_sync_mode() +{ + details::registry::instance().set_sync_mode(); +} + +inline void spdlog::apply_all(std::function)> fun) +{ + details::registry::instance().apply_all(fun); +} + +inline void spdlog::drop_all() +{ + details::registry::instance().drop_all(); +} diff --git a/thirdparty/spdlog/fmt/bundled/LICENSE.rst b/thirdparty/spdlog/fmt/bundled/LICENSE.rst new file mode 100755 index 0000000..eb6be65 --- /dev/null +++ b/thirdparty/spdlog/fmt/bundled/LICENSE.rst @@ -0,0 +1,23 @@ +Copyright (c) 2012 - 2016, Victor Zverovich + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/thirdparty/spdlog/fmt/bundled/format.cc b/thirdparty/spdlog/fmt/bundled/format.cc new file mode 100755 index 0000000..09d2ea9 --- /dev/null +++ b/thirdparty/spdlog/fmt/bundled/format.cc @@ -0,0 +1,535 @@ +/* + Formatting library for C++ + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "format.h" + +#include + +#include +#include +#include +#include +#include +#include // for std::ptrdiff_t + +#if defined(_WIN32) && defined(__MINGW32__) +# include +#endif + +#if FMT_USE_WINDOWS_H +# if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN) +# define WIN32_LEAN_AND_MEAN +# endif +# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX) +# include +# else +# define NOMINMAX +# include +# undef NOMINMAX +# endif +#endif + +#if FMT_EXCEPTIONS +# define FMT_TRY try +# define FMT_CATCH(x) catch (x) +#else +# define FMT_TRY if (true) +# define FMT_CATCH(x) if (false) +#endif + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4127) // conditional expression is constant +# pragma warning(disable: 4702) // unreachable code +// Disable deprecation warning for strerror. The latter is not called but +// MSVC fails to detect it. +# pragma warning(disable: 4996) +#endif + +// Dummy implementations of strerror_r and strerror_s called if corresponding +// system functions are not available. +static inline fmt::internal::Null<> strerror_r(int, char *, ...) { + return fmt::internal::Null<>(); +} +static inline fmt::internal::Null<> strerror_s(char *, std::size_t, ...) { + return fmt::internal::Null<>(); +} + +namespace fmt { + +FMT_FUNC internal::RuntimeError::~RuntimeError() FMT_DTOR_NOEXCEPT {} +FMT_FUNC FormatError::~FormatError() FMT_DTOR_NOEXCEPT {} +FMT_FUNC SystemError::~SystemError() FMT_DTOR_NOEXCEPT {} + +namespace { + +#ifndef _MSC_VER +# define FMT_SNPRINTF snprintf +#else // _MSC_VER +inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) { + va_list args; + va_start(args, format); + int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); + va_end(args); + return result; +} +# define FMT_SNPRINTF fmt_snprintf +#endif // _MSC_VER + +#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) +# define FMT_SWPRINTF snwprintf +#else +# define FMT_SWPRINTF swprintf +#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) + +const char RESET_COLOR[] = "\x1b[0m"; + +typedef void (*FormatFunc)(Writer &, int, StringRef); + +// Portable thread-safe version of strerror. +// Sets buffer to point to a string describing the error code. +// This can be either a pointer to a string stored in buffer, +// or a pointer to some static immutable string. +// Returns one of the following values: +// 0 - success +// ERANGE - buffer is not large enough to store the error message +// other - failure +// Buffer should be at least of size 1. +int safe_strerror( + int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT { + FMT_ASSERT(buffer != 0 && buffer_size != 0, "invalid buffer"); + + class StrError { + private: + int error_code_; + char *&buffer_; + std::size_t buffer_size_; + + // A noop assignment operator to avoid bogus warnings. + void operator=(const StrError &) {} + + // Handle the result of XSI-compliant version of strerror_r. + int handle(int result) { + // glibc versions before 2.13 return result in errno. + return result == -1 ? errno : result; + } + + // Handle the result of GNU-specific version of strerror_r. + int handle(char *message) { + // If the buffer is full then the message is probably truncated. + if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1) + return ERANGE; + buffer_ = message; + return 0; + } + + // Handle the case when strerror_r is not available. + int handle(internal::Null<>) { + return fallback(strerror_s(buffer_, buffer_size_, error_code_)); + } + + // Fallback to strerror_s when strerror_r is not available. + int fallback(int result) { + // If the buffer is full then the message is probably truncated. + return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? + ERANGE : result; + } + + // Fallback to strerror if strerror_r and strerror_s are not available. + int fallback(internal::Null<>) { + errno = 0; + buffer_ = strerror(error_code_); + return errno; + } + + public: + StrError(int err_code, char *&buf, std::size_t buf_size) + : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {} + + int run() { + // Suppress a warning about unused strerror_r. + strerror_r(0, FMT_NULL, ""); + return handle(strerror_r(error_code_, buffer_, buffer_size_)); + } + }; + return StrError(error_code, buffer, buffer_size).run(); +} + +void format_error_code(Writer &out, int error_code, + StringRef message) FMT_NOEXCEPT { + // Report error code making sure that the output fits into + // INLINE_BUFFER_SIZE to avoid dynamic memory allocation and potential + // bad_alloc. + out.clear(); + static const char SEP[] = ": "; + static const char ERROR_STR[] = "error "; + // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. + std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; + typedef internal::IntTraits::MainType MainType; + MainType abs_value = static_cast(error_code); + if (internal::is_negative(error_code)) { + abs_value = 0 - abs_value; + ++error_code_size; + } + error_code_size += internal::count_digits(abs_value); + if (message.size() <= internal::INLINE_BUFFER_SIZE - error_code_size) + out << message << SEP; + out << ERROR_STR << error_code; + assert(out.size() <= internal::INLINE_BUFFER_SIZE); +} + +void report_error(FormatFunc func, int error_code, + StringRef message) FMT_NOEXCEPT { + MemoryWriter full_message; + func(full_message, error_code, message); + // Use Writer::data instead of Writer::c_str to avoid potential memory + // allocation. + std::fwrite(full_message.data(), full_message.size(), 1, stderr); + std::fputc('\n', stderr); +} +} // namespace + +FMT_FUNC void SystemError::init( + int err_code, CStringRef format_str, ArgList args) { + error_code_ = err_code; + MemoryWriter w; + format_system_error(w, err_code, format(format_str, args)); + std::runtime_error &base = *this; + base = std::runtime_error(w.str()); +} + +template +int internal::CharTraits::format_float( + char *buffer, std::size_t size, const char *format, + unsigned width, int precision, T value) { + if (width == 0) { + return precision < 0 ? + FMT_SNPRINTF(buffer, size, format, value) : + FMT_SNPRINTF(buffer, size, format, precision, value); + } + return precision < 0 ? + FMT_SNPRINTF(buffer, size, format, width, value) : + FMT_SNPRINTF(buffer, size, format, width, precision, value); +} + +template +int internal::CharTraits::format_float( + wchar_t *buffer, std::size_t size, const wchar_t *format, + unsigned width, int precision, T value) { + if (width == 0) { + return precision < 0 ? + FMT_SWPRINTF(buffer, size, format, value) : + FMT_SWPRINTF(buffer, size, format, precision, value); + } + return precision < 0 ? + FMT_SWPRINTF(buffer, size, format, width, value) : + FMT_SWPRINTF(buffer, size, format, width, precision, value); +} + +template +const char internal::BasicData::DIGITS[] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + +#define FMT_POWERS_OF_10(factor) \ + factor * 10, \ + factor * 100, \ + factor * 1000, \ + factor * 10000, \ + factor * 100000, \ + factor * 1000000, \ + factor * 10000000, \ + factor * 100000000, \ + factor * 1000000000 + +template +const uint32_t internal::BasicData::POWERS_OF_10_32[] = { + 0, FMT_POWERS_OF_10(1) +}; + +template +const uint64_t internal::BasicData::POWERS_OF_10_64[] = { + 0, + FMT_POWERS_OF_10(1), + FMT_POWERS_OF_10(ULongLong(1000000000)), + // Multiply several constants instead of using a single long long constant + // to avoid warnings about C++98 not supporting long long. + ULongLong(1000000000) * ULongLong(1000000000) * 10 +}; + +FMT_FUNC void internal::report_unknown_type(char code, const char *type) { + (void)type; + if (std::isprint(static_cast(code))) { + FMT_THROW(FormatError( + format("unknown format code '{}' for {}", code, type))); + } + FMT_THROW(FormatError( + format("unknown format code '\\x{:02x}' for {}", + static_cast(code), type))); +} + +#if FMT_USE_WINDOWS_H + +FMT_FUNC internal::UTF8ToUTF16::UTF8ToUTF16(StringRef s) { + static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16"; + if (s.size() > INT_MAX) + FMT_THROW(WindowsError(ERROR_INVALID_PARAMETER, ERROR_MSG)); + int s_size = static_cast(s.size()); + int length = MultiByteToWideChar( + CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0); + if (length == 0) + FMT_THROW(WindowsError(GetLastError(), ERROR_MSG)); + buffer_.resize(length + 1); + length = MultiByteToWideChar( + CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length); + if (length == 0) + FMT_THROW(WindowsError(GetLastError(), ERROR_MSG)); + buffer_[length] = 0; +} + +FMT_FUNC internal::UTF16ToUTF8::UTF16ToUTF8(WStringRef s) { + if (int error_code = convert(s)) { + FMT_THROW(WindowsError(error_code, + "cannot convert string from UTF-16 to UTF-8")); + } +} + +FMT_FUNC int internal::UTF16ToUTF8::convert(WStringRef s) { + if (s.size() > INT_MAX) + return ERROR_INVALID_PARAMETER; + int s_size = static_cast(s.size()); + int length = WideCharToMultiByte( + CP_UTF8, 0, s.data(), s_size, FMT_NULL, 0, FMT_NULL, FMT_NULL); + if (length == 0) + return GetLastError(); + buffer_.resize(length + 1); + length = WideCharToMultiByte( + CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL); + if (length == 0) + return GetLastError(); + buffer_[length] = 0; + return 0; +} + +FMT_FUNC void WindowsError::init( + int err_code, CStringRef format_str, ArgList args) { + error_code_ = err_code; + MemoryWriter w; + internal::format_windows_error(w, err_code, format(format_str, args)); + std::runtime_error &base = *this; + base = std::runtime_error(w.str()); +} + +FMT_FUNC void internal::format_windows_error( + Writer &out, int error_code, StringRef message) FMT_NOEXCEPT { + FMT_TRY { + MemoryBuffer buffer; + buffer.resize(INLINE_BUFFER_SIZE); + for (;;) { + wchar_t *system_message = &buffer[0]; + int result = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + FMT_NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + system_message, static_cast(buffer.size()), FMT_NULL); + if (result != 0) { + UTF16ToUTF8 utf8_message; + if (utf8_message.convert(system_message) == ERROR_SUCCESS) { + out << message << ": " << utf8_message; + return; + } + break; + } + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + break; // Can't get error message, report error code instead. + buffer.resize(buffer.size() * 2); + } + } FMT_CATCH(...) {} + fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32. +} + +#endif // FMT_USE_WINDOWS_H + +FMT_FUNC void format_system_error( + Writer &out, int error_code, StringRef message) FMT_NOEXCEPT { + FMT_TRY { + internal::MemoryBuffer buffer; + buffer.resize(internal::INLINE_BUFFER_SIZE); + for (;;) { + char *system_message = &buffer[0]; + int result = safe_strerror(error_code, system_message, buffer.size()); + if (result == 0) { + out << message << ": " << system_message; + return; + } + if (result != ERANGE) + break; // Can't get error message, report error code instead. + buffer.resize(buffer.size() * 2); + } + } FMT_CATCH(...) {} + fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32. +} + +template +void internal::ArgMap::init(const ArgList &args) { + if (!map_.empty()) + return; + typedef internal::NamedArg NamedArg; + const NamedArg *named_arg = FMT_NULL; + bool use_values = + args.type(ArgList::MAX_PACKED_ARGS - 1) == internal::Arg::NONE; + if (use_values) { + for (unsigned i = 0;/*nothing*/; ++i) { + internal::Arg::Type arg_type = args.type(i); + switch (arg_type) { + case internal::Arg::NONE: + return; + case internal::Arg::NAMED_ARG: + named_arg = static_cast(args.values_[i].pointer); + map_.push_back(Pair(named_arg->name, *named_arg)); + break; + default: + /*nothing*/; + } + } + return; + } + for (unsigned i = 0; i != ArgList::MAX_PACKED_ARGS; ++i) { + internal::Arg::Type arg_type = args.type(i); + if (arg_type == internal::Arg::NAMED_ARG) { + named_arg = static_cast(args.args_[i].pointer); + map_.push_back(Pair(named_arg->name, *named_arg)); + } + } + for (unsigned i = ArgList::MAX_PACKED_ARGS;/*nothing*/; ++i) { + switch (args.args_[i].type) { + case internal::Arg::NONE: + return; + case internal::Arg::NAMED_ARG: + named_arg = static_cast(args.args_[i].pointer); + map_.push_back(Pair(named_arg->name, *named_arg)); + break; + default: + /*nothing*/; + } + } +} + +template +void internal::FixedBuffer::grow(std::size_t) { + FMT_THROW(std::runtime_error("buffer overflow")); +} + +FMT_FUNC internal::Arg internal::FormatterBase::do_get_arg( + unsigned arg_index, const char *&error) { + internal::Arg arg = args_[arg_index]; + switch (arg.type) { + case internal::Arg::NONE: + error = "argument index out of range"; + break; + case internal::Arg::NAMED_ARG: + arg = *static_cast(arg.pointer); + break; + default: + /*nothing*/; + } + return arg; +} + +FMT_FUNC void report_system_error( + int error_code, fmt::StringRef message) FMT_NOEXCEPT { + // 'fmt::' is for bcc32. + report_error(format_system_error, error_code, message); +} + +#if FMT_USE_WINDOWS_H +FMT_FUNC void report_windows_error( + int error_code, fmt::StringRef message) FMT_NOEXCEPT { + // 'fmt::' is for bcc32. + report_error(internal::format_windows_error, error_code, message); +} +#endif + +FMT_FUNC void print(std::FILE *f, CStringRef format_str, ArgList args) { + MemoryWriter w; + w.write(format_str, args); + std::fwrite(w.data(), 1, w.size(), f); +} + +FMT_FUNC void print(CStringRef format_str, ArgList args) { + print(stdout, format_str, args); +} + +FMT_FUNC void print_colored(Color c, CStringRef format, ArgList args) { + char escape[] = "\x1b[30m"; + escape[3] = static_cast('0' + c); + std::fputs(escape, stdout); + print(format, args); + std::fputs(RESET_COLOR, stdout); +} + +#ifndef FMT_HEADER_ONLY + +template struct internal::BasicData; + +// Explicit instantiations for char. + +template void internal::FixedBuffer::grow(std::size_t); + +template void internal::ArgMap::init(const ArgList &args); + +template FMT_API int internal::CharTraits::format_float( + char *buffer, std::size_t size, const char *format, + unsigned width, int precision, double value); + +template FMT_API int internal::CharTraits::format_float( + char *buffer, std::size_t size, const char *format, + unsigned width, int precision, long double value); + +// Explicit instantiations for wchar_t. + +template void internal::FixedBuffer::grow(std::size_t); + +template void internal::ArgMap::init(const ArgList &args); + +template FMT_API int internal::CharTraits::format_float( + wchar_t *buffer, std::size_t size, const wchar_t *format, + unsigned width, int precision, double value); + +template FMT_API int internal::CharTraits::format_float( + wchar_t *buffer, std::size_t size, const wchar_t *format, + unsigned width, int precision, long double value); + +#endif // FMT_HEADER_ONLY + +} // namespace fmt + +#ifdef _MSC_VER +# pragma warning(pop) +#endif diff --git a/thirdparty/spdlog/fmt/bundled/format.h b/thirdparty/spdlog/fmt/bundled/format.h new file mode 100755 index 0000000..91f4348 --- /dev/null +++ b/thirdparty/spdlog/fmt/bundled/format.h @@ -0,0 +1,4659 @@ +/* + Formatting library for C++ + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef FMT_FORMAT_H_ +#define FMT_FORMAT_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for std::pair + +// The fmt library version in the form major * 10000 + minor * 100 + patch. +#define FMT_VERSION 40000 + +#ifdef _SECURE_SCL +# define FMT_SECURE_SCL _SECURE_SCL +#else +# define FMT_SECURE_SCL 0 +#endif + +#if FMT_SECURE_SCL +# include +#endif + +#ifdef _MSC_VER +# define FMT_MSC_VER _MSC_VER +#else +# define FMT_MSC_VER 0 +#endif + +#if FMT_MSC_VER && FMT_MSC_VER <= 1500 +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +typedef __int64 intmax_t; +#else +#include +#endif + +#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) +# ifdef FMT_EXPORT +# define FMT_API __declspec(dllexport) +# elif defined(FMT_SHARED) +# define FMT_API __declspec(dllimport) +# endif +#endif +#ifndef FMT_API +# define FMT_API +#endif + +#ifdef __GNUC__ +# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +# define FMT_GCC_EXTENSION __extension__ +# if FMT_GCC_VERSION >= 406 +# pragma GCC diagnostic push +// Disable the warning about "long long" which is sometimes reported even +// when using __extension__. +# pragma GCC diagnostic ignored "-Wlong-long" +// Disable the warning about declaration shadowing because it affects too +// many valid cases. +# pragma GCC diagnostic ignored "-Wshadow" +// Disable the warning about implicit conversions that may change the sign of +// an integer; silencing it otherwise would require many explicit casts. +# pragma GCC diagnostic ignored "-Wsign-conversion" +# endif +# if __cplusplus >= 201103L || defined __GXX_EXPERIMENTAL_CXX0X__ +# define FMT_HAS_GXX_CXX11 1 +# endif +#else +# define FMT_GCC_EXTENSION +#endif + +#if defined(__INTEL_COMPILER) +# define FMT_ICC_VERSION __INTEL_COMPILER +#elif defined(__ICL) +# define FMT_ICC_VERSION __ICL +#endif + +#if defined(__clang__) && !defined(FMT_ICC_VERSION) +# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdocumentation-unknown-command" +# pragma clang diagnostic ignored "-Wpadded" +#endif + +#ifdef __GNUC_LIBSTD__ +# define FMT_GNUC_LIBSTD_VERSION (__GNUC_LIBSTD__ * 100 + __GNUC_LIBSTD_MINOR__) +#endif + +#ifdef __has_feature +# define FMT_HAS_FEATURE(x) __has_feature(x) +#else +# define FMT_HAS_FEATURE(x) 0 +#endif + +#ifdef __has_builtin +# define FMT_HAS_BUILTIN(x) __has_builtin(x) +#else +# define FMT_HAS_BUILTIN(x) 0 +#endif + +#ifdef __has_cpp_attribute +# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +# define FMT_HAS_CPP_ATTRIBUTE(x) 0 +#endif + +#ifndef FMT_USE_VARIADIC_TEMPLATES +// Variadic templates are available in GCC since version 4.4 +// (http://gcc.gnu.org/projects/cxx0x.html) and in Visual C++ +// since version 2013. +# define FMT_USE_VARIADIC_TEMPLATES \ + (FMT_HAS_FEATURE(cxx_variadic_templates) || \ + (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800) +#endif + +#ifndef FMT_USE_RVALUE_REFERENCES +// Don't use rvalue references when compiling with clang and an old libstdc++ +// as the latter doesn't provide std::move. +# if defined(FMT_GNUC_LIBSTD_VERSION) && FMT_GNUC_LIBSTD_VERSION <= 402 +# define FMT_USE_RVALUE_REFERENCES 0 +# else +# define FMT_USE_RVALUE_REFERENCES \ + (FMT_HAS_FEATURE(cxx_rvalue_references) || \ + (FMT_GCC_VERSION >= 403 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1600) +# endif +#endif + +// Check if exceptions are disabled. +#if defined(__GNUC__) && !defined(__EXCEPTIONS) +# define FMT_EXCEPTIONS 0 +#endif +#if FMT_MSC_VER && !_HAS_EXCEPTIONS +# define FMT_EXCEPTIONS 0 +#endif +#ifndef FMT_EXCEPTIONS +# define FMT_EXCEPTIONS 1 +#endif + +#ifndef FMT_THROW +# if FMT_EXCEPTIONS +# define FMT_THROW(x) throw x +# else +# define FMT_THROW(x) assert(false) +# endif +#endif + +// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature). +#ifndef FMT_USE_NOEXCEPT +# define FMT_USE_NOEXCEPT 0 +#endif + +#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \ + (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \ + FMT_MSC_VER >= 1900 +# define FMT_DETECTED_NOEXCEPT noexcept +#else +# define FMT_DETECTED_NOEXCEPT throw() +#endif + +#ifndef FMT_NOEXCEPT +# if FMT_EXCEPTIONS +# define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT +# else +# define FMT_NOEXCEPT +# endif +#endif + +// This is needed because GCC still uses throw() in its headers when exceptions +// are disabled. +#if FMT_GCC_VERSION +# define FMT_DTOR_NOEXCEPT FMT_DETECTED_NOEXCEPT +#else +# define FMT_DTOR_NOEXCEPT FMT_NOEXCEPT +#endif + +#ifndef FMT_OVERRIDE +# if (defined(FMT_USE_OVERRIDE) && FMT_USE_OVERRIDE) || FMT_HAS_FEATURE(cxx_override) || \ + (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \ + FMT_MSC_VER >= 1900 +# define FMT_OVERRIDE override +# else +# define FMT_OVERRIDE +# endif +#endif + +#ifndef FMT_NULL +# if FMT_HAS_FEATURE(cxx_nullptr) || \ + (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \ + FMT_MSC_VER >= 1600 +# define FMT_NULL nullptr +# else +# define FMT_NULL NULL +# endif +#endif + +// A macro to disallow the copy constructor and operator= functions +// This should be used in the private: declarations for a class +#ifndef FMT_USE_DELETED_FUNCTIONS +# define FMT_USE_DELETED_FUNCTIONS 0 +#endif + +#if FMT_USE_DELETED_FUNCTIONS || FMT_HAS_FEATURE(cxx_deleted_functions) || \ + (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800 +# define FMT_DELETED_OR_UNDEFINED = delete +# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + TypeName& operator=(const TypeName&) = delete +#else +# define FMT_DELETED_OR_UNDEFINED +# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + TypeName& operator=(const TypeName&) +#endif + +#ifndef FMT_USE_DEFAULTED_FUNCTIONS +# define FMT_USE_DEFAULTED_FUNCTIONS 0 +#endif + +#ifndef FMT_DEFAULTED_COPY_CTOR +# if FMT_USE_DEFAULTED_FUNCTIONS || FMT_HAS_FEATURE(cxx_defaulted_functions) || \ + (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800 +# define FMT_DEFAULTED_COPY_CTOR(TypeName) \ + TypeName(const TypeName&) = default; +# else +# define FMT_DEFAULTED_COPY_CTOR(TypeName) +# endif +#endif + +#ifndef FMT_USE_USER_DEFINED_LITERALS +// All compilers which support UDLs also support variadic templates. This +// makes the fmt::literals implementation easier. However, an explicit check +// for variadic templates is added here just in case. +// For Intel's compiler both it and the system gcc/msc must support UDLs. +# define FMT_USE_USER_DEFINED_LITERALS \ + FMT_USE_VARIADIC_TEMPLATES && FMT_USE_RVALUE_REFERENCES && \ + (FMT_HAS_FEATURE(cxx_user_literals) || \ + (FMT_GCC_VERSION >= 407 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900) && \ + (!defined(FMT_ICC_VERSION) || FMT_ICC_VERSION >= 1500) +#endif + +#ifndef FMT_USE_EXTERN_TEMPLATES +# define FMT_USE_EXTERN_TEMPLATES \ + (FMT_CLANG_VERSION >= 209 || (FMT_GCC_VERSION >= 303 && FMT_HAS_GXX_CXX11)) +#endif + +#ifdef FMT_HEADER_ONLY +// If header only do not use extern templates. +# undef FMT_USE_EXTERN_TEMPLATES +# define FMT_USE_EXTERN_TEMPLATES 0 +#endif + +#ifndef FMT_ASSERT +# define FMT_ASSERT(condition, message) assert((condition) && message) +#endif + +// __builtin_clz is broken in clang with Microsoft CodeGen: +// https://github.com/fmtlib/fmt/issues/519 +#ifndef _MSC_VER +# if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clz) +# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) +# endif + +# if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clzll) +# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) +# endif +#endif + +// Some compilers masquerade as both MSVC and GCC-likes or +// otherwise support __builtin_clz and __builtin_clzll, so +// only define FMT_BUILTIN_CLZ using the MSVC intrinsics +// if the clz and clzll builtins are not available. +#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(_MANAGED) +# include // _BitScanReverse, _BitScanReverse64 + +namespace fmt +{ +namespace internal +{ +# pragma intrinsic(_BitScanReverse) +inline uint32_t clz(uint32_t x) +{ + unsigned long r = 0; + _BitScanReverse(&r, x); + + assert(x != 0); + // Static analysis complains about using uninitialized data + // "r", but the only way that can happen is if "x" is 0, + // which the callers guarantee to not happen. +# pragma warning(suppress: 6102) + return 31 - r; +} +# define FMT_BUILTIN_CLZ(n) fmt::internal::clz(n) + +# ifdef _WIN64 +# pragma intrinsic(_BitScanReverse64) +# endif + +inline uint32_t clzll(uint64_t x) +{ + unsigned long r = 0; +# ifdef _WIN64 + _BitScanReverse64(&r, x); +# else + // Scan the high 32 bits. + if (_BitScanReverse(&r, static_cast(x >> 32))) + return 63 - (r + 32); + + // Scan the low 32 bits. + _BitScanReverse(&r, static_cast(x)); +# endif + + assert(x != 0); + // Static analysis complains about using uninitialized data + // "r", but the only way that can happen is if "x" is 0, + // which the callers guarantee to not happen. +# pragma warning(suppress: 6102) + return 63 - r; +} +# define FMT_BUILTIN_CLZLL(n) fmt::internal::clzll(n) +} +} +#endif + +namespace fmt +{ +namespace internal +{ +struct DummyInt +{ + int data[2]; + operator int() const + { + return 0; + } +}; +typedef std::numeric_limits FPUtil; + +// Dummy implementations of system functions such as signbit and ecvt called +// if the latter are not available. +inline DummyInt signbit(...) +{ + return DummyInt(); +} +inline DummyInt _ecvt_s(...) +{ + return DummyInt(); +} +inline DummyInt isinf(...) +{ + return DummyInt(); +} +inline DummyInt _finite(...) +{ + return DummyInt(); +} +inline DummyInt isnan(...) +{ + return DummyInt(); +} +inline DummyInt _isnan(...) +{ + return DummyInt(); +} + +// A helper function to suppress bogus "conditional expression is constant" +// warnings. +template +inline T const_check(T value) +{ + return value; +} +} +} // namespace fmt + +namespace std +{ +// Standard permits specialization of std::numeric_limits. This specialization +// is used to resolve ambiguity between isinf and std::isinf in glibc: +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48891 +// and the same for isnan and signbit. +template <> +class numeric_limits : + public std::numeric_limits +{ +public: + // Portable version of isinf. + template + static bool isinfinity(T x) + { + using namespace fmt::internal; + // The resolution "priority" is: + // isinf macro > std::isinf > ::isinf > fmt::internal::isinf + if (const_check(sizeof(isinf(x)) == sizeof(bool) || + sizeof(isinf(x)) == sizeof(int))) + { + return isinf(x) != 0; + } + return !_finite(static_cast(x)); + } + + // Portable version of isnan. + template + static bool isnotanumber(T x) + { + using namespace fmt::internal; + if (const_check(sizeof(isnan(x)) == sizeof(bool) || + sizeof(isnan(x)) == sizeof(int))) + { + return isnan(x) != 0; + } + return _isnan(static_cast(x)) != 0; + } + + // Portable version of signbit. + static bool isnegative(double x) + { + using namespace fmt::internal; + if (const_check(sizeof(signbit(x)) == sizeof(bool) || + sizeof(signbit(x)) == sizeof(int))) + { + return signbit(x) != 0; + } + if (x < 0) return true; + if (!isnotanumber(x)) return false; + int dec = 0, sign = 0; + char buffer[2]; // The buffer size must be >= 2 or _ecvt_s will fail. + _ecvt_s(buffer, sizeof(buffer), x, 0, &dec, &sign); + return sign != 0; + } +}; +} // namespace std + +namespace fmt +{ + +// Fix the warning about long long on older versions of GCC +// that don't support the diagnostic pragma. +FMT_GCC_EXTENSION typedef long long LongLong; +FMT_GCC_EXTENSION typedef unsigned long long ULongLong; + +#if FMT_USE_RVALUE_REFERENCES +using std::move; +#endif + +template +class BasicWriter; + +typedef BasicWriter Writer; +typedef BasicWriter WWriter; + +template +class ArgFormatter; + +struct FormatSpec; + +template +class BasicPrintfArgFormatter; + +template > +class BasicFormatter; + +/** + \rst + A string reference. It can be constructed from a C string or + ``std::basic_string``. + + You can use one of the following typedefs for common character types: + + +------------+-------------------------+ + | Type | Definition | + +============+=========================+ + | StringRef | BasicStringRef | + +------------+-------------------------+ + | WStringRef | BasicStringRef | + +------------+-------------------------+ + + This class is most useful as a parameter type to allow passing + different types of strings to a function, for example:: + + template + std::string format(StringRef format_str, const Args & ... args); + + format("{}", 42); + format(std::string("{}"), 42); + \endrst + */ +template +class BasicStringRef +{ +private: + const Char *data_; + std::size_t size_; + +public: + /** Constructs a string reference object from a C string and a size. */ + BasicStringRef(const Char *s, std::size_t size) : data_(s), size_(size) {} + + /** + \rst + Constructs a string reference object from a C string computing + the size with ``std::char_traits::length``. + \endrst + */ + BasicStringRef(const Char *s) + : data_(s), size_(std::char_traits::length(s)) {} + + /** + \rst + Constructs a string reference from a ``std::basic_string`` object. + \endrst + */ + template + BasicStringRef( + const std::basic_string, Allocator> &s) + : data_(s.c_str()), size_(s.size()) {} + + /** + \rst + Converts a string reference to an ``std::string`` object. + \endrst + */ + std::basic_string to_string() const + { + return std::basic_string(data_, size_); + } + + /** Returns a pointer to the string data. */ + const Char *data() const + { + return data_; + } + + /** Returns the string size. */ + std::size_t size() const + { + return size_; + } + + // Lexicographically compare this string reference to other. + int compare(BasicStringRef other) const + { + std::size_t size = size_ < other.size_ ? size_ : other.size_; + int result = std::char_traits::compare(data_, other.data_, size); + if (result == 0) + result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); + return result; + } + + friend bool operator==(BasicStringRef lhs, BasicStringRef rhs) + { + return lhs.compare(rhs) == 0; + } + friend bool operator!=(BasicStringRef lhs, BasicStringRef rhs) + { + return lhs.compare(rhs) != 0; + } + friend bool operator<(BasicStringRef lhs, BasicStringRef rhs) + { + return lhs.compare(rhs) < 0; + } + friend bool operator<=(BasicStringRef lhs, BasicStringRef rhs) + { + return lhs.compare(rhs) <= 0; + } + friend bool operator>(BasicStringRef lhs, BasicStringRef rhs) + { + return lhs.compare(rhs) > 0; + } + friend bool operator>=(BasicStringRef lhs, BasicStringRef rhs) + { + return lhs.compare(rhs) >= 0; + } +}; + +typedef BasicStringRef StringRef; +typedef BasicStringRef WStringRef; + +/** + \rst + A reference to a null terminated string. It can be constructed from a C + string or ``std::basic_string``. + + You can use one of the following typedefs for common character types: + + +-------------+--------------------------+ + | Type | Definition | + +=============+==========================+ + | CStringRef | BasicCStringRef | + +-------------+--------------------------+ + | WCStringRef | BasicCStringRef | + +-------------+--------------------------+ + + This class is most useful as a parameter type to allow passing + different types of strings to a function, for example:: + + template + std::string format(CStringRef format_str, const Args & ... args); + + format("{}", 42); + format(std::string("{}"), 42); + \endrst + */ +template +class BasicCStringRef +{ +private: + const Char *data_; + +public: + /** Constructs a string reference object from a C string. */ + BasicCStringRef(const Char *s) : data_(s) {} + + /** + \rst + Constructs a string reference from a ``std::basic_string`` object. + \endrst + */ + template + BasicCStringRef( + const std::basic_string, Allocator> &s) + : data_(s.c_str()) {} + + /** Returns the pointer to a C string. */ + const Char *c_str() const + { + return data_; + } +}; + +typedef BasicCStringRef CStringRef; +typedef BasicCStringRef WCStringRef; + +/** A formatting error such as invalid format string. */ +class FormatError : public std::runtime_error +{ +public: + explicit FormatError(CStringRef message) + : std::runtime_error(message.c_str()) {} + FormatError(const FormatError &ferr) : std::runtime_error(ferr) {} + FMT_API ~FormatError() FMT_DTOR_NOEXCEPT; +}; + +namespace internal +{ + +// MakeUnsigned::Type gives an unsigned type corresponding to integer type T. +template +struct MakeUnsigned +{ + typedef T Type; +}; + +#define FMT_SPECIALIZE_MAKE_UNSIGNED(T, U) \ + template <> \ + struct MakeUnsigned { typedef U Type; } + +FMT_SPECIALIZE_MAKE_UNSIGNED(char, unsigned char); +FMT_SPECIALIZE_MAKE_UNSIGNED(signed char, unsigned char); +FMT_SPECIALIZE_MAKE_UNSIGNED(short, unsigned short); +FMT_SPECIALIZE_MAKE_UNSIGNED(int, unsigned); +FMT_SPECIALIZE_MAKE_UNSIGNED(long, unsigned long); +FMT_SPECIALIZE_MAKE_UNSIGNED(LongLong, ULongLong); + +// Casts nonnegative integer to unsigned. +template +inline typename MakeUnsigned::Type to_unsigned(Int value) +{ + FMT_ASSERT(value >= 0, "negative value"); + return static_cast::Type>(value); +} + +// The number of characters to store in the MemoryBuffer object itself +// to avoid dynamic memory allocation. +enum { INLINE_BUFFER_SIZE = 500 }; + +#if FMT_SECURE_SCL +// Use checked iterator to avoid warnings on MSVC. +template +inline stdext::checked_array_iterator make_ptr(T *ptr, std::size_t size) +{ + return stdext::checked_array_iterator(ptr, size); +} +#else +template +inline T *make_ptr(T *ptr, std::size_t) +{ + return ptr; +} +#endif +} // namespace internal + +/** + \rst + A buffer supporting a subset of ``std::vector``'s operations. + \endrst + */ +template +class Buffer +{ +private: + FMT_DISALLOW_COPY_AND_ASSIGN(Buffer); + +protected: + T *ptr_; + std::size_t size_; + std::size_t capacity_; + + Buffer(T *ptr = FMT_NULL, std::size_t capacity = 0) + : ptr_(ptr), size_(0), capacity_(capacity) {} + + /** + \rst + Increases the buffer capacity to hold at least *size* elements updating + ``ptr_`` and ``capacity_``. + \endrst + */ + virtual void grow(std::size_t size) = 0; + +public: + virtual ~Buffer() {} + + /** Returns the size of this buffer. */ + std::size_t size() const + { + return size_; + } + + /** Returns the capacity of this buffer. */ + std::size_t capacity() const + { + return capacity_; + } + + /** + Resizes the buffer. If T is a POD type new elements may not be initialized. + */ + void resize(std::size_t new_size) + { + if (new_size > capacity_) + grow(new_size); + size_ = new_size; + } + + /** + \rst + Reserves space to store at least *capacity* elements. + \endrst + */ + void reserve(std::size_t capacity) + { + if (capacity > capacity_) + grow(capacity); + } + + void clear() FMT_NOEXCEPT { size_ = 0; } + + void push_back(const T &value) + { + if (size_ == capacity_) + grow(size_ + 1); + ptr_[size_++] = value; + } + + /** Appends data to the end of the buffer. */ + template + void append(const U *begin, const U *end); + + T &operator[](std::size_t index) + { + return ptr_[index]; + } + const T &operator[](std::size_t index) const + { + return ptr_[index]; + } +}; + +template +template +void Buffer::append(const U *begin, const U *end) +{ + FMT_ASSERT(end >= begin, "negative value"); + std::size_t new_size = size_ + (end - begin); + if (new_size > capacity_) + grow(new_size); + std::uninitialized_copy(begin, end, + internal::make_ptr(ptr_, capacity_) + size_); + size_ = new_size; +} + +namespace internal +{ + +// A memory buffer for trivially copyable/constructible types with the first +// SIZE elements stored in the object itself. +template > +class MemoryBuffer : private Allocator, public Buffer +{ +private: + T data_[SIZE]; + + // Deallocate memory allocated by the buffer. + void deallocate() + { + if (this->ptr_ != data_) Allocator::deallocate(this->ptr_, this->capacity_); + } + +protected: + void grow(std::size_t size) FMT_OVERRIDE; + +public: + explicit MemoryBuffer(const Allocator &alloc = Allocator()) + : Allocator(alloc), Buffer(data_, SIZE) {} + ~MemoryBuffer() + { + deallocate(); + } + +#if FMT_USE_RVALUE_REFERENCES +private: + // Move data from other to this buffer. + void move(MemoryBuffer &other) + { + Allocator &this_alloc = *this, &other_alloc = other; + this_alloc = std::move(other_alloc); + this->size_ = other.size_; + this->capacity_ = other.capacity_; + if (other.ptr_ == other.data_) + { + this->ptr_ = data_; + std::uninitialized_copy(other.data_, other.data_ + this->size_, + make_ptr(data_, this->capacity_)); + } + else + { + this->ptr_ = other.ptr_; + // Set pointer to the inline array so that delete is not called + // when deallocating. + other.ptr_ = other.data_; + } + } + +public: + MemoryBuffer(MemoryBuffer &&other) + { + move(other); + } + + MemoryBuffer &operator=(MemoryBuffer &&other) + { + assert(this != &other); + deallocate(); + move(other); + return *this; + } +#endif + + // Returns a copy of the allocator associated with this buffer. + Allocator get_allocator() const + { + return *this; + } +}; + +template +void MemoryBuffer::grow(std::size_t size) +{ + std::size_t new_capacity = this->capacity_ + this->capacity_ / 2; + if (size > new_capacity) + new_capacity = size; + T *new_ptr = this->allocate(new_capacity, FMT_NULL); + // The following code doesn't throw, so the raw pointer above doesn't leak. + std::uninitialized_copy(this->ptr_, this->ptr_ + this->size_, + make_ptr(new_ptr, new_capacity)); + std::size_t old_capacity = this->capacity_; + T *old_ptr = this->ptr_; + this->capacity_ = new_capacity; + this->ptr_ = new_ptr; + // deallocate may throw (at least in principle), but it doesn't matter since + // the buffer already uses the new storage and will deallocate it in case + // of exception. + if (old_ptr != data_) + Allocator::deallocate(old_ptr, old_capacity); +} + +// A fixed-size buffer. +template +class FixedBuffer : public fmt::Buffer +{ +public: + FixedBuffer(Char *array, std::size_t size) : fmt::Buffer(array, size) {} + +protected: + FMT_API void grow(std::size_t size) FMT_OVERRIDE; +}; + +template +class BasicCharTraits +{ +public: +#if FMT_SECURE_SCL + typedef stdext::checked_array_iterator CharPtr; +#else + typedef Char *CharPtr; +#endif + static Char cast(int value) + { + return static_cast(value); + } +}; + +template +class CharTraits; + +template <> +class CharTraits : public BasicCharTraits +{ +private: + // Conversion from wchar_t to char is not allowed. + static char convert(wchar_t); + +public: + static char convert(char value) + { + return value; + } + + // Formats a floating-point number. + template + FMT_API static int format_float(char *buffer, std::size_t size, + const char *format, unsigned width, int precision, T value); +}; + +#if FMT_USE_EXTERN_TEMPLATES +extern template int CharTraits::format_float +(char *buffer, std::size_t size, + const char* format, unsigned width, int precision, double value); +extern template int CharTraits::format_float +(char *buffer, std::size_t size, + const char* format, unsigned width, int precision, long double value); +#endif + +template <> +class CharTraits : public BasicCharTraits +{ +public: + static wchar_t convert(char value) + { + return value; + } + static wchar_t convert(wchar_t value) + { + return value; + } + + template + FMT_API static int format_float(wchar_t *buffer, std::size_t size, + const wchar_t *format, unsigned width, int precision, T value); +}; + +#if FMT_USE_EXTERN_TEMPLATES +extern template int CharTraits::format_float +(wchar_t *buffer, std::size_t size, + const wchar_t* format, unsigned width, int precision, double value); +extern template int CharTraits::format_float +(wchar_t *buffer, std::size_t size, + const wchar_t* format, unsigned width, int precision, long double value); +#endif + +// Checks if a number is negative - used to avoid warnings. +template +struct SignChecker +{ + template + static bool is_negative(T value) + { + return value < 0; + } +}; + +template <> +struct SignChecker +{ + template + static bool is_negative(T) + { + return false; + } +}; + +// Returns true if value is negative, false otherwise. +// Same as (value < 0) but doesn't produce warnings if T is an unsigned type. +template +inline bool is_negative(T value) +{ + return SignChecker::is_signed>::is_negative(value); +} + +// Selects uint32_t if FitsIn32Bits is true, uint64_t otherwise. +template +struct TypeSelector +{ + typedef uint32_t Type; +}; + +template <> +struct TypeSelector +{ + typedef uint64_t Type; +}; + +template +struct IntTraits +{ + // Smallest of uint32_t and uint64_t that is large enough to represent + // all values of T. + typedef typename + TypeSelector::digits <= 32>::Type MainType; +}; + +FMT_API void report_unknown_type(char code, const char *type); + +// Static data is placed in this class template to allow header-only +// configuration. +template +struct FMT_API BasicData +{ + static const uint32_t POWERS_OF_10_32[]; + static const uint64_t POWERS_OF_10_64[]; + static const char DIGITS[]; +}; + +#if FMT_USE_EXTERN_TEMPLATES +extern template struct BasicData; +#endif + +typedef BasicData<> Data; + +#ifdef FMT_BUILTIN_CLZLL +// Returns the number of decimal digits in n. Leading zeros are not counted +// except for n == 0 in which case count_digits returns 1. +inline unsigned count_digits(uint64_t n) +{ + // Based on http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 + // and the benchmark https://github.com/localvoid/cxx-benchmark-count-digits. + int t = (64 - FMT_BUILTIN_CLZLL(n | 1)) * 1233 >> 12; + return to_unsigned(t) - (n < Data::POWERS_OF_10_64[t]) + 1; +} +#else +// Fallback version of count_digits used when __builtin_clz is not available. +inline unsigned count_digits(uint64_t n) +{ + unsigned count = 1; + for (;;) + { + // Integer division is slow so do it for a group of four digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + if (n < 10) return count; + if (n < 100) return count + 1; + if (n < 1000) return count + 2; + if (n < 10000) return count + 3; + n /= 10000u; + count += 4; + } +} +#endif + +#ifdef FMT_BUILTIN_CLZ +// Optional version of count_digits for better performance on 32-bit platforms. +inline unsigned count_digits(uint32_t n) +{ + int t = (32 - FMT_BUILTIN_CLZ(n | 1)) * 1233 >> 12; + return to_unsigned(t) - (n < Data::POWERS_OF_10_32[t]) + 1; +} +#endif + +// A functor that doesn't add a thousands separator. +struct NoThousandsSep +{ + template + void operator()(Char *) {} +}; + +// A functor that adds a thousands separator. +class ThousandsSep +{ +private: + fmt::StringRef sep_; + + // Index of a decimal digit with the least significant digit having index 0. + unsigned digit_index_; + +public: + explicit ThousandsSep(fmt::StringRef sep) : sep_(sep), digit_index_(0) {} + + template + void operator()(Char *&buffer) + { + if (++digit_index_ % 3 != 0) + return; + buffer -= sep_.size(); + std::uninitialized_copy(sep_.data(), sep_.data() + sep_.size(), + internal::make_ptr(buffer, sep_.size())); + } +}; + +// Formats a decimal unsigned integer value writing into buffer. +// thousands_sep is a functor that is called after writing each char to +// add a thousands separator if necessary. +template +inline void format_decimal(Char *buffer, UInt value, unsigned num_digits, + ThousandsSep thousands_sep) +{ + buffer += num_digits; + while (value >= 100) + { + // Integer division is slow so do it for a group of two digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + unsigned index = static_cast((value % 100) * 2); + value /= 100; + *--buffer = Data::DIGITS[index + 1]; + thousands_sep(buffer); + *--buffer = Data::DIGITS[index]; + thousands_sep(buffer); + } + if (value < 10) + { + *--buffer = static_cast('0' + value); + return; + } + unsigned index = static_cast(value * 2); + *--buffer = Data::DIGITS[index + 1]; + thousands_sep(buffer); + *--buffer = Data::DIGITS[index]; +} + +template +inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) +{ + format_decimal(buffer, value, num_digits, NoThousandsSep()); + return; +} + +#ifndef _WIN32 +# define FMT_USE_WINDOWS_H 0 +#elif !defined(FMT_USE_WINDOWS_H) +# define FMT_USE_WINDOWS_H 1 +#endif + +// Define FMT_USE_WINDOWS_H to 0 to disable use of windows.h. +// All the functionality that relies on it will be disabled too. +#if FMT_USE_WINDOWS_H +// A converter from UTF-8 to UTF-16. +// It is only provided for Windows since other systems support UTF-8 natively. +class UTF8ToUTF16 +{ +private: + MemoryBuffer buffer_; + +public: + FMT_API explicit UTF8ToUTF16(StringRef s); + operator WStringRef() const + { + return WStringRef(&buffer_[0], size()); + } + size_t size() const + { + return buffer_.size() - 1; + } + const wchar_t *c_str() const + { + return &buffer_[0]; + } + std::wstring str() const + { + return std::wstring(&buffer_[0], size()); + } +}; + +// A converter from UTF-16 to UTF-8. +// It is only provided for Windows since other systems support UTF-8 natively. +class UTF16ToUTF8 +{ +private: + MemoryBuffer buffer_; + +public: + UTF16ToUTF8() {} + FMT_API explicit UTF16ToUTF8(WStringRef s); + operator StringRef() const + { + return StringRef(&buffer_[0], size()); + } + size_t size() const + { + return buffer_.size() - 1; + } + const char *c_str() const + { + return &buffer_[0]; + } + std::string str() const + { + return std::string(&buffer_[0], size()); + } + + // Performs conversion returning a system error code instead of + // throwing exception on conversion error. This method may still throw + // in case of memory allocation error. + FMT_API int convert(WStringRef s); +}; + +FMT_API void format_windows_error(fmt::Writer &out, int error_code, + fmt::StringRef message) FMT_NOEXCEPT; +#endif + +// A formatting argument value. +struct Value +{ + template + struct StringValue + { + const Char *value; + std::size_t size; + }; + + typedef void (*FormatFunc)( + void *formatter, const void *arg, void *format_str_ptr); + + struct CustomValue + { + const void *value; + FormatFunc format; + }; + + union + { + int int_value; + unsigned uint_value; + LongLong long_long_value; + ULongLong ulong_long_value; + double double_value; + long double long_double_value; + const void *pointer; + StringValue string; + StringValue sstring; + StringValue ustring; + StringValue wstring; + CustomValue custom; + }; + + enum Type + { + NONE, NAMED_ARG, + // Integer types should go first, + INT, UINT, LONG_LONG, ULONG_LONG, BOOL, CHAR, LAST_INTEGER_TYPE = CHAR, + // followed by floating-point types. + DOUBLE, LONG_DOUBLE, LAST_NUMERIC_TYPE = LONG_DOUBLE, + CSTRING, STRING, WSTRING, POINTER, CUSTOM + }; +}; + +// A formatting argument. It is a trivially copyable/constructible type to +// allow storage in internal::MemoryBuffer. +struct Arg : Value +{ + Type type; +}; + +template +struct NamedArg; +template +struct NamedArgWithType; + +template +struct Null {}; + +// A helper class template to enable or disable overloads taking wide +// characters and strings in MakeValue. +template +struct WCharHelper +{ + typedef Null Supported; + typedef T Unsupported; +}; + +template +struct WCharHelper +{ + typedef T Supported; + typedef Null Unsupported; +}; + +typedef char Yes[1]; +typedef char No[2]; + +template +T &get(); + +// These are non-members to workaround an overload resolution bug in bcc32. +Yes &convert(fmt::ULongLong); +No &convert(...); + +template +struct ConvertToIntImpl +{ + enum { value = ENABLE_CONVERSION }; +}; + +template +struct ConvertToIntImpl2 +{ + enum { value = false }; +}; + +template +struct ConvertToIntImpl2 +{ + enum + { + // Don't convert numeric types. + value = ConvertToIntImpl::is_specialized>::value + }; +}; + +template +struct ConvertToInt +{ + enum + { + enable_conversion = sizeof(fmt::internal::convert(get())) == sizeof(Yes) + }; + enum { value = ConvertToIntImpl2::value }; +}; + +#define FMT_DISABLE_CONVERSION_TO_INT(Type) \ + template <> \ + struct ConvertToInt { enum { value = 0 }; } + +// Silence warnings about convering float to int. +FMT_DISABLE_CONVERSION_TO_INT(float); +FMT_DISABLE_CONVERSION_TO_INT(double); +FMT_DISABLE_CONVERSION_TO_INT(long double); + +template +struct EnableIf {}; + +template +struct EnableIf +{ + typedef T type; +}; + +template +struct Conditional +{ + typedef T type; +}; + +template +struct Conditional +{ + typedef F type; +}; + +// For bcc32 which doesn't understand ! in template arguments. +template +struct Not +{ + enum { value = 0 }; +}; + +template <> +struct Not +{ + enum { value = 1 }; +}; + +template +struct FalseType +{ + enum { value = 0 }; +}; + +template struct LConvCheck +{ + LConvCheck(int) {} +}; + +// Returns the thousands separator for the current locale. +// We check if ``lconv`` contains ``thousands_sep`` because on Android +// ``lconv`` is stubbed as an empty struct. +template +inline StringRef thousands_sep( + LConv *lc, LConvCheck = 0) +{ + return lc->thousands_sep; +} + +inline fmt::StringRef thousands_sep(...) +{ + return ""; +} + +#define FMT_CONCAT(a, b) a##b + +#if FMT_GCC_VERSION >= 303 +# define FMT_UNUSED __attribute__((unused)) +#else +# define FMT_UNUSED +#endif + +#ifndef FMT_USE_STATIC_ASSERT +# define FMT_USE_STATIC_ASSERT 0 +#endif + +#if FMT_USE_STATIC_ASSERT || FMT_HAS_FEATURE(cxx_static_assert) || \ + (FMT_GCC_VERSION >= 403 && FMT_HAS_GXX_CXX11) || _MSC_VER >= 1600 +# define FMT_STATIC_ASSERT(cond, message) static_assert(cond, message) +#else +# define FMT_CONCAT_(a, b) FMT_CONCAT(a, b) +# define FMT_STATIC_ASSERT(cond, message) \ + typedef int FMT_CONCAT_(Assert, __LINE__)[(cond) ? 1 : -1] FMT_UNUSED +#endif + +template +void format_arg(Formatter &, const Char *, const T &) +{ + FMT_STATIC_ASSERT(FalseType::value, + "Cannot format argument. To enable the use of ostream " + "operator<< include fmt/ostream.h. Otherwise provide " + "an overload of format_arg."); +} + +// Makes an Arg object from any type. +template +class MakeValue : public Arg +{ +public: + typedef typename Formatter::Char Char; + +private: + // The following two methods are private to disallow formatting of + // arbitrary pointers. If you want to output a pointer cast it to + // "void *" or "const void *". In particular, this forbids formatting + // of "[const] volatile char *" which is printed as bool by iostreams. + // Do not implement! + template + MakeValue(const T *value); + template + MakeValue(T *value); + + // The following methods are private to disallow formatting of wide + // characters and strings into narrow strings as in + // fmt::format("{}", L"test"); + // To fix this, use a wide format string: fmt::format(L"{}", L"test"). +#if !FMT_MSC_VER || defined(_NATIVE_WCHAR_T_DEFINED) + MakeValue(typename WCharHelper::Unsupported); +#endif + MakeValue(typename WCharHelper::Unsupported); + MakeValue(typename WCharHelper::Unsupported); + MakeValue(typename WCharHelper::Unsupported); + MakeValue(typename WCharHelper::Unsupported); + + void set_string(StringRef str) + { + string.value = str.data(); + string.size = str.size(); + } + + void set_string(WStringRef str) + { + wstring.value = str.data(); + wstring.size = str.size(); + } + + // Formats an argument of a custom type, such as a user-defined class. + template + static void format_custom_arg( + void *formatter, const void *arg, void *format_str_ptr) + { + format_arg(*static_cast(formatter), + *static_cast(format_str_ptr), + *static_cast(arg)); + } + +public: + MakeValue() {} + +#define FMT_MAKE_VALUE_(Type, field, TYPE, rhs) \ + MakeValue(Type value) { field = rhs; } \ + static uint64_t type(Type) { return Arg::TYPE; } + +#define FMT_MAKE_VALUE(Type, field, TYPE) \ + FMT_MAKE_VALUE_(Type, field, TYPE, value) + + FMT_MAKE_VALUE(bool, int_value, BOOL) + FMT_MAKE_VALUE(short, int_value, INT) + FMT_MAKE_VALUE(unsigned short, uint_value, UINT) + FMT_MAKE_VALUE(int, int_value, INT) + FMT_MAKE_VALUE(unsigned, uint_value, UINT) + + MakeValue(long value) + { + // To minimize the number of types we need to deal with, long is + // translated either to int or to long long depending on its size. + if (const_check(sizeof(long) == sizeof(int))) + int_value = static_cast(value); + else + long_long_value = value; + } + static uint64_t type(long) + { + return sizeof(long) == sizeof(int) ? Arg::INT : Arg::LONG_LONG; + } + + MakeValue(unsigned long value) + { + if (const_check(sizeof(unsigned long) == sizeof(unsigned))) + uint_value = static_cast(value); + else + ulong_long_value = value; + } + static uint64_t type(unsigned long) + { + return sizeof(unsigned long) == sizeof(unsigned) ? + Arg::UINT : Arg::ULONG_LONG; + } + + FMT_MAKE_VALUE(LongLong, long_long_value, LONG_LONG) + FMT_MAKE_VALUE(ULongLong, ulong_long_value, ULONG_LONG) + FMT_MAKE_VALUE(float, double_value, DOUBLE) + FMT_MAKE_VALUE(double, double_value, DOUBLE) + FMT_MAKE_VALUE(long double, long_double_value, LONG_DOUBLE) + FMT_MAKE_VALUE(signed char, int_value, INT) + FMT_MAKE_VALUE(unsigned char, uint_value, UINT) + FMT_MAKE_VALUE(char, int_value, CHAR) + +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) + MakeValue(typename WCharHelper::Supported value) + { + int_value = value; + } + static uint64_t type(wchar_t) + { + return Arg::CHAR; + } +#endif + +#define FMT_MAKE_STR_VALUE(Type, TYPE) \ + MakeValue(Type value) { set_string(value); } \ + static uint64_t type(Type) { return Arg::TYPE; } + + FMT_MAKE_VALUE(char *, string.value, CSTRING) + FMT_MAKE_VALUE(const char *, string.value, CSTRING) + FMT_MAKE_VALUE(signed char *, sstring.value, CSTRING) + FMT_MAKE_VALUE(const signed char *, sstring.value, CSTRING) + FMT_MAKE_VALUE(unsigned char *, ustring.value, CSTRING) + FMT_MAKE_VALUE(const unsigned char *, ustring.value, CSTRING) + FMT_MAKE_STR_VALUE(const std::string &, STRING) + FMT_MAKE_STR_VALUE(StringRef, STRING) + FMT_MAKE_VALUE_(CStringRef, string.value, CSTRING, value.c_str()) + +#define FMT_MAKE_WSTR_VALUE(Type, TYPE) \ + MakeValue(typename WCharHelper::Supported value) { \ + set_string(value); \ + } \ + static uint64_t type(Type) { return Arg::TYPE; } + + FMT_MAKE_WSTR_VALUE(wchar_t *, WSTRING) + FMT_MAKE_WSTR_VALUE(const wchar_t *, WSTRING) + FMT_MAKE_WSTR_VALUE(const std::wstring &, WSTRING) + FMT_MAKE_WSTR_VALUE(WStringRef, WSTRING) + + FMT_MAKE_VALUE(void *, pointer, POINTER) + FMT_MAKE_VALUE(const void *, pointer, POINTER) + + template + MakeValue(const T &value, + typename EnableIf::value>::value, int>::type = 0) + { + custom.value = &value; + custom.format = &format_custom_arg; + } + + template + static typename EnableIf::value>::value, uint64_t>::type + type(const T &) + { + return Arg::CUSTOM; + } + + // Additional template param `Char_` is needed here because make_type always + // uses char. + template + MakeValue(const NamedArg &value) + { + pointer = &value; + } + template + MakeValue(const NamedArgWithType &value) + { + pointer = &value; + } + + template + static uint64_t type(const NamedArg &) + { + return Arg::NAMED_ARG; + } + template + static uint64_t type(const NamedArgWithType &) + { + return Arg::NAMED_ARG; + } +}; + +template +class MakeArg : public Arg +{ +public: + MakeArg() + { + type = Arg::NONE; + } + + template + MakeArg(const T &value) + : Arg(MakeValue(value)) + { + type = static_cast(MakeValue::type(value)); + } +}; + +template +struct NamedArg : Arg +{ + BasicStringRef name; + + template + NamedArg(BasicStringRef argname, const T &value) + : Arg(MakeArg< BasicFormatter >(value)), name(argname) {} +}; + +template +struct NamedArgWithType : NamedArg +{ + NamedArgWithType(BasicStringRef argname, const T &value) + : NamedArg(argname, value) {} +}; + +class RuntimeError : public std::runtime_error +{ +protected: + RuntimeError() : std::runtime_error("") {} + RuntimeError(const RuntimeError &rerr) : std::runtime_error(rerr) {} + FMT_API ~RuntimeError() FMT_DTOR_NOEXCEPT; +}; + +template +class ArgMap; +} // namespace internal + +/** An argument list. */ +class ArgList +{ +private: + // To reduce compiled code size per formatting function call, types of first + // MAX_PACKED_ARGS arguments are passed in the types_ field. + uint64_t types_; + union + { + // If the number of arguments is less than MAX_PACKED_ARGS, the argument + // values are stored in values_, otherwise they are stored in args_. + // This is done to reduce compiled code size as storing larger objects + // may require more code (at least on x86-64) even if the same amount of + // data is actually copied to stack. It saves ~10% on the bloat test. + const internal::Value *values_; + const internal::Arg *args_; + }; + + internal::Arg::Type type(unsigned index) const + { + return type(types_, index); + } + + template + friend class internal::ArgMap; + +public: + // Maximum number of arguments with packed types. + enum { MAX_PACKED_ARGS = 16 }; + + ArgList() : types_(0) {} + + ArgList(ULongLong types, const internal::Value *values) + : types_(types), values_(values) {} + ArgList(ULongLong types, const internal::Arg *args) + : types_(types), args_(args) {} + + uint64_t types() const + { + return types_; + } + + /** Returns the argument at specified index. */ + internal::Arg operator[](unsigned index) const + { + using internal::Arg; + Arg arg; + bool use_values = type(MAX_PACKED_ARGS - 1) == Arg::NONE; + if (index < MAX_PACKED_ARGS) + { + Arg::Type arg_type = type(index); + internal::Value &val = arg; + if (arg_type != Arg::NONE) + val = use_values ? values_[index] : args_[index]; + arg.type = arg_type; + return arg; + } + if (use_values) + { + // The index is greater than the number of arguments that can be stored + // in values, so return a "none" argument. + arg.type = Arg::NONE; + return arg; + } + for (unsigned i = MAX_PACKED_ARGS; i <= index; ++i) + { + if (args_[i].type == Arg::NONE) + return args_[i]; + } + return args_[index]; + } + + static internal::Arg::Type type(uint64_t types, unsigned index) + { + unsigned shift = index * 4; + uint64_t mask = 0xf; + return static_cast( + (types & (mask << shift)) >> shift); + } +}; + +#define FMT_DISPATCH(call) static_cast(this)->call + +/** + \rst + An argument visitor based on the `curiously recurring template pattern + `_. + + To use `~fmt::ArgVisitor` define a subclass that implements some or all of the + visit methods with the same signatures as the methods in `~fmt::ArgVisitor`, + for example, `~fmt::ArgVisitor::visit_int()`. + Pass the subclass as the *Impl* template parameter. Then calling + `~fmt::ArgVisitor::visit` for some argument will dispatch to a visit method + specific to the argument type. For example, if the argument type is + ``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass + will be called. If the subclass doesn't contain a method with this signature, + then a corresponding method of `~fmt::ArgVisitor` will be called. + + **Example**:: + + class MyArgVisitor : public fmt::ArgVisitor { + public: + void visit_int(int value) { fmt::print("{}", value); } + void visit_double(double value) { fmt::print("{}", value ); } + }; + \endrst + */ +template +class ArgVisitor +{ +private: + typedef internal::Arg Arg; + +public: + void report_unhandled_arg() {} + + Result visit_unhandled_arg() + { + FMT_DISPATCH(report_unhandled_arg()); + return Result(); + } + + /** Visits an ``int`` argument. **/ + Result visit_int(int value) + { + return FMT_DISPATCH(visit_any_int(value)); + } + + /** Visits a ``long long`` argument. **/ + Result visit_long_long(LongLong value) + { + return FMT_DISPATCH(visit_any_int(value)); + } + + /** Visits an ``unsigned`` argument. **/ + Result visit_uint(unsigned value) + { + return FMT_DISPATCH(visit_any_int(value)); + } + + /** Visits an ``unsigned long long`` argument. **/ + Result visit_ulong_long(ULongLong value) + { + return FMT_DISPATCH(visit_any_int(value)); + } + + /** Visits a ``bool`` argument. **/ + Result visit_bool(bool value) + { + return FMT_DISPATCH(visit_any_int(value)); + } + + /** Visits a ``char`` or ``wchar_t`` argument. **/ + Result visit_char(int value) + { + return FMT_DISPATCH(visit_any_int(value)); + } + + /** Visits an argument of any integral type. **/ + template + Result visit_any_int(T) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** Visits a ``double`` argument. **/ + Result visit_double(double value) + { + return FMT_DISPATCH(visit_any_double(value)); + } + + /** Visits a ``long double`` argument. **/ + Result visit_long_double(long double value) + { + return FMT_DISPATCH(visit_any_double(value)); + } + + /** Visits a ``double`` or ``long double`` argument. **/ + template + Result visit_any_double(T) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** Visits a null-terminated C string (``const char *``) argument. **/ + Result visit_cstring(const char *) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** Visits a string argument. **/ + Result visit_string(Arg::StringValue) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** Visits a wide string argument. **/ + Result visit_wstring(Arg::StringValue) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** Visits a pointer argument. **/ + Result visit_pointer(const void *) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** Visits an argument of a custom (user-defined) type. **/ + Result visit_custom(Arg::CustomValue) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** + \rst + Visits an argument dispatching to the appropriate visit method based on + the argument type. For example, if the argument type is ``double`` then + the `~fmt::ArgVisitor::visit_double()` method of the *Impl* class will be + called. + \endrst + */ + Result visit(const Arg &arg) + { + switch (arg.type) + { + case Arg::NONE: + case Arg::NAMED_ARG: + FMT_ASSERT(false, "invalid argument type"); + break; + case Arg::INT: + return FMT_DISPATCH(visit_int(arg.int_value)); + case Arg::UINT: + return FMT_DISPATCH(visit_uint(arg.uint_value)); + case Arg::LONG_LONG: + return FMT_DISPATCH(visit_long_long(arg.long_long_value)); + case Arg::ULONG_LONG: + return FMT_DISPATCH(visit_ulong_long(arg.ulong_long_value)); + case Arg::BOOL: + return FMT_DISPATCH(visit_bool(arg.int_value != 0)); + case Arg::CHAR: + return FMT_DISPATCH(visit_char(arg.int_value)); + case Arg::DOUBLE: + return FMT_DISPATCH(visit_double(arg.double_value)); + case Arg::LONG_DOUBLE: + return FMT_DISPATCH(visit_long_double(arg.long_double_value)); + case Arg::CSTRING: + return FMT_DISPATCH(visit_cstring(arg.string.value)); + case Arg::STRING: + return FMT_DISPATCH(visit_string(arg.string)); + case Arg::WSTRING: + return FMT_DISPATCH(visit_wstring(arg.wstring)); + case Arg::POINTER: + return FMT_DISPATCH(visit_pointer(arg.pointer)); + case Arg::CUSTOM: + return FMT_DISPATCH(visit_custom(arg.custom)); + } + return Result(); + } +}; + +enum Alignment +{ + ALIGN_DEFAULT, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, ALIGN_NUMERIC +}; + +// Flags. +enum +{ + SIGN_FLAG = 1, PLUS_FLAG = 2, MINUS_FLAG = 4, HASH_FLAG = 8, + CHAR_FLAG = 0x10 // Argument has char type - used in error reporting. +}; + +// An empty format specifier. +struct EmptySpec {}; + +// A type specifier. +template +struct TypeSpec : EmptySpec +{ + Alignment align() const + { + return ALIGN_DEFAULT; + } + unsigned width() const + { + return 0; + } + int precision() const + { + return -1; + } + bool flag(unsigned) const + { + return false; + } + char type() const + { + return TYPE; + } + char type_prefix() const + { + return TYPE; + } + char fill() const + { + return ' '; + } +}; + +// A width specifier. +struct WidthSpec +{ + unsigned width_; + // Fill is always wchar_t and cast to char if necessary to avoid having + // two specialization of WidthSpec and its subclasses. + wchar_t fill_; + + WidthSpec(unsigned width, wchar_t fill) : width_(width), fill_(fill) {} + + unsigned width() const + { + return width_; + } + wchar_t fill() const + { + return fill_; + } +}; + +// An alignment specifier. +struct AlignSpec : WidthSpec +{ + Alignment align_; + + AlignSpec(unsigned width, wchar_t fill, Alignment align = ALIGN_DEFAULT) + : WidthSpec(width, fill), align_(align) {} + + Alignment align() const + { + return align_; + } + + int precision() const + { + return -1; + } +}; + +// An alignment and type specifier. +template +struct AlignTypeSpec : AlignSpec +{ + AlignTypeSpec(unsigned width, wchar_t fill) : AlignSpec(width, fill) {} + + bool flag(unsigned) const + { + return false; + } + char type() const + { + return TYPE; + } + char type_prefix() const + { + return TYPE; + } +}; + +// A full format specifier. +struct FormatSpec : AlignSpec +{ + unsigned flags_; + int precision_; + char type_; + + FormatSpec( + unsigned width = 0, char type = 0, wchar_t fill = ' ') + : AlignSpec(width, fill), flags_(0), precision_(-1), type_(type) {} + + bool flag(unsigned f) const + { + return (flags_ & f) != 0; + } + int precision() const + { + return precision_; + } + char type() const + { + return type_; + } + char type_prefix() const + { + return type_; + } +}; + +// An integer format specifier. +template , typename Char = char> +class IntFormatSpec : public SpecT +{ +private: + T value_; + +public: + IntFormatSpec(T val, const SpecT &spec = SpecT()) + : SpecT(spec), value_(val) {} + + T value() const + { + return value_; + } +}; + +// A string format specifier. +template +class StrFormatSpec : public AlignSpec +{ +private: + const Char *str_; + +public: + template + StrFormatSpec(const Char *str, unsigned width, FillChar fill) + : AlignSpec(width, fill), str_(str) + { + internal::CharTraits::convert(FillChar()); + } + + const Char *str() const + { + return str_; + } +}; + +/** + Returns an integer format specifier to format the value in base 2. + */ +IntFormatSpec > bin(int value); + +/** + Returns an integer format specifier to format the value in base 8. + */ +IntFormatSpec > oct(int value); + +/** + Returns an integer format specifier to format the value in base 16 using + lower-case letters for the digits above 9. + */ +IntFormatSpec > hex(int value); + +/** + Returns an integer formatter format specifier to format in base 16 using + upper-case letters for the digits above 9. + */ +IntFormatSpec > hexu(int value); + +/** + \rst + Returns an integer format specifier to pad the formatted argument with the + fill character to the specified width using the default (right) numeric + alignment. + + **Example**:: + + MemoryWriter out; + out << pad(hex(0xcafe), 8, '0'); + // out.str() == "0000cafe" + + \endrst + */ +template +IntFormatSpec, Char> pad( + int value, unsigned width, Char fill = ' '); + +#define FMT_DEFINE_INT_FORMATTERS(TYPE) \ +inline IntFormatSpec > bin(TYPE value) { \ + return IntFormatSpec >(value, TypeSpec<'b'>()); \ +} \ + \ +inline IntFormatSpec > oct(TYPE value) { \ + return IntFormatSpec >(value, TypeSpec<'o'>()); \ +} \ + \ +inline IntFormatSpec > hex(TYPE value) { \ + return IntFormatSpec >(value, TypeSpec<'x'>()); \ +} \ + \ +inline IntFormatSpec > hexu(TYPE value) { \ + return IntFormatSpec >(value, TypeSpec<'X'>()); \ +} \ + \ +template \ +inline IntFormatSpec > pad( \ + IntFormatSpec > f, unsigned width) { \ + return IntFormatSpec >( \ + f.value(), AlignTypeSpec(width, ' ')); \ +} \ + \ +/* For compatibility with older compilers we provide two overloads for pad, */ \ +/* one that takes a fill character and one that doesn't. In the future this */ \ +/* can be replaced with one overload making the template argument Char */ \ +/* default to char (C++11). */ \ +template \ +inline IntFormatSpec, Char> pad( \ + IntFormatSpec, Char> f, \ + unsigned width, Char fill) { \ + return IntFormatSpec, Char>( \ + f.value(), AlignTypeSpec(width, fill)); \ +} \ + \ +inline IntFormatSpec > pad( \ + TYPE value, unsigned width) { \ + return IntFormatSpec >( \ + value, AlignTypeSpec<0>(width, ' ')); \ +} \ + \ +template \ +inline IntFormatSpec, Char> pad( \ + TYPE value, unsigned width, Char fill) { \ + return IntFormatSpec, Char>( \ + value, AlignTypeSpec<0>(width, fill)); \ +} + +FMT_DEFINE_INT_FORMATTERS(int) +FMT_DEFINE_INT_FORMATTERS(long) +FMT_DEFINE_INT_FORMATTERS(unsigned) +FMT_DEFINE_INT_FORMATTERS(unsigned long) +FMT_DEFINE_INT_FORMATTERS(LongLong) +FMT_DEFINE_INT_FORMATTERS(ULongLong) + +/** + \rst + Returns a string formatter that pads the formatted argument with the fill + character to the specified width using the default (left) string alignment. + + **Example**:: + + std::string s = str(MemoryWriter() << pad("abc", 8)); + // s == "abc " + + \endrst + */ +template +inline StrFormatSpec pad( + const Char *str, unsigned width, Char fill = ' ') +{ + return StrFormatSpec(str, width, fill); +} + +inline StrFormatSpec pad( + const wchar_t *str, unsigned width, char fill = ' ') +{ + return StrFormatSpec(str, width, fill); +} + +namespace internal +{ + +template +class ArgMap +{ +private: + typedef std::vector< + std::pair, internal::Arg> > MapType; + typedef typename MapType::value_type Pair; + + MapType map_; + +public: + FMT_API void init(const ArgList &args); + + const internal::Arg *find(const fmt::BasicStringRef &name) const + { + // The list is unsorted, so just return the first matching name. + for (typename MapType::const_iterator it = map_.begin(), end = map_.end(); + it != end; ++it) + { + if (it->first == name) + return &it->second; + } + return FMT_NULL; + } +}; + +template +class ArgFormatterBase : public ArgVisitor +{ +private: + BasicWriter &writer_; + Spec &spec_; + + FMT_DISALLOW_COPY_AND_ASSIGN(ArgFormatterBase); + + void write_pointer(const void *p) + { + spec_.flags_ = HASH_FLAG; + spec_.type_ = 'x'; + writer_.write_int(reinterpret_cast(p), spec_); + } + + // workaround MSVC two-phase lookup issue + typedef internal::Arg Arg; + +protected: + BasicWriter &writer() + { + return writer_; + } + Spec &spec() + { + return spec_; + } + + void write(bool value) + { + const char *str_value = value ? "true" : "false"; + Arg::StringValue str = { str_value, std::strlen(str_value) }; + writer_.write_str(str, spec_); + } + + void write(const char *value) + { + Arg::StringValue str = {value, value ? std::strlen(value) : 0}; + writer_.write_str(str, spec_); + } + +public: + typedef Spec SpecType; + + ArgFormatterBase(BasicWriter &w, Spec &s) + : writer_(w), spec_(s) {} + + template + void visit_any_int(T value) + { + writer_.write_int(value, spec_); + } + + template + void visit_any_double(T value) + { + writer_.write_double(value, spec_); + } + + void visit_bool(bool value) + { + if (spec_.type_) + { + visit_any_int(value); + return; + } + write(value); + } + + void visit_char(int value) + { + if (spec_.type_ && spec_.type_ != 'c') + { + spec_.flags_ |= CHAR_FLAG; + writer_.write_int(value, spec_); + return; + } + if (spec_.align_ == ALIGN_NUMERIC || spec_.flags_ != 0) + FMT_THROW(FormatError("invalid format specifier for char")); + typedef typename BasicWriter::CharPtr CharPtr; + Char fill = internal::CharTraits::cast(spec_.fill()); + CharPtr out = CharPtr(); + const unsigned CHAR_SIZE = 1; + if (spec_.width_ > CHAR_SIZE) + { + out = writer_.grow_buffer(spec_.width_); + if (spec_.align_ == ALIGN_RIGHT) + { + std::uninitialized_fill_n(out, spec_.width_ - CHAR_SIZE, fill); + out += spec_.width_ - CHAR_SIZE; + } + else if (spec_.align_ == ALIGN_CENTER) + { + out = writer_.fill_padding(out, spec_.width_, + internal::const_check(CHAR_SIZE), fill); + } + else + { + std::uninitialized_fill_n(out + CHAR_SIZE, + spec_.width_ - CHAR_SIZE, fill); + } + } + else + { + out = writer_.grow_buffer(CHAR_SIZE); + } + *out = internal::CharTraits::cast(value); + } + + void visit_cstring(const char *value) + { + if (spec_.type_ == 'p') + return write_pointer(value); + write(value); + } + + // Qualification with "internal" here and below is a workaround for nvcc. + void visit_string(internal::Arg::StringValue value) + { + writer_.write_str(value, spec_); + } + + using ArgVisitor::visit_wstring; + + void visit_wstring(internal::Arg::StringValue value) + { + writer_.write_str(value, spec_); + } + + void visit_pointer(const void *value) + { + if (spec_.type_ && spec_.type_ != 'p') + report_unknown_type(spec_.type_, "pointer"); + write_pointer(value); + } +}; + +class FormatterBase +{ +private: + ArgList args_; + int next_arg_index_; + + // Returns the argument with specified index. + FMT_API Arg do_get_arg(unsigned arg_index, const char *&error); + +protected: + const ArgList &args() const + { + return args_; + } + + explicit FormatterBase(const ArgList &args) + { + args_ = args; + next_arg_index_ = 0; + } + + // Returns the next argument. + Arg next_arg(const char *&error) + { + if (next_arg_index_ >= 0) + return do_get_arg(internal::to_unsigned(next_arg_index_++), error); + error = "cannot switch from manual to automatic argument indexing"; + return Arg(); + } + + // Checks if manual indexing is used and returns the argument with + // specified index. + Arg get_arg(unsigned arg_index, const char *&error) + { + return check_no_auto_index(error) ? do_get_arg(arg_index, error) : Arg(); + } + + bool check_no_auto_index(const char *&error) + { + if (next_arg_index_ > 0) + { + error = "cannot switch from automatic to manual argument indexing"; + return false; + } + next_arg_index_ = -1; + return true; + } + + template + void write(BasicWriter &w, const Char *start, const Char *end) + { + if (start != end) + w << BasicStringRef(start, internal::to_unsigned(end - start)); + } +}; +} // namespace internal + +/** + \rst + An argument formatter based on the `curiously recurring template pattern + `_. + + To use `~fmt::BasicArgFormatter` define a subclass that implements some or + all of the visit methods with the same signatures as the methods in + `~fmt::ArgVisitor`, for example, `~fmt::ArgVisitor::visit_int()`. + Pass the subclass as the *Impl* template parameter. When a formatting + function processes an argument, it will dispatch to a visit method + specific to the argument type. For example, if the argument type is + ``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass + will be called. If the subclass doesn't contain a method with this signature, + then a corresponding method of `~fmt::BasicArgFormatter` or its superclass + will be called. + \endrst + */ +template +class BasicArgFormatter : public internal::ArgFormatterBase +{ +private: + BasicFormatter &formatter_; + const Char *format_; + +public: + /** + \rst + Constructs an argument formatter object. + *formatter* is a reference to the main formatter object, *spec* contains + format specifier information for standard argument types, and *fmt* points + to the part of the format string being parsed for custom argument types. + \endrst + */ + BasicArgFormatter(BasicFormatter &formatter, + Spec &spec, const Char *fmt) + : internal::ArgFormatterBase(formatter.writer(), spec), + formatter_(formatter), format_(fmt) {} + + /** Formats an argument of a custom (user-defined) type. */ + void visit_custom(internal::Arg::CustomValue c) + { + c.format(&formatter_, c.value, &format_); + } +}; + +/** The default argument formatter. */ +template +class ArgFormatter : + public BasicArgFormatter, Char, FormatSpec> +{ +public: + /** Constructs an argument formatter object. */ + ArgFormatter(BasicFormatter &formatter, + FormatSpec &spec, const Char *fmt) + : BasicArgFormatter, + Char, FormatSpec>(formatter, spec, fmt) {} +}; + +/** This template formats data and writes the output to a writer. */ +template +class BasicFormatter : private internal::FormatterBase +{ +public: + /** The character type for the output. */ + typedef CharType Char; + +private: + BasicWriter &writer_; + internal::ArgMap map_; + + FMT_DISALLOW_COPY_AND_ASSIGN(BasicFormatter); + + using internal::FormatterBase::get_arg; + + // Checks if manual indexing is used and returns the argument with + // specified name. + internal::Arg get_arg(BasicStringRef arg_name, const char *&error); + + // Parses argument index and returns corresponding argument. + internal::Arg parse_arg_index(const Char *&s); + + // Parses argument name and returns corresponding argument. + internal::Arg parse_arg_name(const Char *&s); + +public: + /** + \rst + Constructs a ``BasicFormatter`` object. References to the arguments and + the writer are stored in the formatter object so make sure they have + appropriate lifetimes. + \endrst + */ + BasicFormatter(const ArgList &args, BasicWriter &w) + : internal::FormatterBase(args), writer_(w) {} + + /** Returns a reference to the writer associated with this formatter. */ + BasicWriter &writer() + { + return writer_; + } + + /** Formats stored arguments and writes the output to the writer. */ + void format(BasicCStringRef format_str); + + // Formats a single argument and advances format_str, a format string pointer. + const Char *format(const Char *&format_str, const internal::Arg &arg); +}; + +// Generates a comma-separated list with results of applying f to +// numbers 0..n-1. +# define FMT_GEN(n, f) FMT_GEN##n(f) +# define FMT_GEN1(f) f(0) +# define FMT_GEN2(f) FMT_GEN1(f), f(1) +# define FMT_GEN3(f) FMT_GEN2(f), f(2) +# define FMT_GEN4(f) FMT_GEN3(f), f(3) +# define FMT_GEN5(f) FMT_GEN4(f), f(4) +# define FMT_GEN6(f) FMT_GEN5(f), f(5) +# define FMT_GEN7(f) FMT_GEN6(f), f(6) +# define FMT_GEN8(f) FMT_GEN7(f), f(7) +# define FMT_GEN9(f) FMT_GEN8(f), f(8) +# define FMT_GEN10(f) FMT_GEN9(f), f(9) +# define FMT_GEN11(f) FMT_GEN10(f), f(10) +# define FMT_GEN12(f) FMT_GEN11(f), f(11) +# define FMT_GEN13(f) FMT_GEN12(f), f(12) +# define FMT_GEN14(f) FMT_GEN13(f), f(13) +# define FMT_GEN15(f) FMT_GEN14(f), f(14) + +namespace internal +{ +inline uint64_t make_type() +{ + return 0; +} + +template +inline uint64_t make_type(const T &arg) +{ + return MakeValue< BasicFormatter >::type(arg); +} + +template + struct ArgArray; + +template +struct ArgArray +{ + typedef Value Type[N > 0 ? N : 1]; + +template +static Value make(const T &value) +{ +#ifdef __clang__ + Value result = MakeValue(value); + // Workaround a bug in Apple LLVM version 4.2 (clang-425.0.28) of clang: + // https://github.com/fmtlib/fmt/issues/276 + (void)result.custom.format; + return result; +#else + return MakeValue(value); +#endif +} + }; + +template +struct ArgArray +{ + typedef Arg Type[N + 1]; // +1 for the list end Arg::NONE + + template + static Arg make(const T &value) + { + return MakeArg(value); + } +}; + +#if FMT_USE_VARIADIC_TEMPLATES +template +inline uint64_t make_type(const Arg &first, const Args & ... tail) +{ + return make_type(first) | (make_type(tail...) << 4); +} + +#else + +struct ArgType +{ + uint64_t type; + + ArgType() : type(0) {} + + template + ArgType(const T &arg) : type(make_type(arg)) {} +}; + +# define FMT_ARG_TYPE_DEFAULT(n) ArgType t##n = ArgType() + +inline uint64_t make_type(FMT_GEN15(FMT_ARG_TYPE_DEFAULT)) +{ + return t0.type | (t1.type << 4) | (t2.type << 8) | (t3.type << 12) | + (t4.type << 16) | (t5.type << 20) | (t6.type << 24) | (t7.type << 28) | + (t8.type << 32) | (t9.type << 36) | (t10.type << 40) | (t11.type << 44) | + (t12.type << 48) | (t13.type << 52) | (t14.type << 56); +} +#endif +} // namespace internal + +# define FMT_MAKE_TEMPLATE_ARG(n) typename T##n +# define FMT_MAKE_ARG_TYPE(n) T##n +# define FMT_MAKE_ARG(n) const T##n &v##n +# define FMT_ASSIGN_char(n) \ + arr[n] = fmt::internal::MakeValue< fmt::BasicFormatter >(v##n) +# define FMT_ASSIGN_wchar_t(n) \ + arr[n] = fmt::internal::MakeValue< fmt::BasicFormatter >(v##n) + +#if FMT_USE_VARIADIC_TEMPLATES +// Defines a variadic function returning void. +# define FMT_VARIADIC_VOID(func, arg_type) \ + template \ + void func(arg_type arg0, const Args & ... args) { \ + typedef fmt::internal::ArgArray ArgArray; \ + typename ArgArray::Type array{ \ + ArgArray::template make >(args)...}; \ + func(arg0, fmt::ArgList(fmt::internal::make_type(args...), array)); \ + } + +// Defines a variadic constructor. +# define FMT_VARIADIC_CTOR(ctor, func, arg0_type, arg1_type) \ + template \ + ctor(arg0_type arg0, arg1_type arg1, const Args & ... args) { \ + typedef fmt::internal::ArgArray ArgArray; \ + typename ArgArray::Type array{ \ + ArgArray::template make >(args)...}; \ + func(arg0, arg1, fmt::ArgList(fmt::internal::make_type(args...), array)); \ + } + +#else + +# define FMT_MAKE_REF(n) \ + fmt::internal::MakeValue< fmt::BasicFormatter >(v##n) +# define FMT_MAKE_REF2(n) v##n + +// Defines a wrapper for a function taking one argument of type arg_type +// and n additional arguments of arbitrary types. +# define FMT_WRAP1(func, arg_type, n) \ + template \ + inline void func(arg_type arg1, FMT_GEN(n, FMT_MAKE_ARG)) { \ + const fmt::internal::ArgArray::Type array = {FMT_GEN(n, FMT_MAKE_REF)}; \ + func(arg1, fmt::ArgList( \ + fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), array)); \ + } + +// Emulates a variadic function returning void on a pre-C++11 compiler. +# define FMT_VARIADIC_VOID(func, arg_type) \ + inline void func(arg_type arg) { func(arg, fmt::ArgList()); } \ + FMT_WRAP1(func, arg_type, 1) FMT_WRAP1(func, arg_type, 2) \ + FMT_WRAP1(func, arg_type, 3) FMT_WRAP1(func, arg_type, 4) \ + FMT_WRAP1(func, arg_type, 5) FMT_WRAP1(func, arg_type, 6) \ + FMT_WRAP1(func, arg_type, 7) FMT_WRAP1(func, arg_type, 8) \ + FMT_WRAP1(func, arg_type, 9) FMT_WRAP1(func, arg_type, 10) + +# define FMT_CTOR(ctor, func, arg0_type, arg1_type, n) \ + template \ + ctor(arg0_type arg0, arg1_type arg1, FMT_GEN(n, FMT_MAKE_ARG)) { \ + const fmt::internal::ArgArray::Type array = {FMT_GEN(n, FMT_MAKE_REF)}; \ + func(arg0, arg1, fmt::ArgList( \ + fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), array)); \ + } + +// Emulates a variadic constructor on a pre-C++11 compiler. +# define FMT_VARIADIC_CTOR(ctor, func, arg0_type, arg1_type) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 1) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 2) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 3) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 4) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 5) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 6) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 7) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 8) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 9) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 10) +#endif + +// Generates a comma-separated list with results of applying f to pairs +// (argument, index). +#define FMT_FOR_EACH1(f, x0) f(x0, 0) +#define FMT_FOR_EACH2(f, x0, x1) \ + FMT_FOR_EACH1(f, x0), f(x1, 1) +#define FMT_FOR_EACH3(f, x0, x1, x2) \ + FMT_FOR_EACH2(f, x0 ,x1), f(x2, 2) +#define FMT_FOR_EACH4(f, x0, x1, x2, x3) \ + FMT_FOR_EACH3(f, x0, x1, x2), f(x3, 3) +#define FMT_FOR_EACH5(f, x0, x1, x2, x3, x4) \ + FMT_FOR_EACH4(f, x0, x1, x2, x3), f(x4, 4) +#define FMT_FOR_EACH6(f, x0, x1, x2, x3, x4, x5) \ + FMT_FOR_EACH5(f, x0, x1, x2, x3, x4), f(x5, 5) +#define FMT_FOR_EACH7(f, x0, x1, x2, x3, x4, x5, x6) \ + FMT_FOR_EACH6(f, x0, x1, x2, x3, x4, x5), f(x6, 6) +#define FMT_FOR_EACH8(f, x0, x1, x2, x3, x4, x5, x6, x7) \ + FMT_FOR_EACH7(f, x0, x1, x2, x3, x4, x5, x6), f(x7, 7) +#define FMT_FOR_EACH9(f, x0, x1, x2, x3, x4, x5, x6, x7, x8) \ + FMT_FOR_EACH8(f, x0, x1, x2, x3, x4, x5, x6, x7), f(x8, 8) +#define FMT_FOR_EACH10(f, x0, x1, x2, x3, x4, x5, x6, x7, x8, x9) \ + FMT_FOR_EACH9(f, x0, x1, x2, x3, x4, x5, x6, x7, x8), f(x9, 9) + +/** + An error returned by an operating system or a language runtime, + for example a file opening error. +*/ +class SystemError : public internal::RuntimeError +{ +private: + FMT_API void init(int err_code, CStringRef format_str, ArgList args); + +protected: + int error_code_; + + typedef char Char; // For FMT_VARIADIC_CTOR. + + SystemError() {} + +public: + /** + \rst + Constructs a :class:`fmt::SystemError` object with a description + formatted with `fmt::format_system_error`. *message* and additional + arguments passed into the constructor are formatted similarly to + `fmt::format`. + + **Example**:: + + // This throws a SystemError with the description + // cannot open file 'madeup': No such file or directory + // or similar (system message may vary). + const char *filename = "madeup"; + std::FILE *file = std::fopen(filename, "r"); + if (!file) + throw fmt::SystemError(errno, "cannot open file '{}'", filename); + \endrst + */ + SystemError(int error_code, CStringRef message) + { + init(error_code, message, ArgList()); + } + FMT_DEFAULTED_COPY_CTOR(SystemError) + FMT_VARIADIC_CTOR(SystemError, init, int, CStringRef) + + FMT_API ~SystemError() FMT_DTOR_NOEXCEPT; + + int error_code() const + { + return error_code_; + } +}; + +/** + \rst + Formats an error returned by an operating system or a language runtime, + for example a file opening error, and writes it to *out* in the following + form: + + .. parsed-literal:: + **: ** + + where ** is the passed message and ** is + the system message corresponding to the error code. + *error_code* is a system error code as given by ``errno``. + If *error_code* is not a valid error code such as -1, the system message + may look like "Unknown error -1" and is platform-dependent. + \endrst + */ +FMT_API void format_system_error(fmt::Writer &out, int error_code, + fmt::StringRef message) FMT_NOEXCEPT; + +/** + \rst + This template provides operations for formatting and writing data into + a character stream. The output is stored in a buffer provided by a subclass + such as :class:`fmt::BasicMemoryWriter`. + + You can use one of the following typedefs for common character types: + + +---------+----------------------+ + | Type | Definition | + +=========+======================+ + | Writer | BasicWriter | + +---------+----------------------+ + | WWriter | BasicWriter | + +---------+----------------------+ + + \endrst + */ +template +class BasicWriter +{ +private: + // Output buffer. + Buffer &buffer_; + + FMT_DISALLOW_COPY_AND_ASSIGN(BasicWriter); + + typedef typename internal::CharTraits::CharPtr CharPtr; + +#if FMT_SECURE_SCL + // Returns pointer value. + static Char *get(CharPtr p) + { + return p.base(); + } +#else + static Char *get(Char *p) + { + return p; + } +#endif + + // Fills the padding around the content and returns the pointer to the + // content area. + static CharPtr fill_padding(CharPtr buffer, + unsigned total_size, std::size_t content_size, wchar_t fill); + + // Grows the buffer by n characters and returns a pointer to the newly + // allocated area. + CharPtr grow_buffer(std::size_t n) + { + std::size_t size = buffer_.size(); + buffer_.resize(size + n); + return internal::make_ptr(&buffer_[size], n); + } + + // Writes an unsigned decimal integer. + template + Char *write_unsigned_decimal(UInt value, unsigned prefix_size = 0) + { + unsigned num_digits = internal::count_digits(value); + Char *ptr = get(grow_buffer(prefix_size + num_digits)); + internal::format_decimal(ptr + prefix_size, value, num_digits); + return ptr; + } + + // Writes a decimal integer. + template + void write_decimal(Int value) + { + typedef typename internal::IntTraits::MainType MainType; + MainType abs_value = static_cast(value); + if (internal::is_negative(value)) + { + abs_value = 0 - abs_value; + *write_unsigned_decimal(abs_value, 1) = '-'; + } + else + { + write_unsigned_decimal(abs_value, 0); + } + } + + // Prepare a buffer for integer formatting. + CharPtr prepare_int_buffer(unsigned num_digits, + const EmptySpec &, const char *prefix, unsigned prefix_size) + { + unsigned size = prefix_size + num_digits; + CharPtr p = grow_buffer(size); + std::uninitialized_copy(prefix, prefix + prefix_size, p); + return p + size - 1; + } + + template + CharPtr prepare_int_buffer(unsigned num_digits, + const Spec &spec, const char *prefix, unsigned prefix_size); + + // Formats an integer. + template + void write_int(T value, Spec spec); + + // Formats a floating-point number (double or long double). + template + void write_double(T value, const Spec &spec); + + // Writes a formatted string. + template + CharPtr write_str(const StrChar *s, std::size_t size, const AlignSpec &spec); + + template + void write_str(const internal::Arg::StringValue &str, + const Spec &spec); + + // This following methods are private to disallow writing wide characters + // and strings to a char stream. If you want to print a wide string as a + // pointer as std::ostream does, cast it to const void*. + // Do not implement! + void operator<<(typename internal::WCharHelper::Unsupported); + void operator<<( + typename internal::WCharHelper::Unsupported); + + // Appends floating-point length specifier to the format string. + // The second argument is only used for overload resolution. + void append_float_length(Char *&format_ptr, long double) + { + *format_ptr++ = 'L'; + } + + template + void append_float_length(Char *&, T) {} + + template + friend class internal::ArgFormatterBase; + + template + friend class BasicPrintfArgFormatter; + +protected: + /** + Constructs a ``BasicWriter`` object. + */ + explicit BasicWriter(Buffer &b) : buffer_(b) {} + +public: + /** + \rst + Destroys a ``BasicWriter`` object. + \endrst + */ + virtual ~BasicWriter() {} + + /** + Returns the total number of characters written. + */ + std::size_t size() const + { + return buffer_.size(); + } + + /** + Returns a pointer to the output buffer content. No terminating null + character is appended. + */ + const Char *data() const FMT_NOEXCEPT + { + return &buffer_[0]; + } + + /** + Returns a pointer to the output buffer content with terminating null + character appended. + */ + const Char *c_str() const + { + std::size_t size = buffer_.size(); + buffer_.reserve(size + 1); + buffer_[size] = '\0'; + return &buffer_[0]; + } + + /** + \rst + Returns the content of the output buffer as an `std::string`. + \endrst + */ + std::basic_string str() const + { + return std::basic_string(&buffer_[0], buffer_.size()); + } + + /** + \rst + Writes formatted data. + + *args* is an argument list representing arbitrary arguments. + + **Example**:: + + MemoryWriter out; + out.write("Current point:\n"); + out.write("({:+f}, {:+f})", -3.14, 3.14); + + This will write the following output to the ``out`` object: + + .. code-block:: none + + Current point: + (-3.140000, +3.140000) + + The output can be accessed using :func:`data()`, :func:`c_str` or + :func:`str` methods. + + See also :ref:`syntax`. + \endrst + */ + void write(BasicCStringRef format, ArgList args) + { + BasicFormatter(args, *this).format(format); + } + FMT_VARIADIC_VOID(write, BasicCStringRef) + + BasicWriter &operator<<(int value) + { + write_decimal(value); + return *this; + } + BasicWriter &operator<<(unsigned value) + { + return *this << IntFormatSpec(value); + } + BasicWriter &operator<<(long value) + { + write_decimal(value); + return *this; + } + BasicWriter &operator<<(unsigned long value) + { + return *this << IntFormatSpec(value); + } + BasicWriter &operator<<(LongLong value) + { + write_decimal(value); + return *this; + } + + /** + \rst + Formats *value* and writes it to the stream. + \endrst + */ + BasicWriter &operator<<(ULongLong value) + { + return *this << IntFormatSpec(value); + } + + BasicWriter &operator<<(double value) + { + write_double(value, FormatSpec()); + return *this; + } + + /** + \rst + Formats *value* using the general format for floating-point numbers + (``'g'``) and writes it to the stream. + \endrst + */ + BasicWriter &operator<<(long double value) + { + write_double(value, FormatSpec()); + return *this; + } + + /** + Writes a character to the stream. + */ + BasicWriter &operator<<(char value) + { + buffer_.push_back(value); + return *this; + } + + BasicWriter &operator<<( + typename internal::WCharHelper::Supported value) + { + buffer_.push_back(value); + return *this; + } + + /** + \rst + Writes *value* to the stream. + \endrst + */ + BasicWriter &operator<<(fmt::BasicStringRef value) + { + const Char *str = value.data(); + buffer_.append(str, str + value.size()); + return *this; + } + + BasicWriter &operator<<( + typename internal::WCharHelper::Supported value) + { + const char *str = value.data(); + buffer_.append(str, str + value.size()); + return *this; + } + + template + BasicWriter &operator<<(IntFormatSpec spec) + { + internal::CharTraits::convert(FillChar()); + write_int(spec.value(), spec); + return *this; + } + + template + BasicWriter &operator<<(const StrFormatSpec &spec) + { + const StrChar *s = spec.str(); + write_str(s, std::char_traits::length(s), spec); + return *this; + } + + void clear() FMT_NOEXCEPT { buffer_.clear(); } + + Buffer &buffer() FMT_NOEXCEPT { return buffer_; } +}; + +template +template +typename BasicWriter::CharPtr BasicWriter::write_str( + const StrChar *s, std::size_t size, const AlignSpec &spec) +{ + CharPtr out = CharPtr(); + if (spec.width() > size) + { + out = grow_buffer(spec.width()); + Char fill = internal::CharTraits::cast(spec.fill()); + if (spec.align() == ALIGN_RIGHT) + { + std::uninitialized_fill_n(out, spec.width() - size, fill); + out += spec.width() - size; + } + else if (spec.align() == ALIGN_CENTER) + { + out = fill_padding(out, spec.width(), size, fill); + } + else + { + std::uninitialized_fill_n(out + size, spec.width() - size, fill); + } + } + else + { + out = grow_buffer(size); + } + std::uninitialized_copy(s, s + size, out); + return out; +} + +template +template +void BasicWriter::write_str( + const internal::Arg::StringValue &s, const Spec &spec) +{ + // Check if StrChar is convertible to Char. + internal::CharTraits::convert(StrChar()); + if (spec.type_ && spec.type_ != 's') + internal::report_unknown_type(spec.type_, "string"); + const StrChar *str_value = s.value; + std::size_t str_size = s.size; + if (str_size == 0) + { + if (!str_value) + { + FMT_THROW(FormatError("string pointer is null")); + } + } + std::size_t precision = static_cast(spec.precision_); + if (spec.precision_ >= 0 && precision < str_size) + str_size = precision; + write_str(str_value, str_size, spec); +} + +template +typename BasicWriter::CharPtr +BasicWriter::fill_padding( + CharPtr buffer, unsigned total_size, + std::size_t content_size, wchar_t fill) +{ + std::size_t padding = total_size - content_size; + std::size_t left_padding = padding / 2; + Char fill_char = internal::CharTraits::cast(fill); + std::uninitialized_fill_n(buffer, left_padding, fill_char); + buffer += left_padding; + CharPtr content = buffer; + std::uninitialized_fill_n(buffer + content_size, + padding - left_padding, fill_char); + return content; +} + +template +template +typename BasicWriter::CharPtr +BasicWriter::prepare_int_buffer( + unsigned num_digits, const Spec &spec, + const char *prefix, unsigned prefix_size) +{ + unsigned width = spec.width(); + Alignment align = spec.align(); + Char fill = internal::CharTraits::cast(spec.fill()); + if (spec.precision() > static_cast(num_digits)) + { + // Octal prefix '0' is counted as a digit, so ignore it if precision + // is specified. + if (prefix_size > 0 && prefix[prefix_size - 1] == '0') + --prefix_size; + unsigned number_size = + prefix_size + internal::to_unsigned(spec.precision()); + AlignSpec subspec(number_size, '0', ALIGN_NUMERIC); + if (number_size >= width) + return prepare_int_buffer(num_digits, subspec, prefix, prefix_size); + buffer_.reserve(width); + unsigned fill_size = width - number_size; + if (align != ALIGN_LEFT) + { + CharPtr p = grow_buffer(fill_size); + std::uninitialized_fill(p, p + fill_size, fill); + } + CharPtr result = prepare_int_buffer( + num_digits, subspec, prefix, prefix_size); + if (align == ALIGN_LEFT) + { + CharPtr p = grow_buffer(fill_size); + std::uninitialized_fill(p, p + fill_size, fill); + } + return result; + } + unsigned size = prefix_size + num_digits; + if (width <= size) + { + CharPtr p = grow_buffer(size); + std::uninitialized_copy(prefix, prefix + prefix_size, p); + return p + size - 1; + } + CharPtr p = grow_buffer(width); + CharPtr end = p + width; + if (align == ALIGN_LEFT) + { + std::uninitialized_copy(prefix, prefix + prefix_size, p); + p += size; + std::uninitialized_fill(p, end, fill); + } + else if (align == ALIGN_CENTER) + { + p = fill_padding(p, width, size, fill); + std::uninitialized_copy(prefix, prefix + prefix_size, p); + p += size; + } + else + { + if (align == ALIGN_NUMERIC) + { + if (prefix_size != 0) + { + p = std::uninitialized_copy(prefix, prefix + prefix_size, p); + size -= prefix_size; + } + } + else + { + std::uninitialized_copy(prefix, prefix + prefix_size, end - size); + } + std::uninitialized_fill(p, end - size, fill); + p = end; + } + return p - 1; +} + +template +template +void BasicWriter::write_int(T value, Spec spec) +{ + unsigned prefix_size = 0; + typedef typename internal::IntTraits::MainType UnsignedType; + UnsignedType abs_value = static_cast(value); + char prefix[4] = ""; + if (internal::is_negative(value)) + { + prefix[0] = '-'; + ++prefix_size; + abs_value = 0 - abs_value; + } + else if (spec.flag(SIGN_FLAG)) + { + prefix[0] = spec.flag(PLUS_FLAG) ? '+' : ' '; + ++prefix_size; + } + switch (spec.type()) + { + case 0: + case 'd': + { + unsigned num_digits = internal::count_digits(abs_value); + CharPtr p = prepare_int_buffer(num_digits, spec, prefix, prefix_size) + 1; + internal::format_decimal(get(p), abs_value, 0); + break; + } + case 'x': + case 'X': + { + UnsignedType n = abs_value; + if (spec.flag(HASH_FLAG)) + { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = spec.type_prefix(); + } + unsigned num_digits = 0; + do + { + ++num_digits; + } + while ((n >>= 4) != 0); + Char *p = get(prepare_int_buffer( + num_digits, spec, prefix, prefix_size)); + n = abs_value; + const char *digits = spec.type() == 'x' ? + "0123456789abcdef" : "0123456789ABCDEF"; + do + { + *p-- = digits[n & 0xf]; + } + while ((n >>= 4) != 0); + break; + } + case 'b': + case 'B': + { + UnsignedType n = abs_value; + if (spec.flag(HASH_FLAG)) + { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = spec.type_prefix(); + } + unsigned num_digits = 0; + do + { + ++num_digits; + } + while ((n >>= 1) != 0); + Char *p = get(prepare_int_buffer(num_digits, spec, prefix, prefix_size)); + n = abs_value; + do + { + *p-- = static_cast('0' + (n & 1)); + } + while ((n >>= 1) != 0); + break; + } + case 'o': + { + UnsignedType n = abs_value; + if (spec.flag(HASH_FLAG)) + prefix[prefix_size++] = '0'; + unsigned num_digits = 0; + do + { + ++num_digits; + } + while ((n >>= 3) != 0); + Char *p = get(prepare_int_buffer(num_digits, spec, prefix, prefix_size)); + n = abs_value; + do + { + *p-- = static_cast('0' + (n & 7)); + } + while ((n >>= 3) != 0); + break; + } + case 'n': + { + unsigned num_digits = internal::count_digits(abs_value); + fmt::StringRef sep = ""; +#if !(defined(ANDROID) || defined(__ANDROID__)) + sep = internal::thousands_sep(std::localeconv()); +#endif + unsigned size = static_cast( + num_digits + sep.size() * ((num_digits - 1) / 3)); + CharPtr p = prepare_int_buffer(size, spec, prefix, prefix_size) + 1; + internal::format_decimal(get(p), abs_value, 0, internal::ThousandsSep(sep)); + break; + } + default: + internal::report_unknown_type( + spec.type(), spec.flag(CHAR_FLAG) ? "char" : "integer"); + break; + } +} + +template +template +void BasicWriter::write_double(T value, const Spec &spec) +{ + // Check type. + char type = spec.type(); + bool upper = false; + switch (type) + { + case 0: + type = 'g'; + break; + case 'e': + case 'f': + case 'g': + case 'a': + break; + case 'F': +#if FMT_MSC_VER + // MSVC's printf doesn't support 'F'. + type = 'f'; +#endif + // Fall through. + case 'E': + case 'G': + case 'A': + upper = true; + break; + default: + internal::report_unknown_type(type, "double"); + break; + } + + char sign = 0; + // Use isnegative instead of value < 0 because the latter is always + // false for NaN. + if (internal::FPUtil::isnegative(static_cast(value))) + { + sign = '-'; + value = -value; + } + else if (spec.flag(SIGN_FLAG)) + { + sign = spec.flag(PLUS_FLAG) ? '+' : ' '; + } + + if (internal::FPUtil::isnotanumber(value)) + { + // Format NaN ourselves because sprintf's output is not consistent + // across platforms. + std::size_t nan_size = 4; + const char *nan = upper ? " NAN" : " nan"; + if (!sign) + { + --nan_size; + ++nan; + } + CharPtr out = write_str(nan, nan_size, spec); + if (sign) + *out = sign; + return; + } + + if (internal::FPUtil::isinfinity(value)) + { + // Format infinity ourselves because sprintf's output is not consistent + // across platforms. + std::size_t inf_size = 4; + const char *inf = upper ? " INF" : " inf"; + if (!sign) + { + --inf_size; + ++inf; + } + CharPtr out = write_str(inf, inf_size, spec); + if (sign) + *out = sign; + return; + } + + std::size_t offset = buffer_.size(); + unsigned width = spec.width(); + if (sign) + { + buffer_.reserve(buffer_.size() + (width > 1u ? width : 1u)); + if (width > 0) + --width; + ++offset; + } + + // Build format string. + enum { MAX_FORMAT_SIZE = 10}; // longest format: %#-*.*Lg + Char format[MAX_FORMAT_SIZE]; + Char *format_ptr = format; + *format_ptr++ = '%'; + unsigned width_for_sprintf = width; + if (spec.flag(HASH_FLAG)) + *format_ptr++ = '#'; + if (spec.align() == ALIGN_CENTER) + { + width_for_sprintf = 0; + } + else + { + if (spec.align() == ALIGN_LEFT) + *format_ptr++ = '-'; + if (width != 0) + *format_ptr++ = '*'; + } + if (spec.precision() >= 0) + { + *format_ptr++ = '.'; + *format_ptr++ = '*'; + } + + append_float_length(format_ptr, value); + *format_ptr++ = type; + *format_ptr = '\0'; + + // Format using snprintf. + Char fill = internal::CharTraits::cast(spec.fill()); + unsigned n = 0; + Char *start = FMT_NULL; + for (;;) + { + std::size_t buffer_size = buffer_.capacity() - offset; +#if FMT_MSC_VER + // MSVC's vsnprintf_s doesn't work with zero size, so reserve + // space for at least one extra character to make the size non-zero. + // Note that the buffer's capacity will increase by more than 1. + if (buffer_size == 0) + { + buffer_.reserve(offset + 1); + buffer_size = buffer_.capacity() - offset; + } +#endif + start = &buffer_[offset]; + int result = internal::CharTraits::format_float( + start, buffer_size, format, width_for_sprintf, spec.precision(), value); + if (result >= 0) + { + n = internal::to_unsigned(result); + if (offset + n < buffer_.capacity()) + break; // The buffer is large enough - continue with formatting. + buffer_.reserve(offset + n + 1); + } + else + { + // If result is negative we ask to increase the capacity by at least 1, + // but as std::vector, the buffer grows exponentially. + buffer_.reserve(buffer_.capacity() + 1); + } + } + if (sign) + { + if ((spec.align() != ALIGN_RIGHT && spec.align() != ALIGN_DEFAULT) || + *start != ' ') + { + *(start - 1) = sign; + sign = 0; + } + else + { + *(start - 1) = fill; + } + ++n; + } + if (spec.align() == ALIGN_CENTER && spec.width() > n) + { + width = spec.width(); + CharPtr p = grow_buffer(width); + std::memmove(get(p) + (width - n) / 2, get(p), n * sizeof(Char)); + fill_padding(p, spec.width(), n, fill); + return; + } + if (spec.fill() != ' ' || sign) + { + while (*start == ' ') + *start++ = fill; + if (sign) + *(start - 1) = sign; + } + grow_buffer(n); +} + +/** + \rst + This class template provides operations for formatting and writing data + into a character stream. The output is stored in a memory buffer that grows + dynamically. + + You can use one of the following typedefs for common character types + and the standard allocator: + + +---------------+-----------------------------------------------------+ + | Type | Definition | + +===============+=====================================================+ + | MemoryWriter | BasicMemoryWriter> | + +---------------+-----------------------------------------------------+ + | WMemoryWriter | BasicMemoryWriter> | + +---------------+-----------------------------------------------------+ + + **Example**:: + + MemoryWriter out; + out << "The answer is " << 42 << "\n"; + out.write("({:+f}, {:+f})", -3.14, 3.14); + + This will write the following output to the ``out`` object: + + .. code-block:: none + + The answer is 42 + (-3.140000, +3.140000) + + The output can be converted to an ``std::string`` with ``out.str()`` or + accessed as a C string with ``out.c_str()``. + \endrst + */ +template > +class BasicMemoryWriter : public BasicWriter +{ +private: + internal::MemoryBuffer buffer_; + +public: + explicit BasicMemoryWriter(const Allocator& alloc = Allocator()) + : BasicWriter(buffer_), buffer_(alloc) {} + +#if FMT_USE_RVALUE_REFERENCES + /** + \rst + Constructs a :class:`fmt::BasicMemoryWriter` object moving the content + of the other object to it. + \endrst + */ + BasicMemoryWriter(BasicMemoryWriter &&other) + : BasicWriter(buffer_), buffer_(std::move(other.buffer_)) + { + } + + /** + \rst + Moves the content of the other ``BasicMemoryWriter`` object to this one. + \endrst + */ + BasicMemoryWriter &operator=(BasicMemoryWriter &&other) + { + buffer_ = std::move(other.buffer_); + return *this; + } +#endif +}; + +typedef BasicMemoryWriter MemoryWriter; +typedef BasicMemoryWriter WMemoryWriter; + +/** + \rst + This class template provides operations for formatting and writing data + into a fixed-size array. For writing into a dynamically growing buffer + use :class:`fmt::BasicMemoryWriter`. + + Any write method will throw ``std::runtime_error`` if the output doesn't fit + into the array. + + You can use one of the following typedefs for common character types: + + +--------------+---------------------------+ + | Type | Definition | + +==============+===========================+ + | ArrayWriter | BasicArrayWriter | + +--------------+---------------------------+ + | WArrayWriter | BasicArrayWriter | + +--------------+---------------------------+ + \endrst + */ +template +class BasicArrayWriter : public BasicWriter +{ +private: + internal::FixedBuffer buffer_; + +public: + /** + \rst + Constructs a :class:`fmt::BasicArrayWriter` object for *array* of the + given size. + \endrst + */ + BasicArrayWriter(Char *array, std::size_t size) + : BasicWriter(buffer_), buffer_(array, size) {} + + /** + \rst + Constructs a :class:`fmt::BasicArrayWriter` object for *array* of the + size known at compile time. + \endrst + */ + template + explicit BasicArrayWriter(Char (&array)[SIZE]) + : BasicWriter(buffer_), buffer_(array, SIZE) {} +}; + +typedef BasicArrayWriter ArrayWriter; +typedef BasicArrayWriter WArrayWriter; + +// Reports a system error without throwing an exception. +// Can be used to report errors from destructors. +FMT_API void report_system_error(int error_code, + StringRef message) FMT_NOEXCEPT; + +#if FMT_USE_WINDOWS_H + +/** A Windows error. */ +class WindowsError : public SystemError +{ +private: + FMT_API void init(int error_code, CStringRef format_str, ArgList args); + +public: + /** + \rst + Constructs a :class:`fmt::WindowsError` object with the description + of the form + + .. parsed-literal:: + **: ** + + where ** is the formatted message and ** is the + system message corresponding to the error code. + *error_code* is a Windows error code as given by ``GetLastError``. + If *error_code* is not a valid error code such as -1, the system message + will look like "error -1". + + **Example**:: + + // This throws a WindowsError with the description + // cannot open file 'madeup': The system cannot find the file specified. + // or similar (system message may vary). + const char *filename = "madeup"; + LPOFSTRUCT of = LPOFSTRUCT(); + HFILE file = OpenFile(filename, &of, OF_READ); + if (file == HFILE_ERROR) { + throw fmt::WindowsError(GetLastError(), + "cannot open file '{}'", filename); + } + \endrst + */ + WindowsError(int error_code, CStringRef message) + { + init(error_code, message, ArgList()); + } + FMT_VARIADIC_CTOR(WindowsError, init, int, CStringRef) +}; + +// Reports a Windows error without throwing an exception. +// Can be used to report errors from destructors. +FMT_API void report_windows_error(int error_code, + StringRef message) FMT_NOEXCEPT; + +#endif + +enum Color { BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE }; + +/** + Formats a string and prints it to stdout using ANSI escape sequences + to specify color (experimental). + Example: + print_colored(fmt::RED, "Elapsed time: {0:.2f} seconds", 1.23); + */ +FMT_API void print_colored(Color c, CStringRef format, ArgList args); + +/** + \rst + Formats arguments and returns the result as a string. + + **Example**:: + + std::string message = format("The answer is {}", 42); + \endrst +*/ +inline std::string format(CStringRef format_str, ArgList args) +{ + MemoryWriter w; + w.write(format_str, args); + return w.str(); +} + +inline std::wstring format(WCStringRef format_str, ArgList args) +{ + WMemoryWriter w; + w.write(format_str, args); + return w.str(); +} + +/** + \rst + Prints formatted data to the file *f*. + + **Example**:: + + print(stderr, "Don't {}!", "panic"); + \endrst + */ +FMT_API void print(std::FILE *f, CStringRef format_str, ArgList args); + +/** + \rst + Prints formatted data to ``stdout``. + + **Example**:: + + print("Elapsed time: {0:.2f} seconds", 1.23); + \endrst + */ +FMT_API void print(CStringRef format_str, ArgList args); + +/** + Fast integer formatter. + */ +class FormatInt +{ +private: + // Buffer should be large enough to hold all digits (digits10 + 1), + // a sign and a null character. + enum {BUFFER_SIZE = std::numeric_limits::digits10 + 3}; + mutable char buffer_[BUFFER_SIZE]; + char *str_; + + // Formats value in reverse and returns the number of digits. + char *format_decimal(ULongLong value) + { + char *buffer_end = buffer_ + BUFFER_SIZE - 1; + while (value >= 100) + { + // Integer division is slow so do it for a group of two digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + unsigned index = static_cast((value % 100) * 2); + value /= 100; + *--buffer_end = internal::Data::DIGITS[index + 1]; + *--buffer_end = internal::Data::DIGITS[index]; + } + if (value < 10) + { + *--buffer_end = static_cast('0' + value); + return buffer_end; + } + unsigned index = static_cast(value * 2); + *--buffer_end = internal::Data::DIGITS[index + 1]; + *--buffer_end = internal::Data::DIGITS[index]; + return buffer_end; + } + + void FormatSigned(LongLong value) + { + ULongLong abs_value = static_cast(value); + bool negative = value < 0; + if (negative) + abs_value = 0 - abs_value; + str_ = format_decimal(abs_value); + if (negative) + *--str_ = '-'; + } + +public: + explicit FormatInt(int value) + { + FormatSigned(value); + } + explicit FormatInt(long value) + { + FormatSigned(value); + } + explicit FormatInt(LongLong value) + { + FormatSigned(value); + } + explicit FormatInt(unsigned value) : str_(format_decimal(value)) {} + explicit FormatInt(unsigned long value) : str_(format_decimal(value)) {} + explicit FormatInt(ULongLong value) : str_(format_decimal(value)) {} + + /** Returns the number of characters written to the output buffer. */ + std::size_t size() const + { + return internal::to_unsigned(buffer_ - str_ + BUFFER_SIZE - 1); + } + + /** + Returns a pointer to the output buffer content. No terminating null + character is appended. + */ + const char *data() const + { + return str_; + } + + /** + Returns a pointer to the output buffer content with terminating null + character appended. + */ + const char *c_str() const + { + buffer_[BUFFER_SIZE - 1] = '\0'; + return str_; + } + + /** + \rst + Returns the content of the output buffer as an ``std::string``. + \endrst + */ + std::string str() const + { + return std::string(str_, size()); + } +}; + +// Formats a decimal integer value writing into buffer and returns +// a pointer to the end of the formatted string. This function doesn't +// write a terminating null character. +template +inline void format_decimal(char *&buffer, T value) +{ + typedef typename internal::IntTraits::MainType MainType; + MainType abs_value = static_cast(value); + if (internal::is_negative(value)) + { + *buffer++ = '-'; + abs_value = 0 - abs_value; + } + if (abs_value < 100) + { + if (abs_value < 10) + { + *buffer++ = static_cast('0' + abs_value); + return; + } + unsigned index = static_cast(abs_value * 2); + *buffer++ = internal::Data::DIGITS[index]; + *buffer++ = internal::Data::DIGITS[index + 1]; + return; + } + unsigned num_digits = internal::count_digits(abs_value); + internal::format_decimal(buffer, abs_value, num_digits); + buffer += num_digits; +} + +/** + \rst + Returns a named argument for formatting functions. + + **Example**:: + + print("Elapsed time: {s:.2f} seconds", arg("s", 1.23)); + + \endrst + */ +template +inline internal::NamedArgWithType arg(StringRef name, const T &arg) +{ + return internal::NamedArgWithType(name, arg); +} + +template +inline internal::NamedArgWithType arg(WStringRef name, const T &arg) +{ + return internal::NamedArgWithType(name, arg); +} + +// The following two functions are deleted intentionally to disable +// nested named arguments as in ``format("{}", arg("a", arg("b", 42)))``. +template +void arg(StringRef, const internal::NamedArg&) FMT_DELETED_OR_UNDEFINED; +template +void arg(WStringRef, const internal::NamedArg&) FMT_DELETED_OR_UNDEFINED; +} + +#if FMT_GCC_VERSION +// Use the system_header pragma to suppress warnings about variadic macros +// because suppressing -Wvariadic-macros with the diagnostic pragma doesn't +// work. It is used at the end because we want to suppress as little warnings +// as possible. +# pragma GCC system_header +#endif + +// This is used to work around VC++ bugs in handling variadic macros. +#define FMT_EXPAND(args) args + +// Returns the number of arguments. +// Based on https://groups.google.com/forum/#!topic/comp.std.c/d-6Mj5Lko_s. +#define FMT_NARG(...) FMT_NARG_(__VA_ARGS__, FMT_RSEQ_N()) +#define FMT_NARG_(...) FMT_EXPAND(FMT_ARG_N(__VA_ARGS__)) +#define FMT_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N +#define FMT_RSEQ_N() 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 + +#define FMT_FOR_EACH_(N, f, ...) \ + FMT_EXPAND(FMT_CONCAT(FMT_FOR_EACH, N)(f, __VA_ARGS__)) +#define FMT_FOR_EACH(f, ...) \ + FMT_EXPAND(FMT_FOR_EACH_(FMT_NARG(__VA_ARGS__), f, __VA_ARGS__)) + +#define FMT_ADD_ARG_NAME(type, index) type arg##index +#define FMT_GET_ARG_NAME(type, index) arg##index + +#if FMT_USE_VARIADIC_TEMPLATES +# define FMT_VARIADIC_(Char, ReturnType, func, call, ...) \ + template \ + ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \ + const Args & ... args) { \ + typedef fmt::internal::ArgArray ArgArray; \ + typename ArgArray::Type array{ \ + ArgArray::template make >(args)...}; \ + call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), \ + fmt::ArgList(fmt::internal::make_type(args...), array)); \ + } +#else +// Defines a wrapper for a function taking __VA_ARGS__ arguments +// and n additional arguments of arbitrary types. +# define FMT_WRAP(Char, ReturnType, func, call, n, ...) \ + template \ + inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \ + FMT_GEN(n, FMT_MAKE_ARG)) { \ + fmt::internal::ArgArray::Type arr; \ + FMT_GEN(n, FMT_ASSIGN_##Char); \ + call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList( \ + fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), arr)); \ + } + +# define FMT_VARIADIC_(Char, ReturnType, func, call, ...) \ + inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__)) { \ + call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList()); \ + } \ + FMT_WRAP(Char, ReturnType, func, call, 1, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 2, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 3, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 4, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 5, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 6, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 7, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 8, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 9, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 10, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 11, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 12, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 13, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 14, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 15, __VA_ARGS__) +#endif // FMT_USE_VARIADIC_TEMPLATES + +/** + \rst + Defines a variadic function with the specified return type, function name + and argument types passed as variable arguments to this macro. + + **Example**:: + + void print_error(const char *file, int line, const char *format, + fmt::ArgList args) { + fmt::print("{}: {}: ", file, line); + fmt::print(format, args); + } + FMT_VARIADIC(void, print_error, const char *, int, const char *) + + ``FMT_VARIADIC`` is used for compatibility with legacy C++ compilers that + don't implement variadic templates. You don't have to use this macro if + you don't need legacy compiler support and can use variadic templates + directly:: + + template + void print_error(const char *file, int line, const char *format, + const Args & ... args) { + fmt::print("{}: {}: ", file, line); + fmt::print(format, args...); + } + \endrst + */ +#define FMT_VARIADIC(ReturnType, func, ...) \ + FMT_VARIADIC_(char, ReturnType, func, return func, __VA_ARGS__) + +#define FMT_VARIADIC_W(ReturnType, func, ...) \ + FMT_VARIADIC_(wchar_t, ReturnType, func, return func, __VA_ARGS__) + +#define FMT_CAPTURE_ARG_(id, index) ::fmt::arg(#id, id) + +#define FMT_CAPTURE_ARG_W_(id, index) ::fmt::arg(L###id, id) + +/** + \rst + Convenient macro to capture the arguments' names and values into several + ``fmt::arg(name, value)``. + + **Example**:: + + int x = 1, y = 2; + print("point: ({x}, {y})", FMT_CAPTURE(x, y)); + // same as: + // print("point: ({x}, {y})", arg("x", x), arg("y", y)); + + \endrst + */ +#define FMT_CAPTURE(...) FMT_FOR_EACH(FMT_CAPTURE_ARG_, __VA_ARGS__) + +#define FMT_CAPTURE_W(...) FMT_FOR_EACH(FMT_CAPTURE_ARG_W_, __VA_ARGS__) + +namespace fmt +{ +FMT_VARIADIC(std::string, format, CStringRef) +FMT_VARIADIC_W(std::wstring, format, WCStringRef) +FMT_VARIADIC(void, print, CStringRef) +FMT_VARIADIC(void, print, std::FILE *, CStringRef) +FMT_VARIADIC(void, print_colored, Color, CStringRef) + +namespace internal +{ +template +inline bool is_name_start(Char c) +{ + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; +} + +// Parses an unsigned integer advancing s to the end of the parsed input. +// This function assumes that the first character of s is a digit. +template +unsigned parse_nonnegative_int(const Char *&s) +{ + assert('0' <= *s && *s <= '9'); + unsigned value = 0; + do + { + unsigned new_value = value * 10 + (*s++ - '0'); + // Check if value wrapped around. + if (new_value < value) + { + value = (std::numeric_limits::max)(); + break; + } + value = new_value; + } + while ('0' <= *s && *s <= '9'); + // Convert to unsigned to prevent a warning. + unsigned max_int = (std::numeric_limits::max)(); + if (value > max_int) + FMT_THROW(FormatError("number is too big")); + return value; +} + +inline void require_numeric_argument(const Arg &arg, char spec) +{ + if (arg.type > Arg::LAST_NUMERIC_TYPE) + { + std::string message = + fmt::format("format specifier '{}' requires numeric argument", spec); + FMT_THROW(fmt::FormatError(message)); + } +} + +template +void check_sign(const Char *&s, const Arg &arg) +{ + char sign = static_cast(*s); + require_numeric_argument(arg, sign); + if (arg.type == Arg::UINT || arg.type == Arg::ULONG_LONG) + { + FMT_THROW(FormatError(fmt::format( + "format specifier '{}' requires signed argument", sign))); + } + ++s; +} +} // namespace internal + +template +inline internal::Arg BasicFormatter::get_arg( + BasicStringRef arg_name, const char *&error) +{ + if (check_no_auto_index(error)) + { + map_.init(args()); + const internal::Arg *arg = map_.find(arg_name); + if (arg) + return *arg; + error = "argument not found"; + } + return internal::Arg(); +} + +template +inline internal::Arg BasicFormatter::parse_arg_index(const Char *&s) +{ + const char *error = FMT_NULL; + internal::Arg arg = *s < '0' || *s > '9' ? + next_arg(error) : get_arg(internal::parse_nonnegative_int(s), error); + if (error) + { + FMT_THROW(FormatError( + *s != '}' && *s != ':' ? "invalid format string" : error)); + } + return arg; +} + +template +inline internal::Arg BasicFormatter::parse_arg_name(const Char *&s) +{ + assert(internal::is_name_start(*s)); + const Char *start = s; + Char c; + do + { + c = *++s; + } + while (internal::is_name_start(c) || ('0' <= c && c <= '9')); + const char *error = FMT_NULL; + internal::Arg arg = get_arg(BasicStringRef(start, s - start), error); + if (error) + FMT_THROW(FormatError(error)); + return arg; +} + +template +const Char *BasicFormatter::format( + const Char *&format_str, const internal::Arg &arg) +{ + using internal::Arg; + const Char *s = format_str; + typename ArgFormatter::SpecType spec; + if (*s == ':') + { + if (arg.type == Arg::CUSTOM) + { + arg.custom.format(this, arg.custom.value, &s); + return s; + } + ++s; + // Parse fill and alignment. + if (Char c = *s) + { + const Char *p = s + 1; + spec.align_ = ALIGN_DEFAULT; + do + { + switch (*p) + { + case '<': + spec.align_ = ALIGN_LEFT; + break; + case '>': + spec.align_ = ALIGN_RIGHT; + break; + case '=': + spec.align_ = ALIGN_NUMERIC; + break; + case '^': + spec.align_ = ALIGN_CENTER; + break; + } + if (spec.align_ != ALIGN_DEFAULT) + { + if (p != s) + { + if (c == '}') break; + if (c == '{') + FMT_THROW(FormatError("invalid fill character '{'")); + s += 2; + spec.fill_ = c; + } + else ++s; + if (spec.align_ == ALIGN_NUMERIC) + require_numeric_argument(arg, '='); + break; + } + } + while (--p >= s); + } + + // Parse sign. + switch (*s) + { + case '+': + check_sign(s, arg); + spec.flags_ |= SIGN_FLAG | PLUS_FLAG; + break; + case '-': + check_sign(s, arg); + spec.flags_ |= MINUS_FLAG; + break; + case ' ': + check_sign(s, arg); + spec.flags_ |= SIGN_FLAG; + break; + } + + if (*s == '#') + { + require_numeric_argument(arg, '#'); + spec.flags_ |= HASH_FLAG; + ++s; + } + + // Parse zero flag. + if (*s == '0') + { + require_numeric_argument(arg, '0'); + spec.align_ = ALIGN_NUMERIC; + spec.fill_ = '0'; + ++s; + } + + // Parse width. + if ('0' <= *s && *s <= '9') + { + spec.width_ = internal::parse_nonnegative_int(s); + } + else if (*s == '{') + { + ++s; + Arg width_arg = internal::is_name_start(*s) ? + parse_arg_name(s) : parse_arg_index(s); + if (*s++ != '}') + FMT_THROW(FormatError("invalid format string")); + ULongLong value = 0; + switch (width_arg.type) + { + case Arg::INT: + if (width_arg.int_value < 0) + FMT_THROW(FormatError("negative width")); + value = width_arg.int_value; + break; + case Arg::UINT: + value = width_arg.uint_value; + break; + case Arg::LONG_LONG: + if (width_arg.long_long_value < 0) + FMT_THROW(FormatError("negative width")); + value = width_arg.long_long_value; + break; + case Arg::ULONG_LONG: + value = width_arg.ulong_long_value; + break; + default: + FMT_THROW(FormatError("width is not integer")); + } + if (value > (std::numeric_limits::max)()) + FMT_THROW(FormatError("number is too big")); + spec.width_ = static_cast(value); + } + + // Parse precision. + if (*s == '.') + { + ++s; + spec.precision_ = 0; + if ('0' <= *s && *s <= '9') + { + spec.precision_ = internal::parse_nonnegative_int(s); + } + else if (*s == '{') + { + ++s; + Arg precision_arg = internal::is_name_start(*s) ? + parse_arg_name(s) : parse_arg_index(s); + if (*s++ != '}') + FMT_THROW(FormatError("invalid format string")); + ULongLong value = 0; + switch (precision_arg.type) + { + case Arg::INT: + if (precision_arg.int_value < 0) + FMT_THROW(FormatError("negative precision")); + value = precision_arg.int_value; + break; + case Arg::UINT: + value = precision_arg.uint_value; + break; + case Arg::LONG_LONG: + if (precision_arg.long_long_value < 0) + FMT_THROW(FormatError("negative precision")); + value = precision_arg.long_long_value; + break; + case Arg::ULONG_LONG: + value = precision_arg.ulong_long_value; + break; + default: + FMT_THROW(FormatError("precision is not integer")); + } + if (value > (std::numeric_limits::max)()) + FMT_THROW(FormatError("number is too big")); + spec.precision_ = static_cast(value); + } + else + { + FMT_THROW(FormatError("missing precision specifier")); + } + if (arg.type <= Arg::LAST_INTEGER_TYPE || arg.type == Arg::POINTER) + { + FMT_THROW(FormatError( + fmt::format("precision not allowed in {} format specifier", + arg.type == Arg::POINTER ? "pointer" : "integer"))); + } + } + + // Parse type. + if (*s != '}' && *s) + spec.type_ = static_cast(*s++); + } + + if (*s++ != '}') + FMT_THROW(FormatError("missing '}' in format string")); + + // Format argument. + ArgFormatter(*this, spec, s - 1).visit(arg); + return s; +} + +template +void BasicFormatter::format(BasicCStringRef format_str) +{ + const Char *s = format_str.c_str(); + const Char *start = s; + while (*s) + { + Char c = *s++; + if (c != '{' && c != '}') continue; + if (*s == c) + { + write(writer_, start, s); + start = ++s; + continue; + } + if (c == '}') + FMT_THROW(FormatError("unmatched '}' in format string")); + write(writer_, start, s - 1); + internal::Arg arg = internal::is_name_start(*s) ? + parse_arg_name(s) : parse_arg_index(s); + start = s = format(s, arg); + } + write(writer_, start, s); +} + +template +struct ArgJoin +{ + It first; + It last; + BasicCStringRef sep; + + ArgJoin(It first, It last, const BasicCStringRef& sep) : + first(first), + last(last), + sep(sep) {} +}; + +template +ArgJoin join(It first, It last, const BasicCStringRef& sep) +{ + return ArgJoin(first, last, sep); +} + +template +ArgJoin join(It first, It last, const BasicCStringRef& sep) +{ + return ArgJoin(first, last, sep); +} + +#if FMT_HAS_GXX_CXX11 +template +auto join(const Range& range, const BasicCStringRef& sep) +-> ArgJoin +{ + return join(std::begin(range), std::end(range), sep); +} + +template +auto join(const Range& range, const BasicCStringRef& sep) +-> ArgJoin +{ + return join(std::begin(range), std::end(range), sep); +} +#endif + +template +void format_arg(fmt::BasicFormatter &f, + const Char *&format_str, const ArgJoin& e) +{ + const Char* end = format_str; + if (*end == ':') + ++end; + while (*end && *end != '}') + ++end; + if (*end != '}') + FMT_THROW(FormatError("missing '}' in format string")); + + It it = e.first; + if (it != e.last) + { + const Char* save = format_str; + f.format(format_str, internal::MakeArg >(*it++)); + while (it != e.last) + { + f.writer().write(e.sep); + format_str = save; + f.format(format_str, internal::MakeArg >(*it++)); + } + } + format_str = end + 1; +} +} // namespace fmt + +#if FMT_USE_USER_DEFINED_LITERALS +namespace fmt +{ +namespace internal +{ + +template +struct UdlFormat +{ + const Char *str; + + template + auto operator()(Args && ... args) const + -> decltype(format(str, std::forward(args)...)) + { + return format(str, std::forward(args)...); + } +}; + +template +struct UdlArg +{ + const Char *str; + + template + NamedArgWithType operator=(T &&value) const + { + return {str, std::forward(value)}; + } +}; + +} // namespace internal + +inline namespace literals +{ + +/** + \rst + C++11 literal equivalent of :func:`fmt::format`. + + **Example**:: + + using namespace fmt::literals; + std::string message = "The answer is {}"_format(42); + \endrst + */ +inline internal::UdlFormat +operator"" _format(const char *s, std::size_t) +{ + return {s}; +} +inline internal::UdlFormat +operator"" _format(const wchar_t *s, std::size_t) +{ + return {s}; +} + +/** + \rst + C++11 literal equivalent of :func:`fmt::arg`. + + **Example**:: + + using namespace fmt::literals; + print("Elapsed time: {s:.2f} seconds", "s"_a=1.23); + \endrst + */ +inline internal::UdlArg +operator"" _a(const char *s, std::size_t) +{ + return {s}; +} +inline internal::UdlArg +operator"" _a(const wchar_t *s, std::size_t) +{ + return {s}; +} + +} // inline namespace literals +} // namespace fmt +#endif // FMT_USE_USER_DEFINED_LITERALS + +// Restore warnings. +#if FMT_GCC_VERSION >= 406 +# pragma GCC diagnostic pop +#endif + +#if defined(__clang__) && !defined(FMT_ICC_VERSION) +# pragma clang diagnostic pop +#endif + +#ifdef FMT_HEADER_ONLY +# define FMT_FUNC inline +# include "format.cc" +#else +# define FMT_FUNC +#endif + +#endif // FMT_FORMAT_H_ diff --git a/thirdparty/spdlog/fmt/bundled/ostream.cc b/thirdparty/spdlog/fmt/bundled/ostream.cc new file mode 100755 index 0000000..2d443f7 --- /dev/null +++ b/thirdparty/spdlog/fmt/bundled/ostream.cc @@ -0,0 +1,35 @@ +/* + Formatting library for C++ - std::ostream support + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + For the license information refer to format.h. + */ + +#include "ostream.h" + +namespace fmt { + +namespace internal { +FMT_FUNC void write(std::ostream &os, Writer &w) { + const char *data = w.data(); + typedef internal::MakeUnsigned::Type UnsignedStreamSize; + UnsignedStreamSize size = w.size(); + UnsignedStreamSize max_size = + internal::to_unsigned((std::numeric_limits::max)()); + do { + UnsignedStreamSize n = size <= max_size ? size : max_size; + os.write(data, static_cast(n)); + data += n; + size -= n; + } while (size != 0); +} +} + +FMT_FUNC void print(std::ostream &os, CStringRef format_str, ArgList args) { + MemoryWriter w; + w.write(format_str, args); + internal::write(os, w); +} +} // namespace fmt diff --git a/thirdparty/spdlog/fmt/bundled/ostream.h b/thirdparty/spdlog/fmt/bundled/ostream.h new file mode 100755 index 0000000..cfb8e03 --- /dev/null +++ b/thirdparty/spdlog/fmt/bundled/ostream.h @@ -0,0 +1,114 @@ +/* + Formatting library for C++ - std::ostream support + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + For the license information refer to format.h. + */ + +#ifndef FMT_OSTREAM_H_ +#define FMT_OSTREAM_H_ + +#include "format.h" +#include + +namespace fmt +{ + +namespace internal +{ + +template +class FormatBuf : public std::basic_streambuf +{ +private: + typedef typename std::basic_streambuf::int_type int_type; + typedef typename std::basic_streambuf::traits_type traits_type; + + Buffer &buffer_; + +public: + FormatBuf(Buffer &buffer) : buffer_(buffer) {} + +protected: + // The put-area is actually always empty. This makes the implementation + // simpler and has the advantage that the streambuf and the buffer are always + // in sync and sputc never writes into uninitialized memory. The obvious + // disadvantage is that each call to sputc always results in a (virtual) call + // to overflow. There is no disadvantage here for sputn since this always + // results in a call to xsputn. + + int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE + { + if (!traits_type::eq_int_type(ch, traits_type::eof())) + buffer_.push_back(static_cast(ch)); + return ch; + } + + std::streamsize xsputn(const Char *s, std::streamsize count) FMT_OVERRIDE + { + buffer_.append(s, s + count); + return count; + } +}; + +Yes &convert(std::ostream &); + +struct DummyStream : std::ostream +{ + DummyStream(); // Suppress a bogus warning in MSVC. + // Hide all operator<< overloads from std::ostream. + void operator<<(Null<>); +}; + +No &operator<<(std::ostream &, int); + +template +struct ConvertToIntImpl +{ + // Convert to int only if T doesn't have an overloaded operator<<. + enum + { + value = sizeof(convert(get() << get())) == sizeof(No) + }; +}; + +// Write the content of w to os. +FMT_API void write(std::ostream &os, Writer &w); +} // namespace internal + +// Formats a value. +template +void format_arg(BasicFormatter &f, + const Char *&format_str, const T &value) +{ + internal::MemoryBuffer buffer; + + internal::FormatBuf format_buf(buffer); + std::basic_ostream output(&format_buf); + output << value; + + BasicStringRef str(&buffer[0], buffer.size()); + typedef internal::MakeArg< BasicFormatter > MakeArg; + format_str = f.format(format_str, MakeArg(str)); +} + +/** + \rst + Prints formatted data to the stream *os*. + + **Example**:: + + print(cerr, "Don't {}!", "panic"); + \endrst + */ +FMT_API void print(std::ostream &os, CStringRef format_str, ArgList args); +FMT_VARIADIC(void, print, std::ostream &, CStringRef) +} // namespace fmt + +#ifdef FMT_HEADER_ONLY +# include "ostream.cc" +#endif + +#endif // FMT_OSTREAM_H_ diff --git a/thirdparty/spdlog/fmt/bundled/posix.cc b/thirdparty/spdlog/fmt/bundled/posix.cc new file mode 100755 index 0000000..356668c --- /dev/null +++ b/thirdparty/spdlog/fmt/bundled/posix.cc @@ -0,0 +1,241 @@ +/* + A C++ interface to POSIX functions. + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + For the license information refer to format.h. + */ + +// Disable bogus MSVC warnings. +#ifndef _CRT_SECURE_NO_WARNINGS +# define _CRT_SECURE_NO_WARNINGS +#endif + +#include "posix.h" + +#include +#include +#include + +#ifndef _WIN32 +# include +#else +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +# include + +# define O_CREAT _O_CREAT +# define O_TRUNC _O_TRUNC + +# ifndef S_IRUSR +# define S_IRUSR _S_IREAD +# endif + +# ifndef S_IWUSR +# define S_IWUSR _S_IWRITE +# endif + +# ifdef __MINGW32__ +# define _SH_DENYNO 0x40 +# endif + +#endif // _WIN32 + +#ifdef fileno +# undef fileno +#endif + +namespace { +#ifdef _WIN32 +// Return type of read and write functions. +typedef int RWResult; + +// On Windows the count argument to read and write is unsigned, so convert +// it from size_t preventing integer overflow. +inline unsigned convert_rwcount(std::size_t count) { + return count <= UINT_MAX ? static_cast(count) : UINT_MAX; +} +#else +// Return type of read and write functions. +typedef ssize_t RWResult; + +inline std::size_t convert_rwcount(std::size_t count) { return count; } +#endif +} + +fmt::BufferedFile::~BufferedFile() FMT_NOEXCEPT { + if (file_ && FMT_SYSTEM(fclose(file_)) != 0) + fmt::report_system_error(errno, "cannot close file"); +} + +fmt::BufferedFile::BufferedFile( + fmt::CStringRef filename, fmt::CStringRef mode) { + FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())), 0); + if (!file_) + FMT_THROW(SystemError(errno, "cannot open file {}", filename)); +} + +void fmt::BufferedFile::close() { + if (!file_) + return; + int result = FMT_SYSTEM(fclose(file_)); + file_ = FMT_NULL; + if (result != 0) + FMT_THROW(SystemError(errno, "cannot close file")); +} + +// A macro used to prevent expansion of fileno on broken versions of MinGW. +#define FMT_ARGS + +int fmt::BufferedFile::fileno() const { + int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_)); + if (fd == -1) + FMT_THROW(SystemError(errno, "cannot get file descriptor")); + return fd; +} + +fmt::File::File(fmt::CStringRef path, int oflag) { + int mode = S_IRUSR | S_IWUSR; +#if defined(_WIN32) && !defined(__MINGW32__) + fd_ = -1; + FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode)); +#else + FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode))); +#endif + if (fd_ == -1) + FMT_THROW(SystemError(errno, "cannot open file {}", path)); +} + +fmt::File::~File() FMT_NOEXCEPT { + // Don't retry close in case of EINTR! + // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html + if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0) + fmt::report_system_error(errno, "cannot close file"); +} + +void fmt::File::close() { + if (fd_ == -1) + return; + // Don't retry close in case of EINTR! + // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html + int result = FMT_POSIX_CALL(close(fd_)); + fd_ = -1; + if (result != 0) + FMT_THROW(SystemError(errno, "cannot close file")); +} + +fmt::LongLong fmt::File::size() const { +#ifdef _WIN32 + // Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT + // is less than 0x0500 as is the case with some default MinGW builds. + // Both functions support large file sizes. + DWORD size_upper = 0; + HANDLE handle = reinterpret_cast(_get_osfhandle(fd_)); + DWORD size_lower = FMT_SYSTEM(GetFileSize(handle, &size_upper)); + if (size_lower == INVALID_FILE_SIZE) { + DWORD error = GetLastError(); + if (error != NO_ERROR) + FMT_THROW(WindowsError(GetLastError(), "cannot get file size")); + } + fmt::ULongLong long_size = size_upper; + return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower; +#else + typedef struct stat Stat; + Stat file_stat = Stat(); + if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1) + FMT_THROW(SystemError(errno, "cannot get file attributes")); + FMT_STATIC_ASSERT(sizeof(fmt::LongLong) >= sizeof(file_stat.st_size), + "return type of File::size is not large enough"); + return file_stat.st_size; +#endif +} + +std::size_t fmt::File::read(void *buffer, std::size_t count) { + RWResult result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count)))); + if (result < 0) + FMT_THROW(SystemError(errno, "cannot read from file")); + return internal::to_unsigned(result); +} + +std::size_t fmt::File::write(const void *buffer, std::size_t count) { + RWResult result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count)))); + if (result < 0) + FMT_THROW(SystemError(errno, "cannot write to file")); + return internal::to_unsigned(result); +} + +fmt::File fmt::File::dup(int fd) { + // Don't retry as dup doesn't return EINTR. + // http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html + int new_fd = FMT_POSIX_CALL(dup(fd)); + if (new_fd == -1) + FMT_THROW(SystemError(errno, "cannot duplicate file descriptor {}", fd)); + return File(new_fd); +} + +void fmt::File::dup2(int fd) { + int result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); + if (result == -1) { + FMT_THROW(SystemError(errno, + "cannot duplicate file descriptor {} to {}", fd_, fd)); + } +} + +void fmt::File::dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT { + int result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); + if (result == -1) + ec = ErrorCode(errno); +} + +void fmt::File::pipe(File &read_end, File &write_end) { + // Close the descriptors first to make sure that assignments don't throw + // and there are no leaks. + read_end.close(); + write_end.close(); + int fds[2] = {}; +#ifdef _WIN32 + // Make the default pipe capacity same as on Linux 2.6.11+. + enum { DEFAULT_CAPACITY = 65536 }; + int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY)); +#else + // Don't retry as the pipe function doesn't return EINTR. + // http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html + int result = FMT_POSIX_CALL(pipe(fds)); +#endif + if (result != 0) + FMT_THROW(SystemError(errno, "cannot create pipe")); + // The following assignments don't throw because read_fd and write_fd + // are closed. + read_end = File(fds[0]); + write_end = File(fds[1]); +} + +fmt::BufferedFile fmt::File::fdopen(const char *mode) { + // Don't retry as fdopen doesn't return EINTR. + FILE *f = FMT_POSIX_CALL(fdopen(fd_, mode)); + if (!f) + FMT_THROW(SystemError(errno, "cannot associate stream with file descriptor")); + BufferedFile file(f); + fd_ = -1; + return file; +} + +long fmt::getpagesize() { +#ifdef _WIN32 + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwPageSize; +#else + long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE)); + if (size < 0) + FMT_THROW(SystemError(errno, "cannot get memory page size")); + return size; +#endif +} diff --git a/thirdparty/spdlog/fmt/bundled/posix.h b/thirdparty/spdlog/fmt/bundled/posix.h new file mode 100755 index 0000000..6932725 --- /dev/null +++ b/thirdparty/spdlog/fmt/bundled/posix.h @@ -0,0 +1,424 @@ +/* + A C++ interface to POSIX functions. + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + For the license information refer to format.h. + */ + +#ifndef FMT_POSIX_H_ +#define FMT_POSIX_H_ + +#if defined(__MINGW32__) || defined(__CYGWIN__) +// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/. +# undef __STRICT_ANSI__ +#endif + +#include +#include // for O_RDONLY +#include // for locale_t +#include +#include // for strtod_l + +#include + +#if defined __APPLE__ || defined(__FreeBSD__) +# include // for LC_NUMERIC_MASK on OS X +#endif + +#include "format.h" + +#ifndef FMT_POSIX +# if defined(_WIN32) && !defined(__MINGW32__) +// Fix warnings about deprecated symbols. +# define FMT_POSIX(call) _##call +# else +# define FMT_POSIX(call) call +# endif +#endif + +// Calls to system functions are wrapped in FMT_SYSTEM for testability. +#ifdef FMT_SYSTEM +# define FMT_POSIX_CALL(call) FMT_SYSTEM(call) +#else +# define FMT_SYSTEM(call) call +# ifdef _WIN32 +// Fix warnings about deprecated symbols. +# define FMT_POSIX_CALL(call) ::_##call +# else +# define FMT_POSIX_CALL(call) ::call +# endif +#endif + +// Retries the expression while it evaluates to error_result and errno +// equals to EINTR. +#ifndef _WIN32 +# define FMT_RETRY_VAL(result, expression, error_result) \ + do { \ + result = (expression); \ + } while (result == error_result && errno == EINTR) +#else +# define FMT_RETRY_VAL(result, expression, error_result) result = (expression) +#endif + +#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) + +namespace fmt +{ + +// An error code. +class ErrorCode +{ +private: + int value_; + +public: +explicit ErrorCode(int value = 0) FMT_NOEXCEPT : + value_(value) {} + + int get() const FMT_NOEXCEPT + { + return value_; + } +}; + +// A buffered file. +class BufferedFile +{ +private: + FILE *file_; + + friend class File; + + explicit BufferedFile(FILE *f) : file_(f) {} + +public: + // Constructs a BufferedFile object which doesn't represent any file. +BufferedFile() FMT_NOEXCEPT : + file_(FMT_NULL) {} + + // Destroys the object closing the file it represents if any. + FMT_API ~BufferedFile() FMT_NOEXCEPT; + +#if !FMT_USE_RVALUE_REFERENCES + // Emulate a move constructor and a move assignment operator if rvalue + // references are not supported. + +private: + // A proxy object to emulate a move constructor. + // It is private to make it impossible call operator Proxy directly. + struct Proxy + { + FILE *file; + }; + +public: + // A "move constructor" for moving from a temporary. +BufferedFile(Proxy p) FMT_NOEXCEPT : + file_(p.file) {} + + // A "move constructor" for moving from an lvalue. +BufferedFile(BufferedFile &f) FMT_NOEXCEPT : + file_(f.file_) + { + f.file_ = FMT_NULL; + } + + // A "move assignment operator" for moving from a temporary. + BufferedFile &operator=(Proxy p) + { + close(); + file_ = p.file; + return *this; + } + + // A "move assignment operator" for moving from an lvalue. + BufferedFile &operator=(BufferedFile &other) + { + close(); + file_ = other.file_; + other.file_ = FMT_NULL; + return *this; + } + + // Returns a proxy object for moving from a temporary: + // BufferedFile file = BufferedFile(...); + operator Proxy() FMT_NOEXCEPT + { + Proxy p = {file_}; + file_ = FMT_NULL; + return p; + } + +#else +private: + FMT_DISALLOW_COPY_AND_ASSIGN(BufferedFile); + +public: +BufferedFile(BufferedFile &&other) FMT_NOEXCEPT : + file_(other.file_) + { + other.file_ = FMT_NULL; + } + + BufferedFile& operator=(BufferedFile &&other) + { + close(); + file_ = other.file_; + other.file_ = FMT_NULL; + return *this; + } +#endif + + // Opens a file. + FMT_API BufferedFile(CStringRef filename, CStringRef mode); + + // Closes the file. + FMT_API void close(); + + // Returns the pointer to a FILE object representing this file. + FILE *get() const FMT_NOEXCEPT + { + return file_; + } + + // We place parentheses around fileno to workaround a bug in some versions + // of MinGW that define fileno as a macro. + FMT_API int (fileno)() const; + + void print(CStringRef format_str, const ArgList &args) + { + fmt::print(file_, format_str, args); + } + FMT_VARIADIC(void, print, CStringRef) +}; + +// A file. Closed file is represented by a File object with descriptor -1. +// Methods that are not declared with FMT_NOEXCEPT may throw +// fmt::SystemError in case of failure. Note that some errors such as +// closing the file multiple times will cause a crash on Windows rather +// than an exception. You can get standard behavior by overriding the +// invalid parameter handler with _set_invalid_parameter_handler. +class File +{ +private: + int fd_; // File descriptor. + + // Constructs a File object with a given descriptor. + explicit File(int fd) : fd_(fd) {} + +public: + // Possible values for the oflag argument to the constructor. + enum + { + RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. + WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. + RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing. + }; + + // Constructs a File object which doesn't represent any file. +File() FMT_NOEXCEPT : + fd_(-1) {} + + // Opens a file and constructs a File object representing this file. + FMT_API File(CStringRef path, int oflag); + +#if !FMT_USE_RVALUE_REFERENCES + // Emulate a move constructor and a move assignment operator if rvalue + // references are not supported. + +private: + // A proxy object to emulate a move constructor. + // It is private to make it impossible call operator Proxy directly. + struct Proxy + { + int fd; + }; + +public: + // A "move constructor" for moving from a temporary. +File(Proxy p) FMT_NOEXCEPT : + fd_(p.fd) {} + + // A "move constructor" for moving from an lvalue. +File(File &other) FMT_NOEXCEPT : + fd_(other.fd_) + { + other.fd_ = -1; + } + + // A "move assignment operator" for moving from a temporary. + File &operator=(Proxy p) + { + close(); + fd_ = p.fd; + return *this; + } + + // A "move assignment operator" for moving from an lvalue. + File &operator=(File &other) + { + close(); + fd_ = other.fd_; + other.fd_ = -1; + return *this; + } + + // Returns a proxy object for moving from a temporary: + // File file = File(...); + operator Proxy() FMT_NOEXCEPT + { + Proxy p = {fd_}; + fd_ = -1; + return p; + } + +#else +private: + FMT_DISALLOW_COPY_AND_ASSIGN(File); + +public: +File(File &&other) FMT_NOEXCEPT : + fd_(other.fd_) + { + other.fd_ = -1; + } + + File& operator=(File &&other) + { + close(); + fd_ = other.fd_; + other.fd_ = -1; + return *this; + } +#endif + + // Destroys the object closing the file it represents if any. + FMT_API ~File() FMT_NOEXCEPT; + + // Returns the file descriptor. + int descriptor() const FMT_NOEXCEPT + { + return fd_; + } + + // Closes the file. + FMT_API void close(); + + // Returns the file size. The size has signed type for consistency with + // stat::st_size. + FMT_API LongLong size() const; + + // Attempts to read count bytes from the file into the specified buffer. + FMT_API std::size_t read(void *buffer, std::size_t count); + + // Attempts to write count bytes from the specified buffer to the file. + FMT_API std::size_t write(const void *buffer, std::size_t count); + + // Duplicates a file descriptor with the dup function and returns + // the duplicate as a file object. + FMT_API static File dup(int fd); + + // Makes fd be the copy of this file descriptor, closing fd first if + // necessary. + FMT_API void dup2(int fd); + + // Makes fd be the copy of this file descriptor, closing fd first if + // necessary. + FMT_API void dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT; + + // Creates a pipe setting up read_end and write_end file objects for reading + // and writing respectively. + FMT_API static void pipe(File &read_end, File &write_end); + + // Creates a BufferedFile object associated with this file and detaches + // this File object from the file. + FMT_API BufferedFile fdopen(const char *mode); +}; + +// Returns the memory page size. +long getpagesize(); + +#if (defined(LC_NUMERIC_MASK) || defined(_MSC_VER)) && \ + !defined(__ANDROID__) && !defined(__CYGWIN__) +# define FMT_LOCALE +#endif + +#ifdef FMT_LOCALE +// A "C" numeric locale. +class Locale +{ +private: +# ifdef _MSC_VER + typedef _locale_t locale_t; + + enum { LC_NUMERIC_MASK = LC_NUMERIC }; + + static locale_t newlocale(int category_mask, const char *locale, locale_t) + { + return _create_locale(category_mask, locale); + } + + static void freelocale(locale_t locale) + { + _free_locale(locale); + } + + static double strtod_l(const char *nptr, char **endptr, _locale_t locale) + { + return _strtod_l(nptr, endptr, locale); + } +# endif + + locale_t locale_; + + FMT_DISALLOW_COPY_AND_ASSIGN(Locale); + +public: + typedef locale_t Type; + + Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", FMT_NULL)) + { + if (!locale_) + FMT_THROW(fmt::SystemError(errno, "cannot create locale")); + } + ~Locale() + { + freelocale(locale_); + } + + Type get() const + { + return locale_; + } + + // Converts string to floating-point number and advances str past the end + // of the parsed input. + double strtod(const char *&str) const + { + char *end = FMT_NULL; + double result = strtod_l(str, &end, locale_); + str = end; + return result; + } +}; +#endif // FMT_LOCALE +} // namespace fmt + +#if !FMT_USE_RVALUE_REFERENCES +namespace std +{ +// For compatibility with C++98. +inline fmt::BufferedFile &move(fmt::BufferedFile &f) +{ + return f; +} +inline fmt::File &move(fmt::File &f) +{ + return f; +} +} +#endif + +#endif // FMT_POSIX_H_ diff --git a/thirdparty/spdlog/fmt/bundled/printf.cc b/thirdparty/spdlog/fmt/bundled/printf.cc new file mode 100755 index 0000000..95d7a36 --- /dev/null +++ b/thirdparty/spdlog/fmt/bundled/printf.cc @@ -0,0 +1,32 @@ +/* + Formatting library for C++ + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + For the license information refer to format.h. + */ + +#include "format.h" +#include "printf.h" + +namespace fmt { + +template +void printf(BasicWriter &w, BasicCStringRef format, ArgList args); + +FMT_FUNC int fprintf(std::FILE *f, CStringRef format, ArgList args) { + MemoryWriter w; + printf(w, format, args); + std::size_t size = w.size(); + return std::fwrite(w.data(), 1, size, f) < size ? -1 : static_cast(size); +} + +#ifndef FMT_HEADER_ONLY + +template void PrintfFormatter::format(CStringRef format); +template void PrintfFormatter::format(WCStringRef format); + +#endif // FMT_HEADER_ONLY + +} // namespace fmt diff --git a/thirdparty/spdlog/fmt/bundled/printf.h b/thirdparty/spdlog/fmt/bundled/printf.h new file mode 100755 index 0000000..7861b46 --- /dev/null +++ b/thirdparty/spdlog/fmt/bundled/printf.h @@ -0,0 +1,712 @@ +/* + Formatting library for C++ + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + For the license information refer to format.h. + */ + +#ifndef FMT_PRINTF_H_ +#define FMT_PRINTF_H_ + +#include // std::fill_n +#include // std::numeric_limits + +#include "ostream.h" + +namespace fmt +{ +namespace internal +{ + +// Checks if a value fits in int - used to avoid warnings about comparing +// signed and unsigned integers. +template +struct IntChecker +{ + template + static bool fits_in_int(T value) + { + unsigned max = std::numeric_limits::max(); + return value <= max; + } + static bool fits_in_int(bool) + { + return true; + } +}; + +template <> +struct IntChecker +{ + template + static bool fits_in_int(T value) + { + return value >= std::numeric_limits::min() && + value <= std::numeric_limits::max(); + } + static bool fits_in_int(int) + { + return true; + } +}; + +class PrecisionHandler : public ArgVisitor +{ +public: + void report_unhandled_arg() + { + FMT_THROW(FormatError("precision is not integer")); + } + + template + int visit_any_int(T value) + { + if (!IntChecker::is_signed>::fits_in_int(value)) + FMT_THROW(FormatError("number is too big")); + return static_cast(value); + } +}; + +// IsZeroInt::visit(arg) returns true iff arg is a zero integer. +class IsZeroInt : public ArgVisitor +{ +public: + template + bool visit_any_int(T value) + { + return value == 0; + } +}; + +// returns the default type for format specific "%s" +class DefaultType : public ArgVisitor +{ +public: + char visit_char(int) + { + return 'c'; + } + + char visit_bool(bool) + { + return 's'; + } + + char visit_pointer(const void *) + { + return 'p'; + } + + template + char visit_any_int(T) + { + return 'd'; + } + + template + char visit_any_double(T) + { + return 'g'; + } + + char visit_unhandled_arg() + { + return 's'; + } +}; + +template +struct is_same +{ + enum { value = 0 }; +}; + +template +struct is_same +{ + enum { value = 1 }; +}; + +// An argument visitor that converts an integer argument to T for printf, +// if T is an integral type. If T is void, the argument is converted to +// corresponding signed or unsigned type depending on the type specifier: +// 'd' and 'i' - signed, other - unsigned) +template +class ArgConverter : public ArgVisitor, void> +{ +private: + internal::Arg &arg_; + wchar_t type_; + + FMT_DISALLOW_COPY_AND_ASSIGN(ArgConverter); + +public: + ArgConverter(internal::Arg &arg, wchar_t type) + : arg_(arg), type_(type) {} + + void visit_bool(bool value) + { + if (type_ != 's') + visit_any_int(value); + } + + void visit_char(char value) + { + if (type_ != 's') + visit_any_int(value); + } + + template + void visit_any_int(U value) + { + bool is_signed = type_ == 'd' || type_ == 'i'; + if (type_ == 's') + { + is_signed = std::numeric_limits::is_signed; + } + + using internal::Arg; + typedef typename internal::Conditional< + is_same::value, U, T>::type TargetType; + if (sizeof(TargetType) <= sizeof(int)) + { + // Extra casts are used to silence warnings. + if (is_signed) + { + arg_.type = Arg::INT; + arg_.int_value = static_cast(static_cast(value)); + } + else + { + arg_.type = Arg::UINT; + typedef typename internal::MakeUnsigned::Type Unsigned; + arg_.uint_value = static_cast(static_cast(value)); + } + } + else + { + if (is_signed) + { + arg_.type = Arg::LONG_LONG; + // glibc's printf doesn't sign extend arguments of smaller types: + // std::printf("%lld", -42); // prints "4294967254" + // but we don't have to do the same because it's a UB. + arg_.long_long_value = static_cast(value); + } + else + { + arg_.type = Arg::ULONG_LONG; + arg_.ulong_long_value = + static_cast::Type>(value); + } + } + } +}; + +// Converts an integer argument to char for printf. +class CharConverter : public ArgVisitor +{ +private: + internal::Arg &arg_; + + FMT_DISALLOW_COPY_AND_ASSIGN(CharConverter); + +public: + explicit CharConverter(internal::Arg &arg) : arg_(arg) {} + + template + void visit_any_int(T value) + { + arg_.type = internal::Arg::CHAR; + arg_.int_value = static_cast(value); + } +}; + +// Checks if an argument is a valid printf width specifier and sets +// left alignment if it is negative. +class WidthHandler : public ArgVisitor +{ +private: + FormatSpec &spec_; + + FMT_DISALLOW_COPY_AND_ASSIGN(WidthHandler); + +public: + explicit WidthHandler(FormatSpec &spec) : spec_(spec) {} + + void report_unhandled_arg() + { + FMT_THROW(FormatError("width is not integer")); + } + + template + unsigned visit_any_int(T value) + { + typedef typename internal::IntTraits::MainType UnsignedType; + UnsignedType width = static_cast(value); + if (internal::is_negative(value)) + { + spec_.align_ = ALIGN_LEFT; + width = 0 - width; + } + unsigned int_max = std::numeric_limits::max(); + if (width > int_max) + FMT_THROW(FormatError("number is too big")); + return static_cast(width); + } +}; +} // namespace internal + +/** + \rst + A ``printf`` argument formatter based on the `curiously recurring template + pattern `_. + + To use `~fmt::BasicPrintfArgFormatter` define a subclass that implements some + or all of the visit methods with the same signatures as the methods in + `~fmt::ArgVisitor`, for example, `~fmt::ArgVisitor::visit_int()`. + Pass the subclass as the *Impl* template parameter. When a formatting + function processes an argument, it will dispatch to a visit method + specific to the argument type. For example, if the argument type is + ``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass + will be called. If the subclass doesn't contain a method with this signature, + then a corresponding method of `~fmt::BasicPrintfArgFormatter` or its + superclass will be called. + \endrst + */ +template +class BasicPrintfArgFormatter : + public internal::ArgFormatterBase +{ +private: + void write_null_pointer() + { + this->spec().type_ = 0; + this->write("(nil)"); + } + + typedef internal::ArgFormatterBase Base; + +public: + /** + \rst + Constructs an argument formatter object. + *writer* is a reference to the output writer and *spec* contains format + specifier information for standard argument types. + \endrst + */ + BasicPrintfArgFormatter(BasicWriter &w, Spec &s) + : internal::ArgFormatterBase(w, s) {} + + /** Formats an argument of type ``bool``. */ + void visit_bool(bool value) + { + Spec &fmt_spec = this->spec(); + if (fmt_spec.type_ != 's') + return this->visit_any_int(value); + fmt_spec.type_ = 0; + this->write(value); + } + + /** Formats a character. */ + void visit_char(int value) + { + const Spec &fmt_spec = this->spec(); + BasicWriter &w = this->writer(); + if (fmt_spec.type_ && fmt_spec.type_ != 'c') + w.write_int(value, fmt_spec); + typedef typename BasicWriter::CharPtr CharPtr; + CharPtr out = CharPtr(); + if (fmt_spec.width_ > 1) + { + Char fill = ' '; + out = w.grow_buffer(fmt_spec.width_); + if (fmt_spec.align_ != ALIGN_LEFT) + { + std::fill_n(out, fmt_spec.width_ - 1, fill); + out += fmt_spec.width_ - 1; + } + else + { + std::fill_n(out + 1, fmt_spec.width_ - 1, fill); + } + } + else + { + out = w.grow_buffer(1); + } + *out = static_cast(value); + } + + /** Formats a null-terminated C string. */ + void visit_cstring(const char *value) + { + if (value) + Base::visit_cstring(value); + else if (this->spec().type_ == 'p') + write_null_pointer(); + else + this->write("(null)"); + } + + /** Formats a pointer. */ + void visit_pointer(const void *value) + { + if (value) + return Base::visit_pointer(value); + this->spec().type_ = 0; + write_null_pointer(); + } + + /** Formats an argument of a custom (user-defined) type. */ + void visit_custom(internal::Arg::CustomValue c) + { + BasicFormatter formatter(ArgList(), this->writer()); + const Char format_str[] = {'}', 0}; + const Char *format = format_str; + c.format(&formatter, c.value, &format); + } +}; + +/** The default printf argument formatter. */ +template +class PrintfArgFormatter : + public BasicPrintfArgFormatter, Char, FormatSpec> +{ +public: + /** Constructs an argument formatter object. */ + PrintfArgFormatter(BasicWriter &w, FormatSpec &s) + : BasicPrintfArgFormatter, Char, FormatSpec>(w, s) {} +}; + +/** This template formats data and writes the output to a writer. */ +template > +class PrintfFormatter : private internal::FormatterBase +{ +private: + BasicWriter &writer_; + + void parse_flags(FormatSpec &spec, const Char *&s); + + // Returns the argument with specified index or, if arg_index is equal + // to the maximum unsigned value, the next argument. + internal::Arg get_arg( + const Char *s, + unsigned arg_index = (std::numeric_limits::max)()); + + // Parses argument index, flags and width and returns the argument index. + unsigned parse_header(const Char *&s, FormatSpec &spec); + +public: + /** + \rst + Constructs a ``PrintfFormatter`` object. References to the arguments and + the writer are stored in the formatter object so make sure they have + appropriate lifetimes. + \endrst + */ + explicit PrintfFormatter(const ArgList &al, BasicWriter &w) + : FormatterBase(al), writer_(w) {} + + /** Formats stored arguments and writes the output to the writer. */ + void format(BasicCStringRef format_str); +}; + +template +void PrintfFormatter::parse_flags(FormatSpec &spec, const Char *&s) +{ + for (;;) + { + switch (*s++) + { + case '-': + spec.align_ = ALIGN_LEFT; + break; + case '+': + spec.flags_ |= SIGN_FLAG | PLUS_FLAG; + break; + case '0': + spec.fill_ = '0'; + break; + case ' ': + spec.flags_ |= SIGN_FLAG; + break; + case '#': + spec.flags_ |= HASH_FLAG; + break; + default: + --s; + return; + } + } +} + +template +internal::Arg PrintfFormatter::get_arg(const Char *s, + unsigned arg_index) +{ + (void)s; + const char *error = FMT_NULL; + internal::Arg arg = arg_index == std::numeric_limits::max() ? + next_arg(error) : FormatterBase::get_arg(arg_index - 1, error); + if (error) + FMT_THROW(FormatError(!*s ? "invalid format string" : error)); + return arg; +} + +template +unsigned PrintfFormatter::parse_header( + const Char *&s, FormatSpec &spec) +{ + unsigned arg_index = std::numeric_limits::max(); + Char c = *s; + if (c >= '0' && c <= '9') + { + // Parse an argument index (if followed by '$') or a width possibly + // preceded with '0' flag(s). + unsigned value = internal::parse_nonnegative_int(s); + if (*s == '$') // value is an argument index + { + ++s; + arg_index = value; + } + else + { + if (c == '0') + spec.fill_ = '0'; + if (value != 0) + { + // Nonzero value means that we parsed width and don't need to + // parse it or flags again, so return now. + spec.width_ = value; + return arg_index; + } + } + } + parse_flags(spec, s); + // Parse width. + if (*s >= '0' && *s <= '9') + { + spec.width_ = internal::parse_nonnegative_int(s); + } + else if (*s == '*') + { + ++s; + spec.width_ = internal::WidthHandler(spec).visit(get_arg(s)); + } + return arg_index; +} + +template +void PrintfFormatter::format(BasicCStringRef format_str) +{ + const Char *start = format_str.c_str(); + const Char *s = start; + while (*s) + { + Char c = *s++; + if (c != '%') continue; + if (*s == c) + { + write(writer_, start, s); + start = ++s; + continue; + } + write(writer_, start, s - 1); + + FormatSpec spec; + spec.align_ = ALIGN_RIGHT; + + // Parse argument index, flags and width. + unsigned arg_index = parse_header(s, spec); + + // Parse precision. + if (*s == '.') + { + ++s; + if ('0' <= *s && *s <= '9') + { + spec.precision_ = static_cast(internal::parse_nonnegative_int(s)); + } + else if (*s == '*') + { + ++s; + spec.precision_ = internal::PrecisionHandler().visit(get_arg(s)); + } + else + { + spec.precision_ = 0; + } + } + + using internal::Arg; + Arg arg = get_arg(s, arg_index); + if (spec.flag(HASH_FLAG) && internal::IsZeroInt().visit(arg)) + spec.flags_ &= ~internal::to_unsigned(HASH_FLAG); + if (spec.fill_ == '0') + { + if (arg.type <= Arg::LAST_NUMERIC_TYPE) + spec.align_ = ALIGN_NUMERIC; + else + spec.fill_ = ' '; // Ignore '0' flag for non-numeric types. + } + + // Parse length and convert the argument to the required type. + using internal::ArgConverter; + switch (*s++) + { + case 'h': + if (*s == 'h') + ArgConverter(arg, *++s).visit(arg); + else + ArgConverter(arg, *s).visit(arg); + break; + case 'l': + if (*s == 'l') + ArgConverter(arg, *++s).visit(arg); + else + ArgConverter(arg, *s).visit(arg); + break; + case 'j': + ArgConverter(arg, *s).visit(arg); + break; + case 'z': + ArgConverter(arg, *s).visit(arg); + break; + case 't': + ArgConverter(arg, *s).visit(arg); + break; + case 'L': + // printf produces garbage when 'L' is omitted for long double, no + // need to do the same. + break; + default: + --s; + ArgConverter(arg, *s).visit(arg); + } + + // Parse type. + if (!*s) + FMT_THROW(FormatError("invalid format string")); + spec.type_ = static_cast(*s++); + + if (spec.type_ == 's') + { + // set the format type to the default if 's' is specified + spec.type_ = internal::DefaultType().visit(arg); + } + + if (arg.type <= Arg::LAST_INTEGER_TYPE) + { + // Normalize type. + switch (spec.type_) + { + case 'i': + case 'u': + spec.type_ = 'd'; + break; + case 'c': + // TODO: handle wchar_t + internal::CharConverter(arg).visit(arg); + break; + } + } + + start = s; + + // Format argument. + AF(writer_, spec).visit(arg); + } + write(writer_, start, s); +} + +inline void printf(Writer &w, CStringRef format, ArgList args) +{ + PrintfFormatter(args, w).format(format); +} +FMT_VARIADIC(void, printf, Writer &, CStringRef) + +inline void printf(WWriter &w, WCStringRef format, ArgList args) +{ + PrintfFormatter(args, w).format(format); +} +FMT_VARIADIC(void, printf, WWriter &, WCStringRef) + +/** + \rst + Formats arguments and returns the result as a string. + + **Example**:: + + std::string message = fmt::sprintf("The answer is %d", 42); + \endrst +*/ +inline std::string sprintf(CStringRef format, ArgList args) +{ + MemoryWriter w; + printf(w, format, args); + return w.str(); +} +FMT_VARIADIC(std::string, sprintf, CStringRef) + +inline std::wstring sprintf(WCStringRef format, ArgList args) +{ + WMemoryWriter w; + printf(w, format, args); + return w.str(); +} +FMT_VARIADIC_W(std::wstring, sprintf, WCStringRef) + +/** + \rst + Prints formatted data to the file *f*. + + **Example**:: + + fmt::fprintf(stderr, "Don't %s!", "panic"); + \endrst + */ +FMT_API int fprintf(std::FILE *f, CStringRef format, ArgList args); +FMT_VARIADIC(int, fprintf, std::FILE *, CStringRef) + +/** + \rst + Prints formatted data to ``stdout``. + + **Example**:: + + fmt::printf("Elapsed time: %.2f seconds", 1.23); + \endrst + */ +inline int printf(CStringRef format, ArgList args) +{ + return fprintf(stdout, format, args); +} +FMT_VARIADIC(int, printf, CStringRef) + +/** + \rst + Prints formatted data to the stream *os*. + + **Example**:: + + fprintf(cerr, "Don't %s!", "panic"); + \endrst + */ +inline int fprintf(std::ostream &os, CStringRef format_str, ArgList args) +{ + MemoryWriter w; + printf(w, format_str, args); + internal::write(os, w); + return static_cast(w.size()); +} +FMT_VARIADIC(int, fprintf, std::ostream &, CStringRef) +} // namespace fmt + +#ifdef FMT_HEADER_ONLY +# include "printf.cc" +#endif + +#endif // FMT_PRINTF_H_ diff --git a/thirdparty/spdlog/fmt/bundled/time.h b/thirdparty/spdlog/fmt/bundled/time.h new file mode 100755 index 0000000..206d092 --- /dev/null +++ b/thirdparty/spdlog/fmt/bundled/time.h @@ -0,0 +1,183 @@ +/* + Formatting library for C++ - time formatting + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + For the license information refer to format.h. + */ + +#ifndef FMT_TIME_H_ +#define FMT_TIME_H_ + +#include "format.h" +#include + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4702) // unreachable code +# pragma warning(disable: 4996) // "deprecated" functions +#endif + +namespace fmt +{ +template +void format_arg(BasicFormatter &f, + const char *&format_str, const std::tm &tm) +{ + if (*format_str == ':') + ++format_str; + const char *end = format_str; + while (*end && *end != '}') + ++end; + if (*end != '}') + FMT_THROW(FormatError("missing '}' in format string")); + internal::MemoryBuffer format; + format.append(format_str, end + 1); + format[format.size() - 1] = '\0'; + Buffer &buffer = f.writer().buffer(); + std::size_t start = buffer.size(); + for (;;) + { + std::size_t size = buffer.capacity() - start; + std::size_t count = std::strftime(&buffer[start], size, &format[0], &tm); + if (count != 0) + { + buffer.resize(start + count); + break; + } + if (size >= format.size() * 256) + { + // If the buffer is 256 times larger than the format string, assume + // that `strftime` gives an empty result. There doesn't seem to be a + // better way to distinguish the two cases: + // https://github.com/fmtlib/fmt/issues/367 + break; + } + const std::size_t MIN_GROWTH = 10; + buffer.reserve(buffer.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); + } + format_str = end + 1; +} + +namespace internal +{ +inline Null<> localtime_r(...) +{ + return Null<>(); +} +inline Null<> localtime_s(...) +{ + return Null<>(); +} +inline Null<> gmtime_r(...) +{ + return Null<>(); +} +inline Null<> gmtime_s(...) +{ + return Null<>(); +} +} + +// Thread-safe replacement for std::localtime +inline std::tm localtime(std::time_t time) +{ + struct LocalTime + { + std::time_t time_; + std::tm tm_; + + LocalTime(std::time_t t): time_(t) {} + + bool run() + { + using namespace fmt::internal; + return handle(localtime_r(&time_, &tm_)); + } + + bool handle(std::tm *tm) + { + return tm != FMT_NULL; + } + + bool handle(internal::Null<>) + { + using namespace fmt::internal; + return fallback(localtime_s(&tm_, &time_)); + } + + bool fallback(int res) + { + return res == 0; + } + + bool fallback(internal::Null<>) + { + using namespace fmt::internal; + std::tm *tm = std::localtime(&time_); + if (tm) tm_ = *tm; + return tm != FMT_NULL; + } + }; + LocalTime lt(time); + if (lt.run()) + return lt.tm_; + // Too big time values may be unsupported. + FMT_THROW(fmt::FormatError("time_t value out of range")); + return std::tm(); +} + +// Thread-safe replacement for std::gmtime +inline std::tm gmtime(std::time_t time) +{ + struct GMTime + { + std::time_t time_; + std::tm tm_; + + GMTime(std::time_t t): time_(t) {} + + bool run() + { + using namespace fmt::internal; + return handle(gmtime_r(&time_, &tm_)); + } + + bool handle(std::tm *tm) + { + return tm != FMT_NULL; + } + + bool handle(internal::Null<>) + { + using namespace fmt::internal; + return fallback(gmtime_s(&tm_, &time_)); + } + + bool fallback(int res) + { + return res == 0; + } + + bool fallback(internal::Null<>) + { + std::tm *tm = std::gmtime(&time_); + if (tm != FMT_NULL) tm_ = *tm; + return tm != FMT_NULL; + } + }; + GMTime gt(time); + if (gt.run()) + return gt.tm_; + // Too big time values may be unsupported. + FMT_THROW(fmt::FormatError("time_t value out of range")); + return std::tm(); +} +} //namespace fmt + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#endif // FMT_TIME_H_ diff --git a/thirdparty/spdlog/fmt/fmt.h b/thirdparty/spdlog/fmt/fmt.h new file mode 100755 index 0000000..92ca4e5 --- /dev/null +++ b/thirdparty/spdlog/fmt/fmt.h @@ -0,0 +1,34 @@ +// +// Copyright(c) 2016 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// +// Include a bundled header-only copy of fmtlib or an external one. +// By default spdlog include its own copy. +// + +#if !defined(SPDLOG_FMT_EXTERNAL) + +#ifndef FMT_HEADER_ONLY +#define FMT_HEADER_ONLY +#endif +#ifndef FMT_USE_WINDOWS_H +#define FMT_USE_WINDOWS_H 0 +#endif +#include "bundled/format.h" +#if defined(SPDLOG_FMT_PRINTF) +#include "bundled/printf.h" +#endif + +#else //external fmtlib + +#include +#if defined(SPDLOG_FMT_PRINTF) +#include +#endif + +#endif + diff --git a/thirdparty/spdlog/fmt/ostr.h b/thirdparty/spdlog/fmt/ostr.h new file mode 100755 index 0000000..5cdd5cd --- /dev/null +++ b/thirdparty/spdlog/fmt/ostr.h @@ -0,0 +1,17 @@ +// +// Copyright(c) 2016 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// include external or bundled copy of fmtlib's ostream support +// +#if !defined(SPDLOG_FMT_EXTERNAL) +#include "fmt.h" +#include "bundled/ostream.h" +#else +#include +#endif + + diff --git a/thirdparty/spdlog/formatter.h b/thirdparty/spdlog/formatter.h new file mode 100755 index 0000000..8bf0f43 --- /dev/null +++ b/thirdparty/spdlog/formatter.h @@ -0,0 +1,47 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "details/log_msg.h" + +#include +#include +#include + +namespace spdlog +{ +namespace details +{ +class flag_formatter; +} + +class formatter +{ +public: + virtual ~formatter() {} + virtual void format(details::log_msg& msg) = 0; +}; + +class pattern_formatter SPDLOG_FINAL : public formatter +{ + +public: + explicit pattern_formatter(const std::string& pattern, pattern_time_type pattern_time = pattern_time_type::local); + pattern_formatter(const pattern_formatter&) = delete; + pattern_formatter& operator=(const pattern_formatter&) = delete; + void format(details::log_msg& msg) override; +private: + const std::string _pattern; + const pattern_time_type _pattern_time; + std::vector> _formatters; + std::tm get_time(details::log_msg& msg); + void handle_flag(char flag); + void compile_pattern(const std::string& pattern); +}; +} + +#include "details/pattern_formatter_impl.h" + diff --git a/thirdparty/spdlog/logger.h b/thirdparty/spdlog/logger.h new file mode 100755 index 0000000..742f667 --- /dev/null +++ b/thirdparty/spdlog/logger.h @@ -0,0 +1,110 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// Thread safe logger (except for set_pattern(..), set_formatter(..) and set_error_handler()) +// Has name, log level, vector of std::shared sink pointers and formatter +// Upon each log write the logger: +// 1. Checks if its log level is enough to log the message +// 2. Format the message using the formatter function +// 3. Pass the formatted message to its sinks to performa the actual logging + +#include "sinks/base_sink.h" +#include "common.h" + +#include +#include +#include + +namespace spdlog +{ + +class logger +{ +public: + logger(const std::string& logger_name, sink_ptr single_sink); + logger(const std::string& name, sinks_init_list); + template + logger(const std::string& name, const It& begin, const It& end); + + virtual ~logger(); + logger(const logger&) = delete; + logger& operator=(const logger&) = delete; + + + template void log(level::level_enum lvl, const char* fmt, const Args&... args); + template void log(level::level_enum lvl, const char* msg); + template void trace(const char* fmt, const Arg1&, const Args&... args); + template void debug(const char* fmt, const Arg1&, const Args&... args); + template void info(const char* fmt, const Arg1&, const Args&... args); + template void warn(const char* fmt, const Arg1&, const Args&... args); + template void error(const char* fmt, const Arg1&, const Args&... args); + template void critical(const char* fmt, const Arg1&, const Args&... args); + + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT + template void log(level::level_enum lvl, const wchar_t* msg); + template void log(level::level_enum lvl, const wchar_t* fmt, const Args&... args); + template void trace(const wchar_t* fmt, const Args&... args); + template void debug(const wchar_t* fmt, const Args&... args); + template void info(const wchar_t* fmt, const Args&... args); + template void warn(const wchar_t* fmt, const Args&... args); + template void error(const wchar_t* fmt, const Args&... args); + template void critical(const wchar_t* fmt, const Args&... args); +#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT + + template void log(level::level_enum lvl, const T&); + template void trace(const T&); + template void debug(const T&); + template void info(const T&); + template void warn(const T&); + template void error(const T&); + template void critical(const T&); + + bool should_log(level::level_enum) const; + void set_level(level::level_enum); + level::level_enum level() const; + const std::string& name() const; + void set_pattern(const std::string&, pattern_time_type = pattern_time_type::local); + void set_formatter(formatter_ptr); + + // automatically call flush() if message level >= log_level + void flush_on(level::level_enum log_level); + + virtual void flush(); + + const std::vector& sinks() const; + + // error handler + virtual void set_error_handler(log_err_handler); + virtual log_err_handler error_handler(); + +protected: + virtual void _sink_it(details::log_msg&); + virtual void _set_pattern(const std::string&, pattern_time_type); + virtual void _set_formatter(formatter_ptr); + + // default error handler: print the error to stderr with the max rate of 1 message/minute + virtual void _default_err_handler(const std::string &msg); + + // return true if the given message level should trigger a flush + bool _should_flush_on(const details::log_msg&); + + // increment the message count (only if defined(SPDLOG_ENABLE_MESSAGE_COUNTER)) + void _incr_msg_counter(details::log_msg &msg); + + const std::string _name; + std::vector _sinks; + formatter_ptr _formatter; + spdlog::level_t _level; + spdlog::level_t _flush_level; + log_err_handler _err_handler; + std::atomic _last_err_time; + std::atomic _msg_counter; +}; +} + +#include "details/logger_impl.h" diff --git a/thirdparty/spdlog/sinks/android_sink.h b/thirdparty/spdlog/sinks/android_sink.h new file mode 100755 index 0000000..63f6d87 --- /dev/null +++ b/thirdparty/spdlog/sinks/android_sink.h @@ -0,0 +1,90 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#if defined(__ANDROID__) + +#include "sink.h" + +#include +#include +#include +#include +#include + +#if !defined(SPDLOG_ANDROID_RETRIES) +#define SPDLOG_ANDROID_RETRIES 2 +#endif + +namespace spdlog +{ +namespace sinks +{ + +/* +* Android sink (logging using __android_log_write) +* __android_log_write is thread-safe. No lock is needed. +*/ +class android_sink : public sink +{ +public: + explicit android_sink(const std::string& tag = "spdlog", bool use_raw_msg = false): _tag(tag), _use_raw_msg(use_raw_msg) {} + + void log(const details::log_msg& msg) override + { + const android_LogPriority priority = convert_to_android(msg.level); + const char *msg_output = (_use_raw_msg ? msg.raw.c_str() : msg.formatted.c_str()); + + // See system/core/liblog/logger_write.c for explanation of return value + int ret = __android_log_write(priority, _tag.c_str(), msg_output); + int retry_count = 0; + while ((ret == -11/*EAGAIN*/) && (retry_count < SPDLOG_ANDROID_RETRIES)) + { + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + ret = __android_log_write(priority, _tag.c_str(), msg_output); + retry_count++; + } + + if (ret < 0) + { + throw spdlog_ex("__android_log_write() failed", ret); + } + } + + void flush() override + { + } + +private: + static android_LogPriority convert_to_android(spdlog::level::level_enum level) + { + switch(level) + { + case spdlog::level::trace: + return ANDROID_LOG_VERBOSE; + case spdlog::level::debug: + return ANDROID_LOG_DEBUG; + case spdlog::level::info: + return ANDROID_LOG_INFO; + case spdlog::level::warn: + return ANDROID_LOG_WARN; + case spdlog::level::err: + return ANDROID_LOG_ERROR; + case spdlog::level::critical: + return ANDROID_LOG_FATAL; + default: + return ANDROID_LOG_DEFAULT; + } + } + + std::string _tag; + bool _use_raw_msg; +}; + +} +} + +#endif diff --git a/thirdparty/spdlog/sinks/ansicolor_sink.h b/thirdparty/spdlog/sinks/ansicolor_sink.h new file mode 100755 index 0000000..2909731 --- /dev/null +++ b/thirdparty/spdlog/sinks/ansicolor_sink.h @@ -0,0 +1,133 @@ +// +// Copyright(c) 2017 spdlog authors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "base_sink.h" +#include "../common.h" +#include "../details/os.h" + +#include +#include + +namespace spdlog +{ +namespace sinks +{ + +/** + * This sink prefixes the output with an ANSI escape sequence color code depending on the severity + * of the message. + * If no color terminal detected, omit the escape codes. + */ +template +class ansicolor_sink: public base_sink +{ +public: + ansicolor_sink(FILE* file): target_file_(file) + { + should_do_colors_ = details::os::in_terminal(file) && details::os::is_color_terminal(); + colors_[level::trace] = cyan; + colors_[level::debug] = cyan; + colors_[level::info] = reset; + colors_[level::warn] = yellow + bold; + colors_[level::err] = red + bold; + colors_[level::critical] = bold + on_red; + colors_[level::off] = reset; + } + virtual ~ansicolor_sink() + { + _flush(); + } + + void set_color(level::level_enum color_level, const std::string& color) + { + std::lock_guard lock(base_sink::_mutex); + colors_[color_level] = color; + } + + /// Formatting codes + const std::string reset = "\033[00m"; + const std::string bold = "\033[1m"; + const std::string dark = "\033[2m"; + const std::string underline = "\033[4m"; + const std::string blink = "\033[5m"; + const std::string reverse = "\033[7m"; + const std::string concealed = "\033[8m"; + + // Foreground colors + const std::string grey = "\033[30m"; + const std::string red = "\033[31m"; + const std::string green = "\033[32m"; + const std::string yellow = "\033[33m"; + const std::string blue = "\033[34m"; + const std::string magenta = "\033[35m"; + const std::string cyan = "\033[36m"; + const std::string white = "\033[37m"; + + /// Background colors + const std::string on_grey = "\033[40m"; + const std::string on_red = "\033[41m"; + const std::string on_green = "\033[42m"; + const std::string on_yellow = "\033[43m"; + const std::string on_blue = "\033[44m"; + const std::string on_magenta = "\033[45m"; + const std::string on_cyan = "\033[46m"; + const std::string on_white = "\033[47m"; + +protected: + virtual void _sink_it(const details::log_msg& msg) override + { + // Wrap the originally formatted message in color codes. + // If color is not supported in the terminal, log as is instead. + if (should_do_colors_) + { + const std::string& prefix = colors_[msg.level]; + fwrite(prefix.data(), sizeof(char), prefix.size(), target_file_); + fwrite(msg.formatted.data(), sizeof(char), msg.formatted.size(), target_file_); + fwrite(reset.data(), sizeof(char), reset.size(), target_file_); + } + else + { + fwrite(msg.formatted.data(), sizeof(char), msg.formatted.size(), target_file_); + } + _flush(); + } + + void _flush() override + { + fflush(target_file_); + } + FILE* target_file_; + bool should_do_colors_; + std::map colors_; +}; + + +template +class ansicolor_stdout_sink: public ansicolor_sink +{ +public: + ansicolor_stdout_sink(): ansicolor_sink(stdout) + {} +}; + +template +class ansicolor_stderr_sink: public ansicolor_sink +{ +public: + ansicolor_stderr_sink(): ansicolor_sink(stderr) + {} +}; + +typedef ansicolor_stdout_sink ansicolor_stdout_sink_mt; +typedef ansicolor_stdout_sink ansicolor_stdout_sink_st; + +typedef ansicolor_stderr_sink ansicolor_stderr_sink_mt; +typedef ansicolor_stderr_sink ansicolor_stderr_sink_st; + +} // namespace sinks +} // namespace spdlog + diff --git a/thirdparty/spdlog/sinks/base_sink.h b/thirdparty/spdlog/sinks/base_sink.h new file mode 100755 index 0000000..23c8565 --- /dev/null +++ b/thirdparty/spdlog/sinks/base_sink.h @@ -0,0 +1,51 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once +// +// base sink templated over a mutex (either dummy or real) +// concrete implementation should only override the _sink_it method. +// all locking is taken care of here so no locking needed by the implementers.. +// + +#include "sink.h" +#include "../formatter.h" +#include "../common.h" +#include "../details/log_msg.h" + +#include + +namespace spdlog +{ +namespace sinks +{ +template +class base_sink:public sink +{ +public: + base_sink():_mutex() {} + virtual ~base_sink() = default; + + base_sink(const base_sink&) = delete; + base_sink& operator=(const base_sink&) = delete; + + void log(const details::log_msg& msg) SPDLOG_FINAL override + { + std::lock_guard lock(_mutex); + _sink_it(msg); + } + void flush() SPDLOG_FINAL override + { + std::lock_guard lock(_mutex); + _flush(); + } + +protected: + virtual void _sink_it(const details::log_msg& msg) = 0; + virtual void _flush() = 0; + Mutex _mutex; +}; +} +} diff --git a/thirdparty/spdlog/sinks/dist_sink.h b/thirdparty/spdlog/sinks/dist_sink.h new file mode 100755 index 0000000..537efe1 --- /dev/null +++ b/thirdparty/spdlog/sinks/dist_sink.h @@ -0,0 +1,72 @@ +// +// Copyright (c) 2015 David Schury, Gabi Melman +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "../details/log_msg.h" +#include "../details/null_mutex.h" +#include "base_sink.h" +#include "sink.h" + +#include +#include +#include +#include + +// Distribution sink (mux). Stores a vector of sinks which get called when log is called + +namespace spdlog +{ +namespace sinks +{ +template +class dist_sink: public base_sink +{ +public: + explicit dist_sink() :_sinks() {} + dist_sink(const dist_sink&) = delete; + dist_sink& operator=(const dist_sink&) = delete; + virtual ~dist_sink() = default; + +protected: + std::vector> _sinks; + + void _sink_it(const details::log_msg& msg) override + { + for (auto &sink : _sinks) + { + if( sink->should_log( msg.level)) + { + sink->log(msg); + } + } + } + + void _flush() override + { + for (auto &sink : _sinks) + sink->flush(); + } + +public: + + + void add_sink(std::shared_ptr sink) + { + std::lock_guard lock(base_sink::_mutex); + _sinks.push_back(sink); + } + + void remove_sink(std::shared_ptr sink) + { + std::lock_guard lock(base_sink::_mutex); + _sinks.erase(std::remove(_sinks.begin(), _sinks.end(), sink), _sinks.end()); + } +}; + +typedef dist_sink dist_sink_mt; +typedef dist_sink dist_sink_st; +} +} diff --git a/thirdparty/spdlog/sinks/file_sinks.h b/thirdparty/spdlog/sinks/file_sinks.h new file mode 100755 index 0000000..e785617 --- /dev/null +++ b/thirdparty/spdlog/sinks/file_sinks.h @@ -0,0 +1,253 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "base_sink.h" +#include "../details/null_mutex.h" +#include "../details/file_helper.h" +#include "../fmt/fmt.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace spdlog +{ +namespace sinks +{ +/* + * Trivial file sink with single file as target + */ +template +class simple_file_sink SPDLOG_FINAL : public base_sink < Mutex > +{ +public: + explicit simple_file_sink(const filename_t &filename, bool truncate = false):_force_flush(false) + { + _file_helper.open(filename, truncate); + } + + void set_force_flush(bool force_flush) + { + _force_flush = force_flush; + } + +protected: + void _sink_it(const details::log_msg& msg) override + { + _file_helper.write(msg); + if(_force_flush) + _file_helper.flush(); + } + void _flush() override + { + _file_helper.flush(); + } +private: + details::file_helper _file_helper; + bool _force_flush; +}; + +typedef simple_file_sink simple_file_sink_mt; +typedef simple_file_sink simple_file_sink_st; + +/* + * Rotating file sink based on size + */ +template +class rotating_file_sink SPDLOG_FINAL : public base_sink < Mutex > +{ +public: + rotating_file_sink(const filename_t &base_filename, + std::size_t max_size, std::size_t max_files) : + _base_filename(base_filename), + _max_size(max_size), + _max_files(max_files), + _current_size(0), + _file_helper() + { + _file_helper.open(calc_filename(_base_filename, 0)); + _current_size = _file_helper.size(); //expensive. called only once + } + + // calc filename according to index and file extension if exists. + // e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". + static filename_t calc_filename(const filename_t& filename, std::size_t index) + { + std::conditional::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; + if (index) + { + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); + w.write(SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); + } + else + { + w.write(SPDLOG_FILENAME_T("{}"), filename); + } + return w.str(); + } + +protected: + void _sink_it(const details::log_msg& msg) override + { + _current_size += msg.formatted.size(); + if (_current_size > _max_size) + { + _rotate(); + _current_size = msg.formatted.size(); + } + _file_helper.write(msg); + } + + void _flush() override + { + _file_helper.flush(); + } + + +private: + // Rotate files: + // log.txt -> log.1.txt + // log.1.txt -> log.2.txt + // log.2.txt -> log.3.txt + // log.3.txt -> delete + void _rotate() + { + using details::os::filename_to_str; + _file_helper.close(); + for (auto i = _max_files; i > 0; --i) + { + filename_t src = calc_filename(_base_filename, i - 1); + filename_t target = calc_filename(_base_filename, i); + + if (details::file_helper::file_exists(target)) + { + if (details::os::remove(target) != 0) + { + throw spdlog_ex("rotating_file_sink: failed removing " + filename_to_str(target), errno); + } + } + if (details::file_helper::file_exists(src) && details::os::rename(src, target)) + { + throw spdlog_ex("rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno); + } + } + _file_helper.reopen(true); + } + filename_t _base_filename; + std::size_t _max_size; + std::size_t _max_files; + std::size_t _current_size; + details::file_helper _file_helper; +}; + +typedef rotating_file_sink rotating_file_sink_mt; +typedef rotating_file_sinkrotating_file_sink_st; + +/* + * Default generator of daily log file names. + */ +struct default_daily_file_name_calculator +{ + // Create filename for the form filename.YYYY-MM-DD_hh-mm.ext + static filename_t calc_filename(const filename_t& filename) + { + std::tm tm = spdlog::details::os::localtime(); + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); + std::conditional::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; + w.write(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}_{:02d}-{:02d}{}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, ext); + return w.str(); + } +}; + +/* + * Generator of daily log file names in format basename.YYYY-MM-DD.ext + */ +struct dateonly_daily_file_name_calculator +{ + // Create filename for the form basename.YYYY-MM-DD + static filename_t calc_filename(const filename_t& filename) + { + std::tm tm = spdlog::details::os::localtime(); + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); + std::conditional::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; + w.write(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}{}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, ext); + return w.str(); + } +}; + +/* + * Rotating file sink based on date. rotates at midnight + */ +template +class daily_file_sink SPDLOG_FINAL :public base_sink < Mutex > +{ +public: + //create daily file sink which rotates on given time + daily_file_sink( + const filename_t& base_filename, + int rotation_hour, + int rotation_minute) : _base_filename(base_filename), + _rotation_h(rotation_hour), + _rotation_m(rotation_minute) + { + if (rotation_hour < 0 || rotation_hour > 23 || rotation_minute < 0 || rotation_minute > 59) + throw spdlog_ex("daily_file_sink: Invalid rotation time in ctor"); + _rotation_tp = _next_rotation_tp(); + _file_helper.open(FileNameCalc::calc_filename(_base_filename)); + } + + +protected: + void _sink_it(const details::log_msg& msg) override + { + if (std::chrono::system_clock::now() >= _rotation_tp) + { + _file_helper.open(FileNameCalc::calc_filename(_base_filename)); + _rotation_tp = _next_rotation_tp(); + } + _file_helper.write(msg); + } + + void _flush() override + { + _file_helper.flush(); + } + +private: + std::chrono::system_clock::time_point _next_rotation_tp() + { + auto now = std::chrono::system_clock::now(); + time_t tnow = std::chrono::system_clock::to_time_t(now); + tm date = spdlog::details::os::localtime(tnow); + date.tm_hour = _rotation_h; + date.tm_min = _rotation_m; + date.tm_sec = 0; + auto rotation_time = std::chrono::system_clock::from_time_t(std::mktime(&date)); + if (rotation_time > now) + return rotation_time; + else + return std::chrono::system_clock::time_point(rotation_time + std::chrono::hours(24)); + } + + filename_t _base_filename; + int _rotation_h; + int _rotation_m; + std::chrono::system_clock::time_point _rotation_tp; + details::file_helper _file_helper; +}; + +typedef daily_file_sink daily_file_sink_mt; +typedef daily_file_sink daily_file_sink_st; +} +} diff --git a/thirdparty/spdlog/sinks/msvc_sink.h b/thirdparty/spdlog/sinks/msvc_sink.h new file mode 100755 index 0000000..22b52c8 --- /dev/null +++ b/thirdparty/spdlog/sinks/msvc_sink.h @@ -0,0 +1,51 @@ +// +// Copyright(c) 2016 Alexander Dalshov. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#if defined(_WIN32) + +#include "base_sink.h" +#include "../details/null_mutex.h" + +#include + +#include +#include + +namespace spdlog +{ +namespace sinks +{ +/* +* MSVC sink (logging using OutputDebugStringA) +*/ +template +class msvc_sink : public base_sink < Mutex > +{ +public: + explicit msvc_sink() + { + } + + + +protected: + void _sink_it(const details::log_msg& msg) override + { + OutputDebugStringA(msg.formatted.c_str()); + } + + void _flush() override + {} +}; + +typedef msvc_sink msvc_sink_mt; +typedef msvc_sink msvc_sink_st; + +} +} + +#endif diff --git a/thirdparty/spdlog/sinks/null_sink.h b/thirdparty/spdlog/sinks/null_sink.h new file mode 100755 index 0000000..7605ac6 --- /dev/null +++ b/thirdparty/spdlog/sinks/null_sink.h @@ -0,0 +1,34 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "base_sink.h" +#include "../details/null_mutex.h" + +#include + +namespace spdlog +{ +namespace sinks +{ + +template +class null_sink : public base_sink < Mutex > +{ +protected: + void _sink_it(const details::log_msg&) override + {} + + void _flush() override + {} + +}; +typedef null_sink null_sink_st; +typedef null_sink null_sink_mt; + +} +} + diff --git a/thirdparty/spdlog/sinks/ostream_sink.h b/thirdparty/spdlog/sinks/ostream_sink.h new file mode 100755 index 0000000..1e5b261 --- /dev/null +++ b/thirdparty/spdlog/sinks/ostream_sink.h @@ -0,0 +1,47 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "../details/null_mutex.h" +#include "base_sink.h" + +#include +#include + +namespace spdlog +{ +namespace sinks +{ +template +class ostream_sink: public base_sink +{ +public: + explicit ostream_sink(std::ostream& os, bool force_flush=false) :_ostream(os), _force_flush(force_flush) {} + ostream_sink(const ostream_sink&) = delete; + ostream_sink& operator=(const ostream_sink&) = delete; + virtual ~ostream_sink() = default; + +protected: + void _sink_it(const details::log_msg& msg) override + { + _ostream.write(msg.formatted.data(), msg.formatted.size()); + if (_force_flush) + _ostream.flush(); + } + + void _flush() override + { + _ostream.flush(); + } + + std::ostream& _ostream; + bool _force_flush; +}; + +typedef ostream_sink ostream_sink_mt; +typedef ostream_sink ostream_sink_st; +} +} diff --git a/thirdparty/spdlog/sinks/sink.h b/thirdparty/spdlog/sinks/sink.h new file mode 100755 index 0000000..af61b54 --- /dev/null +++ b/thirdparty/spdlog/sinks/sink.h @@ -0,0 +1,53 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + + +#pragma once + +#include "../details/log_msg.h" + +namespace spdlog +{ +namespace sinks +{ +class sink +{ +public: + sink() + { + _level = level::trace; + } + + virtual ~sink() {} + virtual void log(const details::log_msg& msg) = 0; + virtual void flush() = 0; + + bool should_log(level::level_enum msg_level) const; + void set_level(level::level_enum log_level); + level::level_enum level() const; + +private: + level_t _level; + +}; + +inline bool sink::should_log(level::level_enum msg_level) const +{ + return msg_level >= _level.load(std::memory_order_relaxed); +} + +inline void sink::set_level(level::level_enum log_level) +{ + _level.store(log_level); +} + +inline level::level_enum sink::level() const +{ + return static_cast(_level.load(std::memory_order_relaxed)); +} + +} +} + diff --git a/thirdparty/spdlog/sinks/stdout_sinks.h b/thirdparty/spdlog/sinks/stdout_sinks.h new file mode 100755 index 0000000..dfbfccd --- /dev/null +++ b/thirdparty/spdlog/sinks/stdout_sinks.h @@ -0,0 +1,77 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "../details/null_mutex.h" +#include "base_sink.h" + +#include +#include +#include + +namespace spdlog +{ +namespace sinks +{ + +template +class stdout_sink SPDLOG_FINAL : public base_sink +{ + using MyType = stdout_sink; +public: + stdout_sink() + {} + static std::shared_ptr instance() + { + static std::shared_ptr instance = std::make_shared(); + return instance; + } +protected: + void _sink_it(const details::log_msg& msg) override + { + fwrite(msg.formatted.data(), sizeof(char), msg.formatted.size(), stdout); + _flush(); + } + + void _flush() override + { + fflush(stdout); + } +}; + +typedef stdout_sink stdout_sink_st; +typedef stdout_sink stdout_sink_mt; + + +template +class stderr_sink SPDLOG_FINAL : public base_sink +{ + using MyType = stderr_sink; +public: + stderr_sink() + {} + static std::shared_ptr instance() + { + static std::shared_ptr instance = std::make_shared(); + return instance; + } +protected: + void _sink_it(const details::log_msg& msg) override + { + fwrite(msg.formatted.data(), sizeof(char), msg.formatted.size(), stderr); + _flush(); + } + + void _flush() override + { + fflush(stderr); + } +}; + +typedef stderr_sink stderr_sink_mt; +typedef stderr_sink stderr_sink_st; +} +} diff --git a/thirdparty/spdlog/sinks/syslog_sink.h b/thirdparty/spdlog/sinks/syslog_sink.h new file mode 100755 index 0000000..c4a726a --- /dev/null +++ b/thirdparty/spdlog/sinks/syslog_sink.h @@ -0,0 +1,81 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "../common.h" + +#ifdef SPDLOG_ENABLE_SYSLOG + +#include "sink.h" +#include "../details/log_msg.h" + +#include +#include +#include + + +namespace spdlog +{ +namespace sinks +{ +/** + * Sink that write to syslog using the `syscall()` library call. + * + * Locking is not needed, as `syslog()` itself is thread-safe. + */ +class syslog_sink : public sink +{ +public: + // + syslog_sink(const std::string& ident = "", int syslog_option=0, int syslog_facility=LOG_USER): + _ident(ident) + { + _priorities[static_cast(level::trace)] = LOG_DEBUG; + _priorities[static_cast(level::debug)] = LOG_DEBUG; + _priorities[static_cast(level::info)] = LOG_INFO; + _priorities[static_cast(level::warn)] = LOG_WARNING; + _priorities[static_cast(level::err)] = LOG_ERR; + _priorities[static_cast(level::critical)] = LOG_CRIT; + _priorities[static_cast(level::off)] = LOG_INFO; + + //set ident to be program name if empty + ::openlog(_ident.empty()? nullptr:_ident.c_str(), syslog_option, syslog_facility); + } + ~syslog_sink() + { + ::closelog(); + } + + syslog_sink(const syslog_sink&) = delete; + syslog_sink& operator=(const syslog_sink&) = delete; + + void log(const details::log_msg &msg) override + { + ::syslog(syslog_prio_from_level(msg), "%s", msg.raw.str().c_str()); + } + + void flush() override + { + } + + +private: + std::array _priorities; + //must store the ident because the man says openlog might use the pointer as is and not a string copy + const std::string _ident; + + // + // Simply maps spdlog's log level to syslog priority level. + // + int syslog_prio_from_level(const details::log_msg &msg) const + { + return _priorities[static_cast(msg.level)]; + } +}; +} +} + +#endif diff --git a/thirdparty/spdlog/sinks/wincolor_sink.h b/thirdparty/spdlog/sinks/wincolor_sink.h new file mode 100755 index 0000000..8ee3d89 --- /dev/null +++ b/thirdparty/spdlog/sinks/wincolor_sink.h @@ -0,0 +1,121 @@ +// +// Copyright(c) 2016 spdlog +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "base_sink.h" +#include "../details/null_mutex.h" +#include "../common.h" + +#include +#include +#include +#include + +namespace spdlog +{ +namespace sinks +{ +/* + * Windows color console sink. Uses WriteConsoleA to write to the console with colors + */ +template +class wincolor_sink: public base_sink +{ +public: + const WORD BOLD = FOREGROUND_INTENSITY; + const WORD RED = FOREGROUND_RED; + const WORD CYAN = FOREGROUND_GREEN | FOREGROUND_BLUE; + const WORD WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + const WORD YELLOW = FOREGROUND_RED | FOREGROUND_GREEN; + + wincolor_sink(HANDLE std_handle): out_handle_(std_handle) + { + colors_[level::trace] = CYAN; + colors_[level::debug] = CYAN; + colors_[level::info] = WHITE | BOLD; + colors_[level::warn] = YELLOW | BOLD; + colors_[level::err] = RED | BOLD; // red bold + colors_[level::critical] = BACKGROUND_RED | WHITE | BOLD; // white bold on red background + colors_[level::off] = 0; + } + + virtual ~wincolor_sink() + { + this->flush(); + } + + wincolor_sink(const wincolor_sink& other) = delete; + wincolor_sink& operator=(const wincolor_sink& other) = delete; + +protected: + virtual void _sink_it(const details::log_msg& msg) override + { + auto color = colors_[msg.level]; + auto orig_attribs = set_console_attribs(color); + WriteConsoleA(out_handle_, msg.formatted.data(), static_cast(msg.formatted.size()), nullptr, nullptr); + SetConsoleTextAttribute(out_handle_, orig_attribs); //reset to orig colors + } + + virtual void _flush() override + { + // windows console always flushed? + } + + // change the color for the given level + void set_color(level::level_enum level, WORD color) + { + std::lock_guard lock(base_sink::_mutex); + colors_[level] = color; + } + +private: + HANDLE out_handle_; + std::map colors_; + + // set color and return the orig console attributes (for resetting later) + WORD set_console_attribs(WORD attribs) + { + CONSOLE_SCREEN_BUFFER_INFO orig_buffer_info; + GetConsoleScreenBufferInfo(out_handle_, &orig_buffer_info); + WORD back_color = orig_buffer_info.wAttributes; + // retrieve the current background color + back_color &= static_cast(~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY)); + // keep the background color unchanged + SetConsoleTextAttribute(out_handle_, attribs | back_color); + return orig_buffer_info.wAttributes; //return orig attribs + } +}; + +// +// windows color console to stdout +// +template +class wincolor_stdout_sink: public wincolor_sink +{ +public: + wincolor_stdout_sink() : wincolor_sink(GetStdHandle(STD_OUTPUT_HANDLE)) + {} +}; + +typedef wincolor_stdout_sink wincolor_stdout_sink_mt; +typedef wincolor_stdout_sink wincolor_stdout_sink_st; + +// +// windows color console to stderr +// +template +class wincolor_stderr_sink: public wincolor_sink +{ +public: + wincolor_stderr_sink() : wincolor_sink(GetStdHandle(STD_ERROR_HANDLE)) + {} +}; + +typedef wincolor_stderr_sink wincolor_stderr_sink_mt; +typedef wincolor_stderr_sink wincolor_stderr_sink_st; + +} +} diff --git a/thirdparty/spdlog/sinks/windebug_sink.h b/thirdparty/spdlog/sinks/windebug_sink.h new file mode 100755 index 0000000..c22e952 --- /dev/null +++ b/thirdparty/spdlog/sinks/windebug_sink.h @@ -0,0 +1,29 @@ +// +// Copyright(c) 2017 Alexander Dalshov. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#if defined(_WIN32) + +#include "msvc_sink.h" + +namespace spdlog +{ +namespace sinks +{ + +/* +* Windows debug sink (logging using OutputDebugStringA, synonym for msvc_sink) +*/ +template +using windebug_sink = msvc_sink; + +typedef msvc_sink_mt windebug_sink_mt; +typedef msvc_sink_st windebug_sink_st; + +} +} + +#endif diff --git a/thirdparty/spdlog/spdlog.h b/thirdparty/spdlog/spdlog.h new file mode 100755 index 0000000..c7af2c7 --- /dev/null +++ b/thirdparty/spdlog/spdlog.h @@ -0,0 +1,192 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// +// spdlog main header file. +// see example.cpp for usage example + +#pragma once + +#define SPDLOG_VERSION "0.16.2" + +#include "tweakme.h" +#include "common.h" +#include "logger.h" + +#include +#include +#include +#include + +namespace spdlog +{ + +// +// Return an existing logger or nullptr if a logger with such name doesn't exist. +// example: spdlog::get("my_logger")->info("hello {}", "world"); +// +std::shared_ptr get(const std::string& name); + + +// +// Set global formatting +// example: spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e %l : %v"); +// +void set_pattern(const std::string& format_string); +void set_formatter(formatter_ptr f); + +// +// Set global logging level +// +void set_level(level::level_enum log_level); + +// +// Set global flush level +// +void flush_on(level::level_enum log_level); + +// +// Set global error handler +// +void set_error_handler(log_err_handler); + +// +// Turn on async mode (off by default) and set the queue size for each async_logger. +// effective only for loggers created after this call. +// queue_size: size of queue (must be power of 2): +// Each logger will pre-allocate a dedicated queue with queue_size entries upon construction. +// +// async_overflow_policy (optional, block_retry by default): +// async_overflow_policy::block_retry - if queue is full, block until queue has room for the new log entry. +// async_overflow_policy::discard_log_msg - never block and discard any new messages when queue overflows. +// +// worker_warmup_cb (optional): +// callback function that will be called in worker thread upon start (can be used to init stuff like thread affinity) +// +// worker_teardown_cb (optional): +// callback function that will be called in worker thread upon exit +// +void set_async_mode(size_t queue_size, const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, const std::function& worker_warmup_cb = nullptr, const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), const std::function& worker_teardown_cb = nullptr); + +// Turn off async mode +void set_sync_mode(); + + +// +// Create and register multi/single threaded basic file logger. +// Basic logger simply writes to given file without any limitations or rotations. +// +std::shared_ptr basic_logger_mt(const std::string& logger_name, const filename_t& filename, bool truncate = false); +std::shared_ptr basic_logger_st(const std::string& logger_name, const filename_t& filename, bool truncate = false); + +// +// Create and register multi/single threaded rotating file logger +// +std::shared_ptr rotating_logger_mt(const std::string& logger_name, const filename_t& filename, size_t max_file_size, size_t max_files); +std::shared_ptr rotating_logger_st(const std::string& logger_name, const filename_t& filename, size_t max_file_size, size_t max_files); + +// +// Create file logger which creates new file on the given time (default in midnight): +// +std::shared_ptr daily_logger_mt(const std::string& logger_name, const filename_t& filename, int hour=0, int minute=0); +std::shared_ptr daily_logger_st(const std::string& logger_name, const filename_t& filename, int hour=0, int minute=0); + +// +// Create and register stdout/stderr loggers +// +std::shared_ptr stdout_logger_mt(const std::string& logger_name); +std::shared_ptr stdout_logger_st(const std::string& logger_name); +std::shared_ptr stderr_logger_mt(const std::string& logger_name); +std::shared_ptr stderr_logger_st(const std::string& logger_name); +// +// Create and register colored stdout/stderr loggers +// +std::shared_ptr stdout_color_mt(const std::string& logger_name); +std::shared_ptr stdout_color_st(const std::string& logger_name); +std::shared_ptr stderr_color_mt(const std::string& logger_name); +std::shared_ptr stderr_color_st(const std::string& logger_name); + + +// +// Create and register a syslog logger +// +#ifdef SPDLOG_ENABLE_SYSLOG +std::shared_ptr syslog_logger(const std::string& logger_name, const std::string& ident = "", int syslog_option = 0, int syslog_facilty = (1<<3)); +#endif + +#if defined(__ANDROID__) +std::shared_ptr android_logger(const std::string& logger_name, const std::string& tag = "spdlog"); +#endif + +// Create and register a logger with a single sink +std::shared_ptr create(const std::string& logger_name, const sink_ptr& sink); + +// Create and register a logger with multiple sinks +std::shared_ptr create(const std::string& logger_name, sinks_init_list sinks); +template +std::shared_ptr create(const std::string& logger_name, const It& sinks_begin, const It& sinks_end); + + +// Create and register a logger with templated sink type +// Example: +// spdlog::create("mylog", "dailylog_filename"); +template +std::shared_ptr create(const std::string& logger_name, Args...); + +// Create and register an async logger with a single sink +std::shared_ptr create_async(const std::string& logger_name, const sink_ptr& sink, size_t queue_size, const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, const std::function& worker_warmup_cb = nullptr, const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), const std::function& worker_teardown_cb = nullptr); + +// Create and register an async logger with multiple sinks +std::shared_ptr create_async(const std::string& logger_name, sinks_init_list sinks, size_t queue_size, const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, const std::function& worker_warmup_cb = nullptr, const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), const std::function& worker_teardown_cb = nullptr); +template +std::shared_ptr create_async(const std::string& logger_name, const It& sinks_begin, const It& sinks_end, size_t queue_size, const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, const std::function& worker_warmup_cb = nullptr, const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(), const std::function& worker_teardown_cb = nullptr); + +// Register the given logger with the given name +void register_logger(std::shared_ptr logger); + +// Apply a user defined function on all registered loggers +// Example: +// spdlog::apply_all([&](std::shared_ptr l) {l->flush();}); +void apply_all(std::function)> fun); + +// Drop the reference to the given logger +void drop(const std::string &name); + +// Drop all references from the registry +void drop_all(); + + +/////////////////////////////////////////////////////////////////////////////// +// +// Trace & Debug can be switched on/off at compile time for zero cost debug statements. +// Uncomment SPDLOG_DEBUG_ON/SPDLOG_TRACE_ON in tweakme.h to enable. +// SPDLOG_TRACE(..) will also print current file and line. +// +// Example: +// spdlog::set_level(spdlog::level::trace); +// SPDLOG_TRACE(my_logger, "some trace message"); +// SPDLOG_TRACE(my_logger, "another trace message {} {}", 1, 2); +// SPDLOG_DEBUG(my_logger, "some debug message {} {}", 3, 4); +/////////////////////////////////////////////////////////////////////////////// + +#ifdef SPDLOG_TRACE_ON +# define SPDLOG_STR_H(x) #x +# define SPDLOG_STR_HELPER(x) SPDLOG_STR_H(x) +# ifdef _MSC_VER +# define SPDLOG_TRACE(logger, ...) logger->trace("[ " __FILE__ "(" SPDLOG_STR_HELPER(__LINE__) ") ] " __VA_ARGS__) +# else +# define SPDLOG_TRACE(logger, ...) logger->trace("[ " __FILE__ ":" SPDLOG_STR_HELPER(__LINE__) " ] " __VA_ARGS__) +# endif +#else +# define SPDLOG_TRACE(logger, ...) (void)0 +#endif + +#ifdef SPDLOG_DEBUG_ON +# define SPDLOG_DEBUG(logger, ...) logger->debug(__VA_ARGS__) +#else +# define SPDLOG_DEBUG(logger, ...) (void)0 +#endif + +} + +#include "details/spdlog_impl.h" diff --git a/thirdparty/spdlog/tweakme.h b/thirdparty/spdlog/tweakme.h new file mode 100755 index 0000000..ad01a09 --- /dev/null +++ b/thirdparty/spdlog/tweakme.h @@ -0,0 +1,160 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +/////////////////////////////////////////////////////////////////////////////// +// +// Edit this file to squeeze more performance, and to customize supported features +// +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Under Linux, the much faster CLOCK_REALTIME_COARSE clock can be used. +// This clock is less accurate - can be off by dozens of millis - depending on the kernel HZ. +// Uncomment to use it instead of the regular clock. +// +// #define SPDLOG_CLOCK_COARSE +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment if date/time logging is not needed and never appear in the log pattern. +// This will prevent spdlog from querying the clock on each log call. +// +// WARNING: If the log pattern contains any date/time while this flag is on, the result is undefined. +// You must set new pattern(spdlog::set_pattern(..") without any date/time in it +// +// #define SPDLOG_NO_DATETIME +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment if thread id logging is not needed (i.e. no %t in the log pattern). +// This will prevent spdlog from querying the thread id on each log call. +// +// WARNING: If the log pattern contains thread id (i.e, %t) while this flag is on, the result is undefined. +// +// #define SPDLOG_NO_THREAD_ID +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to prevent spdlog from caching thread ids in thread local storage. +// By default spdlog saves thread ids in tls to gain a few micros for each call. +// +// WARNING: if your program forks, UNCOMMENT this flag to prevent undefined thread ids in the children logs. +// +// #define SPDLOG_DISABLE_TID_CACHING +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment if logger name logging is not needed. +// This will prevent spdlog from copying the logger name on each log call. +// +// #define SPDLOG_NO_NAME +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable the SPDLOG_DEBUG/SPDLOG_TRACE macros. +// +// #define SPDLOG_DEBUG_ON +// #define SPDLOG_TRACE_ON +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to avoid locking in the registry operations (spdlog::get(), spdlog::drop() spdlog::register()). +// Use only if your code never modifies concurrently the registry. +// Note that upon creating a logger the registry is modified by spdlog.. +// +// #define SPDLOG_NO_REGISTRY_MUTEX +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to avoid spdlog's usage of atomic log levels +// Use only if your code never modifies a logger's log levels concurrently by different threads. +// +// #define SPDLOG_NO_ATOMIC_LEVELS +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable usage of wchar_t for file names on Windows. +// +// #define SPDLOG_WCHAR_FILENAMES +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to override default eol ("\n" or "\r\n" under Linux/Windows) +// +// #define SPDLOG_EOL ";-)\n" +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to use your own copy of the fmt library instead of spdlog's copy. +// In this case spdlog will try to include so set your -I flag accordingly. +// +// #define SPDLOG_FMT_EXTERNAL +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to use printf-style messages in your logs instead of the usual +// format-style used by default. +// +// #define SPDLOG_FMT_PRINTF +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable syslog (disabled by default) +// +// #define SPDLOG_ENABLE_SYSLOG +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable wchar_t support (convert to utf8) +// +// #define SPDLOG_WCHAR_TO_UTF8_SUPPORT +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to prevent child processes from inheriting log file descriptors +// +// #define SPDLOG_PREVENT_CHILD_FD +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment if your compiler doesn't support the "final" keyword. +// The final keyword allows more optimizations in release +// mode with recent compilers. See GCC's documentation for -Wsuggest-final-types +// for instance. +// +// #define SPDLOG_NO_FINAL +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable message counting feature. +// Use the %i in the logger pattern to display log message sequence id. +// +// #define SPDLOG_ENABLE_MESSAGE_COUNTER +/////////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to customize level names (e.g. "MT TRACE") +// +// #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING", "MY ERROR", "MY CRITICAL", "OFF" } +/////////////////////////////////////////////////////////////////////////////// diff --git a/variant_catalog/variant_catalog_grch37.json b/variant_catalog/variant_catalog_grch37.json new file mode 100755 index 0000000..9cef6ed --- /dev/null +++ b/variant_catalog/variant_catalog_grch37.json @@ -0,0 +1,231 @@ +[ + { + "VariantType": "Repeat", + "LocusId": "AFF2", + "LocusStructure": "(GCC)*", + "ReferenceRegion": "X:147582151-147582211" + }, + { + "VariantType": "Repeat", + "LocusId": "AR", + "LocusStructure": "(GCA)*", + "ReferenceRegion": "X:66765158-66765227" + }, + { + "VariantType": "Repeat", + "LocusId": "ATN1", + "LocusStructure": "(CAG)*", + "ReferenceRegion": "12:7045879-7045936" + }, + { + "VariantType": "Repeat", + "LocusId": "ATXN10", + "LocusStructure": "(ATTCT)*", + "ReferenceRegion": "22:46191234-46191304" + }, + { + "VariantType": "Repeat", + "LocusId": "ATXN1", + "LocusStructure": "(TGC)*", + "ReferenceRegion": "6:16327864-16327954" + }, + { + "VariantType": "Repeat", + "LocusId": "ATXN2", + "LocusStructure": "(GCT)*", + "ReferenceRegion": "12:112036753-112036822" + }, + { + "VariantType": "Repeat", + "LocusId": "ATXN3", + "LocusStructure": "(GCT)*", + "ReferenceRegion": "14:92537353-92537386" + }, + { + "VariantType": "Repeat", + "LocusId": "PHOX2B", + "LocusStructure": "(GCN)*", + "ReferenceRegion": "4:41747989-41748049" + }, + { + "VariantType": "Repeat", + "LocusId": "ATXN7", + "LocusStructure": "(GCA)*", + "ReferenceRegion": "3:63898360-63898390" + }, + { + "LocusId": "ATXN8OS", + "LocusStructure": "(CTA)*(CTG)*", + "ReferenceRegion": [ + "13:70713485-70713515", + "13:70713515-70713560" + ], + "VariantType": [ + "Repeat", + "Repeat" + ] + }, + { + "VariantType": "Repeat", + "LocusId": "C9ORF72", + "LocusStructure": "(GGCCCC)*", + "ReferenceRegion": "9:27573526-27573544", + "OfftargetRegions": [ + "1:150552238-150552273", + "11:910821-910969", + "11:2905960-2906190", + "11:44748502-44748739", + "12:54476278-54476439", + "12:128751638-128751649", + "13:107569867-107570000", + "16:819162-819226", + "16:49526377-49526380", + "18:74203223-74203243", + "19:878803-878954", + "19:39897404-39897410", + "2:240659782-240660010", + "20:388475-388656", + "20:60640211-60640346", + "22:51066608-51066626", + "3:49591993-49592173", + "3:50161662-50161732", + "5:10761494-10761495", + "5:176797951-176798133", + "6:33601522-33601525", + "6:109804703-109804748", + "9:132331488-132331496", + "9:132428311-132428437", + "X:9754276-9754424", + "X:49644488-49644585", + "X:53652706-53652891", + "X:153569931-153569996", + "X:153618748-153618918" + ] + }, + { + "VariantType": "Repeat", + "LocusId": "CACNA1A", + "LocusStructure": "(CTG)*", + "ReferenceRegion": "19:13318672-13318711" + }, + { + "VariantType": "Repeat", + "LocusId": "CBL", + "LocusStructure": "(CGG)*", + "ReferenceRegion": "11:119076999-119077032" + }, + { + "LocusId": "CNBP", + "LocusStructure": "(CAGG)*(CAGA)*(CA)*", + "ReferenceRegion": [ + "3:128891419-128891499", + "3:128891499-128891539", + "3:128891539-128891575" + ], + "VariantType": [ + "Repeat", + "Repeat", + "Repeat" + ] + }, + { + "VariantType": "Repeat", + "LocusId": "CSTB", + "LocusStructure": "(CGCGGGGCGGGG)*", + "ReferenceRegion": "21:45196324-45196360" + }, + { + "VariantType": "Repeat", + "LocusId": "DIP2B", + "LocusStructure": "(GGC)*", + "ReferenceRegion": "12:50898784-50898805" + }, + { + "VariantType": "Repeat", + "LocusId": "DMPK", + "LocusStructure": "(CAG)*", + "ReferenceRegion": "19:46273462-46273522" + }, + { + "VariantType": "RareRepeat", + "LocusId": "FMR1", + "LocusStructure": "(CGG)*", + "ReferenceRegion": "X:146993568-146993628", + "OfftargetRegions": [ + "10:101295192-101295194", + "12:7781290-7781350", + "12:125052154-125052156", + "16:25703613-25703635", + "16:28074516-28074518", + "17:30814024-30814026", + "17:64298467-64298469", + "19:2015524-2015526", + "2:87141540-87141618", + "2:92230909-92230911", + "2:211036020-211036032", + "2:225449878-225449880", + "20:30865500-30865516", + "5:443334-443364", + "7:20824939-20824941", + "7:100271437-100271439", + "7:104654597-104654599", + "7:143059853-143059855", + "9:100616695-100616697", + "X:20009036-20009046" + ] + }, + { + "LocusId": "FXN", + "LocusStructure": "(A)*(GAA)*", + "ReferenceRegion": [ + "9:71652177-71652202", + "9:71652202-71652220" + ], + "VariantType": [ + "Repeat", + "Repeat" + ] + }, + { + "LocusId": "HTT", + "LocusStructure": "(CAG)*CAACAG(CCG)*", + "ReferenceRegion": [ + "4:3076603-3076660", + "4:3076666-3076693" + ], + "VariantType": [ + "Repeat", + "Repeat" + ] + }, + { + "VariantType": "Repeat", + "LocusId": "JPH3", + "LocusStructure": "(CTG)*", + "ReferenceRegion": "16:87637893-87637935" + }, + { + "VariantType": "Repeat", + "LocusId": "NOP56", + "LocusStructure": "(GGCCTG)*", + "ReferenceRegion": "20:2633379-2633403" + }, + { + "VariantType": "Repeat", + "LocusId": "PPP2R2B", + "LocusStructure": "(GCT)*", + "ReferenceRegion": "5:146258290-146258320" + }, + { + "VariantType": "Repeat", + "LocusId": "TBP", + "LocusStructure": "(GCA)*", + "ReferenceRegion": "6:170870994-170871105" + }, + { + "VariantType": "Repeat", + "LocusId": "TCF4", + "LocusStructure": "(CAG)*", + "ReferenceRegion": "18:53253386-53253458" + } +] diff --git a/variant_catalog/variant_catalog_grch38.json b/variant_catalog/variant_catalog_grch38.json new file mode 100755 index 0000000..6a54171 --- /dev/null +++ b/variant_catalog/variant_catalog_grch38.json @@ -0,0 +1,471 @@ +[ + { + "LocusId": "AFF2", + "LocusStructure": "(GCC)*", + "ReferenceRegion": "X:148500631-148500691", + "VariantType": "Repeat" + }, + { + "LocusId": "AR", + "LocusStructure": "(GCA)*", + "ReferenceRegion": "X:67545316-67545385", + "VariantType": "Repeat" + }, + { + "LocusId": "ATN1", + "LocusStructure": "(CAG)*", + "ReferenceRegion": "12:6936716-6936773", + "VariantType": "Repeat" + }, + { + "LocusId": "ATXN10", + "LocusStructure": "(ATTCT)*", + "ReferenceRegion": "22:45795354-45795424", + "VariantType": "Repeat" + }, + { + "LocusId": "ATXN1", + "LocusStructure": "(TGC)*", + "ReferenceRegion": "6:16327633-16327723", + "VariantType": "Repeat" + }, + { + "LocusId": "ATXN2", + "LocusStructure": "(GCT)*", + "ReferenceRegion": "12:111598949-111599018", + "VariantType": "Repeat" + }, + { + "LocusId": "ATXN3", + "LocusStructure": "(GCT)*", + "ReferenceRegion": "14:92071009-92071042", + "VariantType": "Repeat" + }, + { + "LocusId": "PHOX2B", + "LocusStructure": "(GCN)*", + "ReferenceRegion": "4:41745972-41746032", + "VariantType": "Repeat" + }, + { + "LocusId": "ATXN7", + "LocusStructure": "(GCA)*", + "ReferenceRegion": "3:63912684-63912714", + "VariantType": "Repeat" + }, + { + "LocusId": "ATXN8OS", + "LocusStructure": "(CTA)*(CTG)*", + "ReferenceRegion": [ + "13:70139353-70139383", + "13:70139383-70139428" + ], + "VariantType": [ + "Repeat", + "Repeat" + ] + }, + { + "LocusId": "C9ORF72", + "LocusStructure": "(GGCCCC)*", + "OfftargetRegions": [ + "1:8423620-8424123", + "1:30908133-30908774", + "1:150579390-150580033", + "10:930904-931660", + "10:24952416-24952917", + "10:43304792-43305293", + "10:46336798-46337407", + "10:46816083-46816694", + "10:100999603-101000210", + "11:910565-911217", + "11:2884462-2885264", + "11:44726686-44727485", + "11:67372279-67372872", + "12:459654-460155", + "12:54082197-54082983", + "12:128266836-128267355", + "13:20987998-20988595", + "13:25468389-25469012", + "13:106917207-106917872", + "14:32938886-32939513", + "14:105679947-105680448", + "15:30223043-30223669", + "16:768799-769576", + "16:1614059-1614704", + "16:3134791-3135352", + "16:87602166-87602667", + "17:10198365-10198994", + "17:68205352-68205991", + "17:81891215-81891716", + "18:54223897-54224704", + "19:613299-613854", + "19:878542-879249", + "19:2808202-2808704", + "19:17947549-17948080", + "19:39406514-39407140", + "19:50568087-50568733", + "19:52296834-52297335", + "2:3592013-3592538", + "2:25129074-25129575", + "2:168247028-168247668", + "2:234496868-234497369", + "2:234951698-234952200", + "2:236237107-236237609", + "2:239737824-239738572", + "20:407681-408343", + "20:62064993-62065670", + "21:45286831-45287440", + "22:39994753-39995382", + "22:49117029-49117530", + "22:50627810-50628453", + "3:46845815-46846372", + "3:47578060-47578713", + "3:49554283-49555050", + "3:50124039-50124697", + "3:51706744-51707245", + "3:128497689-128498194", + "3:184379811-184380428", + "4:151408887-151409388", + "5:10761008-10761635", + "5:146458824-146459326", + "5:172454307-172454808", + "5:177370694-177371467", + "6:33633374-33634093", + "6:43021418-43022045", + "6:46490762-46491263", + "6:109483278-109483938", + "8:141308350-141308989", + "9:83817587-83818088", + "9:129568833-129569572", + "9:129665778-129666418", + "X:9785974-9786751", + "X:49879636-49880326", + "X:53625582-53626227", + "X:154341221-154341987", + "X:154390157-154390883" + ], + "ReferenceRegion": "9:27573528-27573546", + "VariantType": "Repeat" + }, + { + "LocusId": "CACNA1A", + "LocusStructure": "(CTG)*", + "ReferenceRegion": "19:13207858-13207897", + "VariantType": "Repeat" + }, + { + "LocusId": "CBL", + "LocusStructure": "(CGG)*", + "ReferenceRegion": "11:119206289-119206322", + "VariantType": "Repeat" + }, + { + "LocusId": "CNBP", + "LocusStructure": "(CAGG)*(CAGA)*(CA)*", + "ReferenceRegion": [ + "3:129172576-129172656", + "3:129172656-129172696", + "3:129172696-129172732" + ], + "VariantType": [ + "Repeat", + "Repeat", + "Repeat" + ] + }, + { + "LocusId": "CSTB", + "LocusStructure": "(CGCGGGGCGGGG)*", + "ReferenceRegion": "21:43776443-43776479", + "VariantType": "Repeat" + }, + { + "LocusId": "DIP2B", + "LocusStructure": "(GGC)*", + "ReferenceRegion": "12:50505001-50505022", + "VariantType": "Repeat" + }, + { + "LocusId": "DMPK", + "LocusStructure": "(CAG)*", + "ReferenceRegion": "19:45770204-45770264", + "VariantType": "Repeat" + }, + { + "LocusId": "FMR1", + "LocusStructure": "(CGG)*", + "OfftargetRegions": [ + "1:3068800-3069410", + "1:21176191-21176850", + "1:22142746-22143526", + "1:26695803-26696436", + "1:32964053-32964646", + "1:38046253-38047002", + "1:43979082-43979731", + "1:50418725-50419472", + "1:53738908-53739574", + "1:108559853-108560513", + "1:120069049-120069669", + "1:120723873-120724492", + "1:146228424-146229045", + "1:149390544-149391206", + "1:154558232-154558862", + "1:166166596-166167097", + "1:208244048-208244696", + "1:244048773-244049274", + "1:246931114-246931739", + "10:15370499-15371495", + "10:50991053-50991697", + "10:75400963-75401593", + "10:96586506-96587141", + "10:99535056-99535844", + "10:131900767-131901422", + "10:132396901-132397563", + "11:535030-535612", + "11:13010216-13010825", + "11:44309365-44309993", + "11:119206036-119206672", + "12:1593780-1594552", + "12:32399142-32399783", + "12:45729325-45729915", + "12:55743146-55743788", + "12:64404090-64404725", + "12:69239354-69239905", + "12:112381709-112382586", + "12:123533342-123533981", + "13:36919925-36920587", + "14:28767200-28767848", + "14:33800220-33800846", + "14:67515148-67515807", + "14:69572823-69573477", + "14:102592572-102593302", + "15:24847862-24848492", + "15:51737291-51737920", + "15:88256090-88256591", + "16:2178673-2179293", + "16:24729306-24730163", + "16:25691935-25692619", + "16:28062828-28063460", + "16:49855133-49855768", + "16:55478902-55479533", + "16:73058438-73059091", + "16:79598817-79599460", + "16:85168882-85169517", + "16:88570363-88570870", + "17:2593208-2593916", + "17:7885051-7885670", + "17:26774258-26774914", + "17:28644585-28645198", + "17:44219714-44220215", + "17:45241687-45242324", + "17:47693725-47694343", + "17:62065114-62065757", + "17:66301954-66302705", + "17:75588817-75589447", + "18:5891121-5891766", + "18:12896734-12897356", + "18:28176796-28177449", + "18:31942935-31943436", + "18:33578203-33578845", + "18:47246692-47247323", + "18:57435463-57436097", + "19:1285831-1286476", + "19:1407366-1408019", + "19:1567732-1568394", + "19:2015163-2015946", + "19:2163771-2164421", + "19:2307770-2308540", + "19:14495661-14496299", + "19:15332243-15332867", + "19:19406196-19406820", + "19:33796448-33797068", + "19:36140743-36141396", + "19:36309135-36309673", + "19:42116612-42117226", + "19:42241986-42242612", + "19:42253491-42254110", + "19:45496698-45497341", + "19:45972993-45973745", + "19:49072290-49072904", + "2:37156530-37157166", + "2:47905271-47905864", + "2:50346524-50347267", + "2:72148267-72148905", + "2:73269201-73269839", + "2:85133319-85133967", + "2:86914035-86915119", + "2:90324439-90325070", + "2:90325128-90325838", + "2:91809550-91810294", + "2:92042460-92043192", + "2:104855035-104856469", + "2:109760018-109760924", + "2:109794204-109794856", + "2:110610684-110611442", + "2:117870706-117871322", + "2:160493196-160493697", + "2:202376257-202376900", + "2:210170918-210171567", + "2:219627269-219627917", + "2:224584787-224585433", + "2:231037450-231038211", + "20:10034651-10035260", + "20:32277442-32278091", + "20:35226390-35227019", + "20:57709537-57710167", + "21:15730043-15730612", + "21:31732025-31732607", + "21:33554862-33555470", + "21:34614575-34615198", + "21:37072924-37073548", + "21:42218961-42219628", + "22:20131541-20132067", + "22:20858295-20858923", + "22:28883458-28884052", + "22:37983424-37984043", + "22:47379027-47379724", + "22:50474703-50475344", + "3:12287534-12288151", + "3:15859307-15859969", + "3:18425089-18425592", + "3:19947012-19947631", + "3:28575591-28576219", + "3:51390995-51391577", + "3:129605015-129605681", + "3:158105430-158106067", + "3:177197309-177197932", + "3:194583636-194584137", + "4:3074688-3075409", + "4:15003613-15004273", + "4:20251745-20252399", + "4:74932808-74933428", + "4:104491108-104491609", + "4:140153857-140154359", + "4:146639045-146639684", + "5:442841-443512", + "5:6448350-6449058", + "5:14144642-14145253", + "5:45695838-45696469", + "5:61332420-61333055", + "5:93583955-93584562", + "5:108380749-108381400", + "5:134371096-134371734", + "5:141878162-141878822", + "5:171419845-171420495", + "5:177554234-177554890", + "5:180291708-180292348", + "6:1389780-1390413", + "6:7107296-7107932", + "6:13711134-13711716", + "6:45422373-45423021", + "6:46490940-46491586", + "6:98834721-98835357", + "6:114342166-114342817", + "6:116680740-116681370", + "6:117906804-117907451", + "6:149317153-149317781", + "6:149317785-149318327", + "6:150865347-150865972", + "6:156778590-156779239", + "7:18086648-18087297", + "7:20785040-20785780", + "7:27173496-27174130", + "7:32891624-32892251", + "7:42237599-42238236", + "7:50793142-50793767", + "7:55887234-55888138", + "7:63114054-63114797", + "7:64181749-64182417", + "7:75092521-75093173", + "7:77536829-77537470", + "7:97005856-97006507", + "7:99143640-99144272", + "7:100673442-100674084", + "7:100693980-100694615", + "7:105013776-105014419", + "7:143362396-143363025", + "7:151086293-151086936", + "8:37698038-37698670", + "8:60680068-60680652", + "8:65841587-65842212", + "8:88327245-88327873", + "8:104588708-104589361", + "8:139702696-139703331", + "8:144461991-144462624", + "9:27572828-27573455", + "9:34957849-34958490", + "9:63450116-63450878", + "9:77019890-77020661", + "9:97854152-97854814", + "9:120714014-120714592", + "9:122126507-122127093", + "9:124869204-124869705", + "9:128841198-128841699", + "9:130579253-130579892", + "X:19990567-19991309", + "X:21374362-21374866", + "X:48957464-48958121", + "X:68912863-68913485", + "X:100409331-100409962", + "X:126165699-126166354", + "X:150983148-150983796" + ], + "ReferenceRegion": "X:147912050-147912110", + "VariantType": "RareRepeat" + }, + { + "LocusId": "FXN", + "LocusStructure": "(A)*(GAA)*", + "ReferenceRegion": [ + "9:69037261-69037286", + "9:69037286-69037304" + ], + "VariantType": [ + "Repeat", + "Repeat" + ] + }, + { + "LocusId": "HTT", + "LocusStructure": "(CAG)*CAACAG(CCG)*", + "ReferenceRegion": [ + "4:3074876-3074933", + "4:3074939-3074966" + ], + "VariantType": [ + "Repeat", + "Repeat" + ] + }, + { + "LocusId": "JPH3", + "LocusStructure": "(CTG)*", + "ReferenceRegion": "16:87604287-87604329", + "VariantType": "Repeat" + }, + { + "LocusId": "NOP56", + "LocusStructure": "(GGCCTG)*", + "ReferenceRegion": "20:2652733-2652757", + "VariantType": "Repeat" + }, + { + "LocusId": "PPP2R2B", + "LocusStructure": "(GCT)*", + "ReferenceRegion": "5:146878727-146878757", + "VariantType": "Repeat" + }, + { + "LocusId": "TBP", + "LocusStructure": "(GCA)*", + "ReferenceRegion": "6:170561906-170562017", + "VariantType": "Repeat" + }, + { + "LocusId": "TCF4", + "LocusStructure": "(CAG)*", + "ReferenceRegion": "18:55586155-55586227", + "VariantType": "Repeat" + } +] diff --git a/variant_catalog/variant_catalog_hg19.json b/variant_catalog/variant_catalog_hg19.json new file mode 100755 index 0000000..0c9f2f5 --- /dev/null +++ b/variant_catalog/variant_catalog_hg19.json @@ -0,0 +1,231 @@ +[ + { + "VariantType": "Repeat", + "LocusId": "AFF2", + "LocusStructure": "(GCC)*", + "ReferenceRegion": "chrX:147582151-147582211" + }, + { + "VariantType": "Repeat", + "LocusId": "AR", + "LocusStructure": "(GCA)*", + "ReferenceRegion": "chrX:66765158-66765227" + }, + { + "VariantType": "Repeat", + "LocusId": "ATN1", + "LocusStructure": "(CAG)*", + "ReferenceRegion": "chr12:7045879-7045936" + }, + { + "VariantType": "Repeat", + "LocusId": "ATXN10", + "LocusStructure": "(ATTCT)*", + "ReferenceRegion": "chr22:46191234-46191304" + }, + { + "VariantType": "Repeat", + "LocusId": "ATXN1", + "LocusStructure": "(TGC)*", + "ReferenceRegion": "chr6:16327864-16327954" + }, + { + "VariantType": "Repeat", + "LocusId": "ATXN2", + "LocusStructure": "(GCT)*", + "ReferenceRegion": "chr12:112036753-112036822" + }, + { + "VariantType": "Repeat", + "LocusId": "ATXN3", + "LocusStructure": "(GCT)*", + "ReferenceRegion": "chr14:92537353-92537386" + }, + { + "VariantType": "Repeat", + "LocusId": "PHOX2B", + "LocusStructure": "(GCN)*", + "ReferenceRegion": "chr4:41747989-41748049" + }, + { + "VariantType": "Repeat", + "LocusId": "ATXN7", + "LocusStructure": "(GCA)*", + "ReferenceRegion": "chr3:63898360-63898390" + }, + { + "LocusId": "ATXN8OS", + "LocusStructure": "(CTA)*(CTG)*", + "ReferenceRegion": [ + "chr13:70713485-70713515", + "chr13:70713515-70713560" + ], + "VariantType": [ + "Repeat", + "Repeat" + ] + }, + { + "VariantType": "Repeat", + "LocusId": "C9ORF72", + "LocusStructure": "(GGCCCC)*", + "ReferenceRegion": "chr9:27573526-27573544", + "OfftargetRegions": [ + "chr1:150552238-150552273", + "chr11:910821-910969", + "chr11:2905960-2906190", + "chr11:44748502-44748739", + "chr12:54476278-54476439", + "chr12:128751638-128751649", + "chr13:107569867-107570000", + "chr16:819162-819226", + "chr16:49526377-49526380", + "chr18:74203223-74203243", + "chr19:878803-878954", + "chr19:39897404-39897410", + "chr2:240659782-240660010", + "chr20:388475-388656", + "chr20:60640211-60640346", + "chr22:51066608-51066626", + "chr3:49591993-49592173", + "chr3:50161662-50161732", + "chr5:10761494-10761495", + "chr5:176797951-176798133", + "chr6:33601522-33601525", + "chr6:109804703-109804748", + "chr9:132331488-132331496", + "chr9:132428311-132428437", + "chrX:9754276-9754424", + "chrX:49644488-49644585", + "chrX:53652706-53652891", + "chrX:153569931-153569996", + "chrX:153618748-153618918" + ] + }, + { + "VariantType": "Repeat", + "LocusId": "CACNA1A", + "LocusStructure": "(CTG)*", + "ReferenceRegion": "chr19:13318672-13318711" + }, + { + "VariantType": "Repeat", + "LocusId": "CBL", + "LocusStructure": "(CGG)*", + "ReferenceRegion": "chr11:119076999-119077032" + }, + { + "LocusId": "CNBP", + "LocusStructure": "(CAGG)*(CAGA)*(CA)*", + "ReferenceRegion": [ + "chr3:128891419-128891499", + "chr3:128891499-128891539", + "chr3:128891539-128891575" + ], + "VariantType": [ + "Repeat", + "Repeat", + "Repeat" + ] + }, + { + "VariantType": "Repeat", + "LocusId": "CSTB", + "LocusStructure": "(CGCGGGGCGGGG)*", + "ReferenceRegion": "chr21:45196324-45196360" + }, + { + "VariantType": "Repeat", + "LocusId": "DIP2B", + "LocusStructure": "(GGC)*", + "ReferenceRegion": "chr12:50898784-50898805" + }, + { + "VariantType": "Repeat", + "LocusId": "DMPK", + "LocusStructure": "(CAG)*", + "ReferenceRegion": "chr19:46273462-46273522" + }, + { + "VariantType": "RareRepeat", + "LocusId": "FMR1", + "LocusStructure": "(CGG)*", + "ReferenceRegion": "chrX:146993568-146993628", + "OfftargetRegions": [ + "chr10:101295192-101295194", + "chr12:7781290-7781350", + "chr12:125052154-125052156", + "chr16:25703613-25703635", + "chr16:28074516-28074518", + "chr17:30814024-30814026", + "chr17:64298467-64298469", + "chr19:2015524-2015526", + "chr2:87141540-87141618", + "chr2:92230909-92230911", + "chr2:211036020-211036032", + "chr2:225449878-225449880", + "chr20:30865500-30865516", + "chr5:443334-443364", + "chr7:20824939-20824941", + "chr7:100271437-100271439", + "chr7:104654597-104654599", + "chr7:143059853-143059855", + "chr9:100616695-100616697", + "chrX:20009036-20009046" + ] + }, + { + "LocusId": "FXN", + "LocusStructure": "(A)*(GAA)*", + "ReferenceRegion": [ + "chr9:71652177-71652202", + "chr9:71652202-71652220" + ], + "VariantType": [ + "Repeat", + "Repeat" + ] + }, + { + "LocusId": "HTT", + "LocusStructure": "(CAG)*CAACAG(CCG)*", + "ReferenceRegion": [ + "chr4:3076603-3076660", + "chr4:3076666-3076693" + ], + "VariantType": [ + "Repeat", + "Repeat" + ] + }, + { + "VariantType": "Repeat", + "LocusId": "JPH3", + "LocusStructure": "(CTG)*", + "ReferenceRegion": "chr16:87637893-87637935" + }, + { + "VariantType": "Repeat", + "LocusId": "NOP56", + "LocusStructure": "(GGCCTG)*", + "ReferenceRegion": "chr20:2633379-2633403" + }, + { + "VariantType": "Repeat", + "LocusId": "PPP2R2B", + "LocusStructure": "(GCT)*", + "ReferenceRegion": "chr5:146258290-146258320" + }, + { + "VariantType": "Repeat", + "LocusId": "TBP", + "LocusStructure": "(GCA)*", + "ReferenceRegion": "chr6:170870994-170871105" + }, + { + "VariantType": "Repeat", + "LocusId": "TCF4", + "LocusStructure": "(CAG)*", + "ReferenceRegion": "chr18:53253386-53253458" + } +] diff --git a/variant_catalog/variant_catalog_hg38.json b/variant_catalog/variant_catalog_hg38.json new file mode 100755 index 0000000..46c8837 --- /dev/null +++ b/variant_catalog/variant_catalog_hg38.json @@ -0,0 +1,471 @@ +[ + { + "LocusId": "AFF2", + "LocusStructure": "(GCC)*", + "ReferenceRegion": "chrX:148500631-148500691", + "VariantType": "Repeat" + }, + { + "LocusId": "AR", + "LocusStructure": "(GCA)*", + "ReferenceRegion": "chrX:67545316-67545385", + "VariantType": "Repeat" + }, + { + "LocusId": "ATN1", + "LocusStructure": "(CAG)*", + "ReferenceRegion": "chr12:6936716-6936773", + "VariantType": "Repeat" + }, + { + "LocusId": "ATXN10", + "LocusStructure": "(ATTCT)*", + "ReferenceRegion": "chr22:45795354-45795424", + "VariantType": "Repeat" + }, + { + "LocusId": "ATXN1", + "LocusStructure": "(TGC)*", + "ReferenceRegion": "chr6:16327633-16327723", + "VariantType": "Repeat" + }, + { + "LocusId": "ATXN2", + "LocusStructure": "(GCT)*", + "ReferenceRegion": "chr12:111598949-111599018", + "VariantType": "Repeat" + }, + { + "LocusId": "ATXN3", + "LocusStructure": "(GCT)*", + "ReferenceRegion": "chr14:92071009-92071042", + "VariantType": "Repeat" + }, + { + "LocusId": "PHOX2B", + "LocusStructure": "(GCN)*", + "ReferenceRegion": "chr4:41745972-41746032", + "VariantType": "Repeat" + }, + { + "LocusId": "ATXN7", + "LocusStructure": "(GCA)*", + "ReferenceRegion": "chr3:63912684-63912714", + "VariantType": "Repeat" + }, + { + "LocusId": "ATXN8OS", + "LocusStructure": "(CTA)*(CTG)*", + "ReferenceRegion": [ + "chr13:70139353-70139383", + "chr13:70139383-70139428" + ], + "VariantType": [ + "Repeat", + "Repeat" + ] + }, + { + "LocusId": "C9ORF72", + "LocusStructure": "(GGCCCC)*", + "OfftargetRegions": [ + "chr1:8423620-8424123", + "chr1:30908133-30908774", + "chr1:150579390-150580033", + "chr10:930904-931660", + "chr10:24952416-24952917", + "chr10:43304792-43305293", + "chr10:46336798-46337407", + "chr10:46816083-46816694", + "chr10:100999603-101000210", + "chr11:910565-911217", + "chr11:2884462-2885264", + "chr11:44726686-44727485", + "chr11:67372279-67372872", + "chr12:459654-460155", + "chr12:54082197-54082983", + "chr12:128266836-128267355", + "chr13:20987998-20988595", + "chr13:25468389-25469012", + "chr13:106917207-106917872", + "chr14:32938886-32939513", + "chr14:105679947-105680448", + "chr15:30223043-30223669", + "chr16:768799-769576", + "chr16:1614059-1614704", + "chr16:3134791-3135352", + "chr16:87602166-87602667", + "chr17:10198365-10198994", + "chr17:68205352-68205991", + "chr17:81891215-81891716", + "chr18:54223897-54224704", + "chr19:613299-613854", + "chr19:878542-879249", + "chr19:2808202-2808704", + "chr19:17947549-17948080", + "chr19:39406514-39407140", + "chr19:50568087-50568733", + "chr19:52296834-52297335", + "chr2:3592013-3592538", + "chr2:25129074-25129575", + "chr2:168247028-168247668", + "chr2:234496868-234497369", + "chr2:234951698-234952200", + "chr2:236237107-236237609", + "chr2:239737824-239738572", + "chr20:407681-408343", + "chr20:62064993-62065670", + "chr21:45286831-45287440", + "chr22:39994753-39995382", + "chr22:49117029-49117530", + "chr22:50627810-50628453", + "chr3:46845815-46846372", + "chr3:47578060-47578713", + "chr3:49554283-49555050", + "chr3:50124039-50124697", + "chr3:51706744-51707245", + "chr3:128497689-128498194", + "chr3:184379811-184380428", + "chr4:151408887-151409388", + "chr5:10761008-10761635", + "chr5:146458824-146459326", + "chr5:172454307-172454808", + "chr5:177370694-177371467", + "chr6:33633374-33634093", + "chr6:43021418-43022045", + "chr6:46490762-46491263", + "chr6:109483278-109483938", + "chr8:141308350-141308989", + "chr9:83817587-83818088", + "chr9:129568833-129569572", + "chr9:129665778-129666418", + "chrX:9785974-9786751", + "chrX:49879636-49880326", + "chrX:53625582-53626227", + "chrX:154341221-154341987", + "chrX:154390157-154390883" + ], + "ReferenceRegion": "chr9:27573528-27573546", + "VariantType": "Repeat" + }, + { + "LocusId": "CACNA1A", + "LocusStructure": "(CTG)*", + "ReferenceRegion": "chr19:13207858-13207897", + "VariantType": "Repeat" + }, + { + "LocusId": "CBL", + "LocusStructure": "(CGG)*", + "ReferenceRegion": "chr11:119206289-119206322", + "VariantType": "Repeat" + }, + { + "LocusId": "CNBP", + "LocusStructure": "(CAGG)*(CAGA)*(CA)*", + "ReferenceRegion": [ + "chr3:129172576-129172656", + "chr3:129172656-129172696", + "chr3:129172696-129172732" + ], + "VariantType": [ + "Repeat", + "Repeat", + "Repeat" + ] + }, + { + "LocusId": "CSTB", + "LocusStructure": "(CGCGGGGCGGGG)*", + "ReferenceRegion": "chr21:43776443-43776479", + "VariantType": "Repeat" + }, + { + "LocusId": "DIP2B", + "LocusStructure": "(GGC)*", + "ReferenceRegion": "chr12:50505001-50505022", + "VariantType": "Repeat" + }, + { + "LocusId": "DMPK", + "LocusStructure": "(CAG)*", + "ReferenceRegion": "chr19:45770204-45770264", + "VariantType": "Repeat" + }, + { + "LocusId": "FMR1", + "LocusStructure": "(CGG)*", + "OfftargetRegions": [ + "chr1:3068800-3069410", + "chr1:21176191-21176850", + "chr1:22142746-22143526", + "chr1:26695803-26696436", + "chr1:32964053-32964646", + "chr1:38046253-38047002", + "chr1:43979082-43979731", + "chr1:50418725-50419472", + "chr1:53738908-53739574", + "chr1:108559853-108560513", + "chr1:120069049-120069669", + "chr1:120723873-120724492", + "chr1:146228424-146229045", + "chr1:149390544-149391206", + "chr1:154558232-154558862", + "chr1:166166596-166167097", + "chr1:208244048-208244696", + "chr1:244048773-244049274", + "chr1:246931114-246931739", + "chr10:15370499-15371495", + "chr10:50991053-50991697", + "chr10:75400963-75401593", + "chr10:96586506-96587141", + "chr10:99535056-99535844", + "chr10:131900767-131901422", + "chr10:132396901-132397563", + "chr11:535030-535612", + "chr11:13010216-13010825", + "chr11:44309365-44309993", + "chr11:119206036-119206672", + "chr12:1593780-1594552", + "chr12:32399142-32399783", + "chr12:45729325-45729915", + "chr12:55743146-55743788", + "chr12:64404090-64404725", + "chr12:69239354-69239905", + "chr12:112381709-112382586", + "chr12:123533342-123533981", + "chr13:36919925-36920587", + "chr14:28767200-28767848", + "chr14:33800220-33800846", + "chr14:67515148-67515807", + "chr14:69572823-69573477", + "chr14:102592572-102593302", + "chr15:24847862-24848492", + "chr15:51737291-51737920", + "chr15:88256090-88256591", + "chr16:2178673-2179293", + "chr16:24729306-24730163", + "chr16:25691935-25692619", + "chr16:28062828-28063460", + "chr16:49855133-49855768", + "chr16:55478902-55479533", + "chr16:73058438-73059091", + "chr16:79598817-79599460", + "chr16:85168882-85169517", + "chr16:88570363-88570870", + "chr17:2593208-2593916", + "chr17:7885051-7885670", + "chr17:26774258-26774914", + "chr17:28644585-28645198", + "chr17:44219714-44220215", + "chr17:45241687-45242324", + "chr17:47693725-47694343", + "chr17:62065114-62065757", + "chr17:66301954-66302705", + "chr17:75588817-75589447", + "chr18:5891121-5891766", + "chr18:12896734-12897356", + "chr18:28176796-28177449", + "chr18:31942935-31943436", + "chr18:33578203-33578845", + "chr18:47246692-47247323", + "chr18:57435463-57436097", + "chr19:1285831-1286476", + "chr19:1407366-1408019", + "chr19:1567732-1568394", + "chr19:2015163-2015946", + "chr19:2163771-2164421", + "chr19:2307770-2308540", + "chr19:14495661-14496299", + "chr19:15332243-15332867", + "chr19:19406196-19406820", + "chr19:33796448-33797068", + "chr19:36140743-36141396", + "chr19:36309135-36309673", + "chr19:42116612-42117226", + "chr19:42241986-42242612", + "chr19:42253491-42254110", + "chr19:45496698-45497341", + "chr19:45972993-45973745", + "chr19:49072290-49072904", + "chr2:37156530-37157166", + "chr2:47905271-47905864", + "chr2:50346524-50347267", + "chr2:72148267-72148905", + "chr2:73269201-73269839", + "chr2:85133319-85133967", + "chr2:86914035-86915119", + "chr2:90324439-90325070", + "chr2:90325128-90325838", + "chr2:91809550-91810294", + "chr2:92042460-92043192", + "chr2:104855035-104856469", + "chr2:109760018-109760924", + "chr2:109794204-109794856", + "chr2:110610684-110611442", + "chr2:117870706-117871322", + "chr2:160493196-160493697", + "chr2:202376257-202376900", + "chr2:210170918-210171567", + "chr2:219627269-219627917", + "chr2:224584787-224585433", + "chr2:231037450-231038211", + "chr20:10034651-10035260", + "chr20:32277442-32278091", + "chr20:35226390-35227019", + "chr20:57709537-57710167", + "chr21:15730043-15730612", + "chr21:31732025-31732607", + "chr21:33554862-33555470", + "chr21:34614575-34615198", + "chr21:37072924-37073548", + "chr21:42218961-42219628", + "chr22:20131541-20132067", + "chr22:20858295-20858923", + "chr22:28883458-28884052", + "chr22:37983424-37984043", + "chr22:47379027-47379724", + "chr22:50474703-50475344", + "chr3:12287534-12288151", + "chr3:15859307-15859969", + "chr3:18425089-18425592", + "chr3:19947012-19947631", + "chr3:28575591-28576219", + "chr3:51390995-51391577", + "chr3:129605015-129605681", + "chr3:158105430-158106067", + "chr3:177197309-177197932", + "chr3:194583636-194584137", + "chr4:3074688-3075409", + "chr4:15003613-15004273", + "chr4:20251745-20252399", + "chr4:74932808-74933428", + "chr4:104491108-104491609", + "chr4:140153857-140154359", + "chr4:146639045-146639684", + "chr5:442841-443512", + "chr5:6448350-6449058", + "chr5:14144642-14145253", + "chr5:45695838-45696469", + "chr5:61332420-61333055", + "chr5:93583955-93584562", + "chr5:108380749-108381400", + "chr5:134371096-134371734", + "chr5:141878162-141878822", + "chr5:171419845-171420495", + "chr5:177554234-177554890", + "chr5:180291708-180292348", + "chr6:1389780-1390413", + "chr6:7107296-7107932", + "chr6:13711134-13711716", + "chr6:45422373-45423021", + "chr6:46490940-46491586", + "chr6:98834721-98835357", + "chr6:114342166-114342817", + "chr6:116680740-116681370", + "chr6:117906804-117907451", + "chr6:149317153-149317781", + "chr6:149317785-149318327", + "chr6:150865347-150865972", + "chr6:156778590-156779239", + "chr7:18086648-18087297", + "chr7:20785040-20785780", + "chr7:27173496-27174130", + "chr7:32891624-32892251", + "chr7:42237599-42238236", + "chr7:50793142-50793767", + "chr7:55887234-55888138", + "chr7:63114054-63114797", + "chr7:64181749-64182417", + "chr7:75092521-75093173", + "chr7:77536829-77537470", + "chr7:97005856-97006507", + "chr7:99143640-99144272", + "chr7:100673442-100674084", + "chr7:100693980-100694615", + "chr7:105013776-105014419", + "chr7:143362396-143363025", + "chr7:151086293-151086936", + "chr8:37698038-37698670", + "chr8:60680068-60680652", + "chr8:65841587-65842212", + "chr8:88327245-88327873", + "chr8:104588708-104589361", + "chr8:139702696-139703331", + "chr8:144461991-144462624", + "chr9:27572828-27573455", + "chr9:34957849-34958490", + "chr9:63450116-63450878", + "chr9:77019890-77020661", + "chr9:97854152-97854814", + "chr9:120714014-120714592", + "chr9:122126507-122127093", + "chr9:124869204-124869705", + "chr9:128841198-128841699", + "chr9:130579253-130579892", + "chrX:19990567-19991309", + "chrX:21374362-21374866", + "chrX:48957464-48958121", + "chrX:68912863-68913485", + "chrX:100409331-100409962", + "chrX:126165699-126166354", + "chrX:150983148-150983796" + ], + "ReferenceRegion": "chrX:147912050-147912110", + "VariantType": "RareRepeat" + }, + { + "LocusId": "FXN", + "LocusStructure": "(A)*(GAA)*", + "ReferenceRegion": [ + "chr9:69037261-69037286", + "chr9:69037286-69037304" + ], + "VariantType": [ + "Repeat", + "Repeat" + ] + }, + { + "LocusId": "HTT", + "LocusStructure": "(CAG)*CAACAG(CCG)*", + "ReferenceRegion": [ + "chr4:3074876-3074933", + "chr4:3074939-3074966" + ], + "VariantType": [ + "Repeat", + "Repeat" + ] + }, + { + "LocusId": "JPH3", + "LocusStructure": "(CTG)*", + "ReferenceRegion": "chr16:87604287-87604329", + "VariantType": "Repeat" + }, + { + "LocusId": "NOP56", + "LocusStructure": "(GGCCTG)*", + "ReferenceRegion": "chr20:2652733-2652757", + "VariantType": "Repeat" + }, + { + "LocusId": "PPP2R2B", + "LocusStructure": "(GCT)*", + "ReferenceRegion": "chr5:146878727-146878757", + "VariantType": "Repeat" + }, + { + "LocusId": "TBP", + "LocusStructure": "(GCA)*", + "ReferenceRegion": "chr6:170561906-170562017", + "VariantType": "Repeat" + }, + { + "LocusId": "TCF4", + "LocusStructure": "(CAG)*", + "ReferenceRegion": "chr18:55586155-55586227", + "VariantType": "Repeat" + } +] From 34502c84bc6d3206a62be83841ff26a1e12dfd7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anne-Sophie=20Denomm=C3=A9-Pichon?= Date: Sun, 16 Dec 2018 04:10:58 +0100 Subject: [PATCH 2/2] Fix variant catalog file link --- docs/03_Usage.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/03_Usage.md b/docs/03_Usage.md index 2dd89d6..28e059e 100755 --- a/docs/03_Usage.md +++ b/docs/03_Usage.md @@ -3,7 +3,7 @@ Expansion Hunter requires an indexed BAM or a CRAM file containing aligned reads from a PCR-free WGS sample, a FASTA file with a reference genome assembly (which must be the same as the one used to align the reads), and a [variant catalog -file](04_VariantCatalogs.md). +file](04_VariantCatalogFiles.md). Expansion Hunter outputs a VCF file and a JSON file with repeat genotypes along with other useful information and a log file with alignments of spanning and @@ -33,4 +33,4 @@ optional arguments. to search for informative reads. Set to 1000 by default. Note that the full list of program options with brief explanations can be -obtained by running `ExpansionHunter --help`. \ No newline at end of file +obtained by running `ExpansionHunter --help`.

    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�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%0D$}6s69L0J38(?J3M&v8c&xQOru2I!f-LwM6t$I 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