Project Overview
As a sequencing company, we frequently received client-provided index information for post-sequencing sample demultiplexing. However, these indexes were often problematic. Indexes from different samples within the same lane could be highly similar or even duplicated, leading to demultiplexing failures or reduced confidence in sample assignment. Previously, we manually inspected indexes to avoid duplication and used conservative demultiplexing parameters that allowed only minimal index mismatches. However, this approach became increasingly labor-intensive when processing multiple lanes containing samples from different clients. The situation was further complicated by index information being transferred through sales teams, where errors could occur during communication or data handling. To address this issue, I designed an algorithm to:
- Automatically detect duplicated or highly similar sample indexes, reducing index-related errors to ~0% and improving inspection efficiency by ~98%.
- Determine the maximum safe mismatch allowance between indexes, improving demultiplexing yield by ~2%.
Version 1
The first version of the algorithm was designed to calculate the distance between every pair of index sequences.
- Assumption: Index mismatch is only allowed for ONE of the paired index.
Goal
maximum safe mismatch allowance (MSMA) →
How many mismatches can be allowed before one index becomes indistinguishable from another exact index sequence?
Case 1: Indexes with the same length
seq1: ATGGC
seq2: AGCGC
To convert seq1 into seq2, two positions need to be changed:
seq1: ATGGC
seq2: AGCGC
^ ^
Therefore, the distance between seq1 and seq2 is 2 mismatches.
For seq1, this means that allowing 1 mismatch would still prevent it from becoming exactly identical to seq2. In other words:
MSMA = distance - 1 = 2 - 1
= 1
MSMA = 1
Case 2: Indexes with different lengths
If two index sequences have different lengths:
seq1: AGT
seq2: AGCGC
To compare them, the algorithm pads seq1 with N in all possible positions until it has the same length as seq2 :
seq1-1: NNAGT
seq1-2: NAGTN
seq1-3: AGTNN
seq2: AGCGC
Each padded version is then compared with seq2.
seq1-1: NNAGT
seq2: AGCGC
-> requires 4 mismatches to become seq2
seq1-2: NAGTN
seq2: AGCGC
-> requires 5 mismatches to become seq2
seq1-3: AGTNN
seq2: AGCGC
-> requires 3 mismatches to become seq2
minimum padded distance = 3
However, because 2 artificial N bases were inserted, these positions should not be counted as real mismatches from the original index sequence.
adjusted distance = minimum padded distance - number of inserted N bases
= 3 - 2
= 1
MSMA = adjusted distance - 1 = 1 - 1
= 0
Version 2: Symmetric Mismatch Collision Detection
Goal
The first version of the algorithm had one important limitation: it assumed that only one index sequence could contain mismatches.
The problem is that seq2 can also contain mismatches during demultiplexing. This means that seq1 and seq2 do not need to become each other directly.
Instead, they only need to become the same intermediate sequence.
seq1: ATGGC
seq2: AGCGC
A possible intermediate sequence is:
collision sequence: AGGGC
Then
seq1: ATGGC
AGGGC
^
1 mismatch
seq2: AGCGC
AGGGC
^
1 mismatch
Therefore the formula in version 1 IS NOT ALWAYS TRUE
MSMA = adjusted distance - 1
New Concept: Collision Sequence
- A collision sequence is an intermediate sequence that both indexes could become after mismatches.
x = number of mismatches required for seq1 to become the collision sequence
y = number of mismatches required for seq2 to become the collision sequence
Goal
The goal is to find the collision sequence where x and y are as balanced as possible:
minimum abs(x - y)
If two index sequences differ by d positions, then the minimum collision condition is:
seq1 needs floor(d / 2) mismatches
seq2 needs ceil(d / 2) mismatches
or vice versa.
EXAMPLE
If the two indexes differ by 4 positions
d = 4
The closest collision condition is:
seq1: 2 mismatches
seq2: 2 mismatches
So if both indexes are allowed 2 mismatches, they may collide. If the two indexes differ by 5 positions The closest collision condition is:
seq1: 2 mismatches
seq2: 3 mismatches
or
seq1: 3 mismatches
seq2: 2 mismatches
So if both indexes are allowed 3 mismatches, they may collide. **MSMA is **
MSMA = floor((d - 1) / 2)
EXAMPLE
seq1: ATGGC
seq2: AGCGC
so:
d = 2
therefore
MSMA = floor((2 - 1) / 2)
= floor(0.5)
= 0
→ allowing 1 mismatch is already unsafe, because both indexes could mutate toward the same collision sequence. if 1 mismatch, seq1 can be AGGGC, seq2 can be AGGGC *(red is mismatched) → *same collision sequence.
Handling Indexes with Different Lengths
If seq1 is shorter than seq2, for example:
seq1: AGT
seq2: AGCGC
The algorithm pads the shorter sequence with N in all possible positions until both sequences have the same length.
seq1-1: NNAGT
seq1-2: NAGTN
seq1-3: AGTNN
seq2: AGCGC
Each padded version of seq1 is then compared against seq2.
seq1-1: NNAGT
seq2: AGCGC
adjusted distance = total differences - inserted N bases = 2
and
seq1-2: NAGTN
seq2: AGCGC
adjusted distance = total differences - inserted N bases = 3
and
seq1-3: AGTNN
seq2: AGCGC
adjusted distance = total differences - inserted N bases = 1
The algorithm takes the smallest adjusted distance, because this represents the most dangerous case
MSMA = floor((1 - 1) / 2) = 0
→ no mismatch should be allowed
Calculating MSMA Across Multiple Samples
- In real sequencing runs, each lane usually contains multiple samples.
Sample_ID,Index
SampleA,ATGGC
SampleB,AGCGC
SampleC,TTAAC
SampleD,ATGGA
Pairwise comparison within each lane
SampleA vs SampleB = 2
SampleA vs SampleC = 3
SampleA vs SampleD = 1
minimum distance for SampleA = 1
This means SampleD is the closest index to SampleA.
Even though SampleA is far away from SampleC, that does not matter for safety. The mismatch allowance must be determined by the closest possible collision.
MSMA_sampleA = 1 - 1 = 0
No mismatch is allowed for SampleA.
Additional Edge Cases and Data Validation Rules
In this workflow, Index1 is always expected to exist, while Index2 may be empty.
Therefore, the algorithm needs to handle missing Index2 values carefully to avoid false safety assumptions or incorrect distance calculations.
Case 1: Duplicated Index1 with missing Index2
Sample_ID,Index,Index2
SampleA,ATGGC,
SampleB,ATGGC,
→ 2 samples cannot be distinguished during demultiplexing
BUT, duplicated Index1 is not necessarily fatal if Index2 is available and different. For example:
Sample_ID,Index,Index2
SampleA,ATGGC,TTAAC
SampleB,ATGGC,CCGTA
Case 2: Missing Index2 should not be included in Index2 distance calculation
- when some samples have
Index2, while others do not…
Sample_ID,Index,Index2
SampleA,ATGGC,TTAAC
SampleB,CGTAA,
algorithm should skip
TTAAC vs `empty`
Limitation of Version 2
-
We only consider shift-style padding, not all possible N insertion positions:
NNAGT NAGTN AGTNNNOT
NANGT
NAGNT
ANNGT
ANGNT
AGNTN
...
- Assume index1 will never be empty.