Commit e5ab254
Promote consensus mass-tracing types + add Ms1Feature writer for MassFeature (#1069)
* add new protease subtilisin
* feat: add subtilisin|p protease and unit test
Add subtilisin|p to the embedded proteases.tsv with full cleavage
specificity and proline-inhibition motifs (N[P]|, S[P]|, L[P]|,
K[P]|, I[P]|, D[P]|, Y[P]|, V[P]|, G[P]|, F[P]|, T[P]|, E[P]|,
Q[P]|, A[P]|, R[P]|).
Add TestSubtilisinP_DigestsCorrectlyAndRespectsProlineRestriction to
ProteinDigestionTests to verify:
- subtilisin|p is present in the embedded protease dictionary with
CleavageSpecificity.Full
- All expected cleavage sites fire on a proline-free sequence (ANKTIDE)
- The K[P]| proline-inhibition rule is respected (AKPIDE keeps KP intact)
* Accept newer-TopFD column names in Ms1Feature
PR 1064 ships an Ms1Feature reader that recognises only the older
FlashDeconv / TopFD-v1.6.2 _ms1.feature schema (Sample_ID, ID,
Time_begin, Time_end, Minimum_charge_state, Maximum_charge_state,
Minimum_fraction_id, Maximum_fraction_id). Newer TopFD output keeps
the same _ms1.feature extension but uses different column names
(File_name, Fraction_ID, Feature_ID, Min_time, Max_time, Min_charge,
Max_charge), plus extras like Envelope_num and EC_score. Format
detection picks Ms1FeatureFile by extension; CsvHelper then throws
because none of the expected [Name(...)] columns are present.
Discovered while integrating PR 1064's FromFileDeconvolutionParameters
into MetaMorpheus and pointing it at a real TopFD .ms1.feature from a
top-down yeast run -- the file parsed by hand looks identical in shape
to the old schema, just relabelled.
Fix is column-name aliases on the existing record, plus [Optional] on
fields that the newer schema omits entirely. Downstream
GetSingleChargeFeatures() reads only Mass, ChargeStateMin/Max,
RetentionTimeBegin/End, and IntensityApex -- all aliased to a column
present in both schemas, so the join algorithm behaves identically
regardless of which producer wrote the file.
Per-field summary:
Sample_ID -> [Optional] (newer TopFD has File_name
instead; type-incompatible
-- int vs string path --
and not used downstream)
ID -> alias "Feature_ID" + [Optional]
Time_begin -> alias "Min_time"
Time_end -> alias "Max_time"
Minimum_charge_state -> alias "Min_charge"
Maximum_charge_state -> alias "Max_charge"
Minimum_fraction_id -> [Optional] (newer TopFD has a single
Fraction_ID column, not a
min/max pair; not used
downstream so alias would
add no value)
Maximum_fraction_id -> [Optional]
No tests added in this commit -- a follow-up should drop a newer-TopFD
_ms1.feature sample into Test/FileReadingTests/ExternalFileTypes/ and
extend the existing Ms1FeatureFile read-roundtrip tests to cover both
schemas. The current
Ms1Feature_FlashDeconvOpenMs3.0.0_ms1.feature
Ms1Feature_TopFDv1.6.2_ms1.feature
fixtures keep passing because the existing [Name(...)] heads remain
the first entry in every alias list.
* Drops a 4-row fixture (Ms1Feature_TopFDvLatest_ms1.feature) captured
from real TopFD output that uses the File_name / Fraction_ID /
Feature_ID / Min_time / Max_time / Min_charge / Max_charge schema, and
wires it into the existing TestMsFeature parameterised tests:
* TestFeaturesLoadAndCountIsCorrect gains a TestCase asserting the
fixture loads four features end-to-end via FileReader.
* TestTopFDLatestMs1FeatureFirstAndLastAreCorrect locks the per-
field mapping for both the aliased columns (Time_begin/Min_time,
Minimum_charge_state/Min_charge, etc.) and the [Optional] fields
absent from the newer schema (SampleId, FractionIdMin/Max default
to 0). Covers the single-charge edge case (Min_charge == Max_charge
== 1) in the last record.
* TestTopFDLatestMs1GetSingleChargeFeatureFunctions confirms charge-
range expansion is identical across the two TopFD schemas: a 6-14
range yields 9 envelopes; a 1-1 range yields exactly one;
GetMs1Features() flattens to 9 + 12 + 10 + 1 = 32 across the four
fixture features.
* TestMs1FeatureReadWrite gains the new fixture as a TestCase. The
writer emits the older-schema headers (newer columns aren't on
the record), so the round-trip converts schema-newer -> schema-
older + Optional defaults. Comment in the test explains why that
is correct: every field downstream consumers actually read
survives the round-trip; the columns that don't are exactly the
ones marked [Optional] and unused.
All 17 TestMsFeature tests pass, as do the 148 tests covering the
related Ms1Feature / FromFile / SupportedFileExtensions / DinosaurTsv
surface area.
* Promote consensus mass-tracing types out of Development
Move the post-decon consensus pipeline (per-charge trace grouping,
median off-by-one correction, cross-charge feature stitching) from
Development.Deconvolution.ConsensusTracing into a new production
namespace MassSpectrometry.Deconvolution.Consensus. The types had
been parked in Development as the research arc explored their design
(see NOTES.md, phases 1-13); promotion was flagged as the next move
once the design stabilised.
New files under mzLib/MassSpectrometry/Deconvolution/Consensus/:
* MassTrace -- per-scan envelope list at one charge, anchored to
the first envelope's mass.
* MassTraceBuilder -- greedy charge-locked grouper, gap-tolerant
within a configurable mass tolerance.
* CorrectedEnvelope -- one envelope inside a corrected trace,
carrying both original and post-correction mass plus a
WasCorrected flag.
* CorrectedTrace -- a MassTrace after weighted-median off-by-one
correction.
* TraceCorrector -- the corrector itself, with uniform/intensity/
scorer-weighted variants via an EnvelopeWeight delegate.
* MassFeature -- a cross-charge feature: a group of CorrectedTrace
entries whose consensus masses agree within a ppm tolerance and
whose RT windows overlap.
* MassFeatureBuilder -- the union-find sweep that produces them.
No behaviour change. The classes, methods, and signatures are
identical to the research-namespace versions; only their namespace
and file locations changed (each type now lives in its own file).
Doc comments updated to drop references to phase numbers and to the
"Development" location.
The research scaffolding in Development.Deconvolution.ConsensusTracing
(Phase*.cs fixtures, MakeConsensusSnips, LargeTestDataLocator) stays on
the consensus-tracing branch; it will be updated to consume the new
namespace when that branch is rebased on top of this PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add Ms1FeatureFile.FromMassFeatures factory
Lets the consensus-tracing pipeline persist its cross-charge feature
list as a FLASHDeconv-style _ms1.feature file -- the existing wire
format any Ms1FeatureFile consumer (notably MetaMorpheus's
FromFileDeconvolutionParameters) already understands. The
consensus-to-search bridge then needs no new file format and no new
MetaMorpheus surface area.
Two pieces:
* MassFeature.ToMs1Feature(sequentialId, sampleId = 0,
fractionId = 0) extension method (under Readers, because the
return type is the Readers-namespace Ms1Feature record). Maps:
Mass <- MassFeature.ConsensusMass
Intensity <- MassFeature.SummedIntensity
RetentionTimeBegin <- MassFeature.RTStart
RetentionTimeEnd <- MassFeature.RTEnd
RetentionTimeApex <- RT of the highest-intensity envelope
on the highest-summed-intensity
constituent trace ("apex of the
dominant charge state at its most
intense scan")
IntensityApex <- intensity of that same envelope
ChargeStateMin/Max <- min/max of MassFeature.Charges
SampleId / FractionId <- callers' choice (defaults 0)
* Static Ms1FeatureFile.FromMassFeatures(IEnumerable<MassFeature>,
sampleId, fractionId, software) factory. Builds the records via
ToMs1Feature, populates a fresh Ms1FeatureFile instance, and
returns it. The caller invokes .WriteResults(path) on the
returned file to actually emit bytes -- no new IO path, the
existing reader's writer is reused. Software label defaults to
FLASHDeconv because that's the canonical schema the writer
emits; the [Name(...)] alias machinery from PR #1064 +
schema-aliases PR means newer-TopFD readers also see the
output as valid.
No filter applied: every MassFeature becomes one row. The original
question surfaced four filtering options (all, multi-charge only,
score-thresholded, all + side artifact); per the design pin "all
features, no filter" is the right default. Downstream consumers
(MetaMorpheus's precursor HashSet + FDR machinery) handle the noise.
The writer is decoupled from the producer: callers pass a sequence
of finalised MassFeature objects; the factory doesn't care where
they came from. Future producers (mass-trace results from non-
Classic algorithms, etc.) can use the same path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Cover Ms1FeatureFile.FromMassFeatures with a round-trip test
Five NUnit cases under
Test\FileReadingTests\ExternalFileReading\TestMs1FeatureFromMassFeature.cs:
* ToMs1Feature_SingletonFeature_MapsAllFields -- one trace, one
envelope. Locks the mapping: Mass, Intensity, RT begin/end/apex,
apex intensity, charge bounds, fraction id all come from
Finalise()-derived MassFeature fields.
* ToMs1Feature_MultiChargeFeature_ApexIsDominantTraceMaxEnvelope --
two-charge feature with five envelopes. Confirms apex is "highest-
intensity envelope on the highest-summed-intensity constituent
trace", and that cross-charge consensus mass is the intensity-
weighted mean of per-trace consensus masses.
* ToMs1Feature_SampleIdAndFractionIdHonoured -- callers can override
Sample_ID and Fraction_ID for multi-file/multi-fraction outputs.
* FromMassFeatures_WriteThenRead_AllFieldsSurvive -- three features
built in-memory, written via WriteResults, re-read with
FileReader.ReadFile<Ms1FeatureFile>, every downstream-consumed
field compared to the original ToMs1Feature output. Catches any
serialisation drift between writer and reader on the new path.
* FromMassFeatures_AssignsSequentialIdsFromZero -- producer's
internal MassFeature.Id is ignored; written rows get 0..N-1. Keeps
the output file's IDs dense and stable even when the upstream
pipeline has filtered features and left holes.
Fix discovered while writing the second-to-last test: the original
FromMassFeatures factory used `file.Results.Add(...)` in a loop, which
goes through ResultFile<T>.Results' lazy-load getter. The getter calls
LoadResults() whenever the backing list is empty; for an in-memory-
constructed Ms1FeatureFile with no FilePath, LoadResults throws
ArgumentException on the empty path. Reshaped the factory to build
the records up front, then assign Results once via the setter (which
bypasses the lazy-load). Inline comment explains why.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add [Explicit] end-to-end driver from mzML to _ms1.feature
Demonstrates the consensus-to-search loop the rest of this PR was
built for: load an mzML, run Classic decon per MS1 scan with top-down
parameters (charge 1..60), build mass traces with Pass-B grouping
(loose 1.5 Da to cohort off-by-one twins), apply weighted-median
correction, stitch cross-charge features at 10 ppm, write the
resulting MassFeature list as a FLASHDeconv-style _ms1.feature via
the new Ms1FeatureFile.FromMassFeatures factory, and round-trip read
the written file as a sanity check.
Lives as an NUnit [Explicit] test because it consumes a 165 MB local
mzML at a hard-coded path that's not part of the repo or CI:
E:\TestData\MetaMorpheus\05-26-17_B7A_yeast_td_fract7_rep1.mzML
Assume.That on File.Exists keeps it silently skipped on machines
without the data. Run with:
dotnet test --filter FullyQualifiedName~TestConsensusToMs1FeatureEndToEnd
On the yeast top-down test file (4837 scans total, ~2700 MS1) the
driver writes a 24 MB _ms1.feature with 266,373 features. That file,
fed to MetaMorpheus's FromFile precursor path (#2650), yields a
Cal -> GPTMD -> Search pipeline that converges to a tighter precursor
mass tolerance (1.6 ppm) than the equivalent classic-only baseline
(2.4 ppm) and finds 907 PSMs at 1% FDR vs 785 -- a 15.5% increase in
identifications from consensus precursors alone, before any additive
combination with the classic source.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Parameterise consensus driver via TestCaseSource
Lets a single dotnet test invocation run the consensus pipeline over
multiple raw/mzML files in succession. Each case captures its own
source path, output path, and decon charge ceiling -- bottom-up uses
MaxCharge=12 (matching MetaMorpheus's standard BU default), top-down
would use 60.
Currently shipping two bottom-up HEK293 Velos raws as the parameterised
test cases:
* 20100609_Velos1_TaGe_SA_293_3.raw (5163 MS1 scans, 76878 features
after correction, 7756 multi-charge -- 8.9 s wall time)
* 20100609_Velos1_TaGe_SA_293_4.raw (5111 MS1 scans, 74173 features,
7318 multi-charge -- 5.7 s)
Each test case still gates on File.Exists via Assume.That; cases for
machines without the data skip silently.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Normalise feature-file RT to minutes when loading
FlashDeconv and TopFD both canonically emit _ms1.feature files with
RetentionTime columns in seconds (e.g. Time_begin = 2787 for a 46-min
LC run). MetaMorpheus and mzLib's MsDataScan.RetentionTime are always
in minutes (mzML / Thermo convention). FromFileDeconvolutionAlgorithm
compared the file's RT directly against the scan's RT, so on any real
FlashDeconv or TopFD output the seconds-vs-minutes mismatch produced
zero overlapping windows -- and zero PSMs at search time.
Caught while running a Cal -> GPTMD -> Search pipeline over a 20-mzML
Jurkat top-down dataset using its FlashDeconv companion _ms1.feature
files. Classic-only produced 23,636 PSMs at 1% FDR; FromFile-only
produced 0 with no error or warning -- silent failure mode.
Fix: in the file-path FromFileDeconvolutionParameters constructor,
detect seconds-as-loaded via a max-RetentionTimeEnd > 500 heuristic
(no realistic LC run exceeds 8 hours = 500 min) and divide all RT
fields by 60. Files already in minutes pass through unchanged. The
internal pre-loaded-features constructor (test seam) is unaffected
so callers supplying explicit synthetic units still get raw values.
Verification: re-running the same Jurkat pipeline after the fix
yields 23,976 PSMs in FromFile-only mode and 39,700 PSMs in additive
Both mode (vs Classic-only's 23,636) -- +68% PSMs / +68% proteoforms
over the Classic baseline on this dataset.
Tests:
* Existing EndToEnd_FilePathCtor_ResolvesExpectedChargeState
updated to use MS2 RT in minutes (39.85) instead of seconds
(2390.0) -- the fixtures are FlashDeconv / TopFD with seconds-RT
and the fix now normalises them at load, so the test had to
match the new units.
* New FromFileDeconvolutionParameters_FileWithRtInSeconds_NormalisesToMinutes
writes a synthetic seconds-RT file, loads via the file-path
ctor, asserts every per-charge feature's RT lands in the
expected minutes range (~39.83 / ~40.17), and pairs against an
in-minutes MS2 to confirm overlap is found.
* New FromFileDeconvolutionParameters_FileWithRtInMinutes_LeavesUnchanged
sister test: file already in minutes (max RT < 500) is NOT
double-converted.
31/31 tests in TestFromFileDeconvolution + TestMs1FeatureFromMassFeature
pass after the fix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(decon): split resolvable co-grouped species in trace correction
TraceCorrector now derives its off-by-one window from per-trace mass
scatter (3*sigma via MAD, capped below half the isotope spacing) instead
of a fixed +/-0.05 Da. An envelope at a resolvable, non-isotope offset
(e.g. a deamidated form at +0.98402 Da, which the old fixed window snapped
to the unmodified mass) is split into its own CorrectedTrace and surfaced
as a distinct feature; when scatter is too large to resolve it from the
1.00335 Da isotope spacing it conservatively merges. Correct() now returns
List<CorrectedTrace>. Bottom-up search needs this -- deamidation detection
is a requirement there.
Also in this hardening pass:
- MassFeatureBuilder sorts components by (mass, charge, RT) before
assigning IDs, so feature IDs and written _ms1.feature row order are
deterministic instead of depending on Dictionary enumeration.
- Guard clauses with clear messages on Finalise (empty traces),
ToMs1Feature (un-finalised feature), and BuildTraces (scan/envelope
length mismatch).
- FromFileDeconvolutionParameters exposes RetentionTimeNormalizedFromSeconds
and warns when the seconds->minutes heuristic fires, rather than silently
mutating RT.
- Unit + performance tests for the consensus engines (0 -> 100% line
coverage on the promoted types) covering the behaviours above.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(decon): add TD/BU feature generators + per-feature decon scoring
[Explicit], local-data drivers used to benchmark the consensus pipeline:
JurkatMs1FeatureGenerator and VelosMs1FeatureGenerator regenerate the
_ms1.feature files for the Jurkat top-down (charge 1..60) and HEK293 Velos
bottom-up (charge 1..12) datasets via the new deamidation-split pipeline.
ConsensusFeatureScoring computes the generic per-envelope deconvolution
score (DeconvolutionScorer.ScoreEnvelope: averagine cosine, ppm error, peak
completeness, intensity-ratio consistency) for every Classic envelope and
aggregates it per feature into a sidecar TSV aligned to the _ms1.feature
row order. This gives a uniform, search-independent feature-quality value
for a computed noise-filter cutoff -- scored at generation time because the
consensus pipeline keeps only mass+intensity per envelope and drops the
peaks the scorer needs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(readers): preserve gapped charge sets on _ms1.feature round-trip
Hardening fixes to the consensus MassFeature -> _ms1.feature path surfaced by
code review. None were observed in normal use, but each is a real edge-case
correctness or crash issue:
- Write one row per contiguous charge run so a gapped set (e.g. {10,12,15},
when intermediate charges fall below the score cutoff) no longer reads back
as the fabricated full range 10..15. Adds ToMs1Features.
- FromMassFeatures: empty input writes a valid header-only file instead of
crashing; also fixes a LoadResults infinite recursion when reading a
zero-row file (the Results getter re-entered LoadResults on an empty set).
- Ignore non-finite RetentionTimeEnd when sniffing seconds-vs-minutes, so a
single NaN row can't flip an in-minutes file into a /60 conversion.
- Default the produced file's Software to TopFD to match how it is re-detected
on reload (the writer always emits Apex_intensity); fix the misleading
"FLASHDeconv canonical" doc.
- Document that per-charge Intensity comes from Apex_intensity, not the summed
Intensity column.
Adds NUnit coverage for each; tidies now-stale FLASHDeconv comments.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(consensus): use Constants.C13MinusC12 for isotope spacing
TraceCorrector defined its own IsotopeSpacingDa = 1.00335, a less-precise copy of the canonical Chemistry.Constants.C13MinusC12 (1.00335483810). Alias the shared constant so the C12/C13 spacing has one source of truth; the 4.8e-6 Da change is far inside the off-by-one detection window, so results are unchanged.
Addresses review feedback on #1069.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(readers): only lazy-load Results when the file exists
ResultFile<T>.Results lazy-loaded whenever _results was empty, which crashed for a factory-built file with an empty FilePath (e.g. a zero-feature Ms1FeatureFile.FromMassFeatures). Guarding the load with File.Exists(FilePath) lets an in-memory file return its set records directly, removing the need for Ms1FeatureFile's parallel _factoryRecords store so the records have a single source of truth.
Addresses review feedback on #1069.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(consensus): add XIC-vs-MassTrace grouping comparison harness
Two [Explicit] drivers (local Jurkat top-down data, not CI) that back the decision to keep MassTraceBuilder over reusing the existing GetAllXics: TestXicVsMassTraceComparison times both groupers head-to-head on the same deconvoluted envelopes (wall-clock, allocations, trace counts, grouping agreement); JurkatMs1FeatureGeneratorViaXic emits XIC-grouped _ms1.feature files through the identical downstream so end-to-end proteoform yield can be compared (GetAllXics measured -18% vs MassTraceBuilder).
Refs #1069.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add consensus pipeline hierarchy overview to MassFeature doc comment
Defines how IsotopicEnvelope, MassTrace, CorrectedTrace, and MassFeature
nest and which builder produces each level, per review request.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(rt): compare duplicate-peptide predictions within tolerance, not bit-exactly
PredictRetentionTimeEquivalents_DuplicatePeptides_ReturnsSameValueForEach
asserted exact equality via Distinct().Count() == 1. The Chronologer batch path
runs the duplicate peptides as identical rows in a single libtorch forward pass,
whose CPU conv/matmul kernels are not bit-reproducible across thread counts / CI
runners, so identical rows can differ in the last float32 bits (intermittent CI
failure: distinct == 2). The values are correct to float32 precision — the rest
of this suite already pins batch agreement with .Within(1e-4).
Assert every duplicate prediction is non-null and equal to the first within
1e-4, matching the suite's existing tolerance. Production code unchanged; the
real heap-corruption non-determinism was already fixed in #1075.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011QVxqemPqC9BjvYtVg6aKY
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: MICHAEL SHORTREED <mrshortreed@wisc.edu>1 parent 125c995 commit e5ab254
23 files changed
Lines changed: 2823 additions & 9 deletions
File tree
- mzLib
- MassSpectrometry/Deconvolution/Consensus
- Readers
- BaseClasses
- Deconvolution
- ExternalResults
- IndividualResultRecords
- ResultFiles
- Test
- FileReadingTests/ExternalFileReading
- MassSpectrometryTests
- Deconvolution
- RetentionTimePrediction
Lines changed: 21 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
Lines changed: 29 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
Lines changed: 96 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
Lines changed: 101 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
Lines changed: 37 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
Lines changed: 95 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
0 commit comments