jbrowse-plugin-msaview 2.6.2 → 2.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/AddHighlightModel/MsaToGenomeHighlight.js +8 -9
  2. package/dist/LaunchMsaViewExtensionPoint/index.js +12 -5
  3. package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +0 -2
  4. package/dist/MsaViewPanel/afterCreateAutoruns.js +8 -81
  5. package/dist/MsaViewPanel/model.d.ts +3 -28
  6. package/dist/MsaViewPanel/model.js +5 -103
  7. package/dist/MsaViewPanel/structureConnection.d.ts +0 -18
  8. package/dist/MsaViewPanel/types.d.ts +1 -4
  9. package/dist/jbrowse-plugin-msaview.umd.production.min.js +26 -26
  10. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  11. package/dist/version.d.ts +1 -1
  12. package/dist/version.js +1 -1
  13. package/package.json +1 -1
  14. package/src/AddHighlightModel/MsaToGenomeHighlight.tsx +8 -9
  15. package/src/LaunchMsaViewExtensionPoint/index.ts +12 -5
  16. package/src/MsaViewPanel/afterCreateAutoruns.ts +8 -109
  17. package/src/MsaViewPanel/model.ts +4 -135
  18. package/src/MsaViewPanel/structureConnection.ts +0 -17
  19. package/src/MsaViewPanel/types.ts +14 -9
  20. package/src/version.ts +1 -1
  21. package/dist/MsaViewPanel/components/ConnectStructureDialog.d.ts +0 -7
  22. package/dist/MsaViewPanel/components/ConnectStructureDialog.js +0 -60
  23. package/dist/MsaViewPanel/pairwiseAlignment.d.ts +0 -20
  24. package/dist/MsaViewPanel/pairwiseAlignment.js +0 -138
  25. package/dist/MsaViewPanel/pairwiseAlignment.test.d.ts +0 -1
  26. package/dist/MsaViewPanel/pairwiseAlignment.test.js +0 -111
  27. package/src/MsaViewPanel/components/ConnectStructureDialog.tsx +0 -154
  28. package/src/MsaViewPanel/pairwiseAlignment.test.ts +0 -140
  29. package/src/MsaViewPanel/pairwiseAlignment.ts +0 -182
@@ -1,138 +0,0 @@
1
- import BLOSUM62 from './blosum62';
2
- function getScore(a, b) {
3
- return BLOSUM62[a.toUpperCase()]?.[b.toUpperCase()] ?? -4;
4
- }
5
- const GAP_OPEN = -10;
6
- const GAP_EXTEND = -0.5;
7
- export function needlemanWunsch(seq1, seq2, gapOpen = GAP_OPEN, gapExtend = GAP_EXTEND) {
8
- const m = seq1.length;
9
- const n = seq2.length;
10
- const M = [];
11
- const Ix = [];
12
- const Iy = [];
13
- for (let i = 0; i <= m; i++) {
14
- M[i] = [];
15
- Ix[i] = [];
16
- Iy[i] = [];
17
- for (let j = 0; j <= n; j++) {
18
- M[i][j] = -Infinity;
19
- Ix[i][j] = -Infinity;
20
- Iy[i][j] = -Infinity;
21
- }
22
- }
23
- M[0][0] = 0;
24
- for (let i = 1; i <= m; i++) {
25
- Ix[i][0] = gapOpen + (i - 1) * gapExtend;
26
- }
27
- for (let j = 1; j <= n; j++) {
28
- Iy[0][j] = gapOpen + (j - 1) * gapExtend;
29
- }
30
- for (let i = 1; i <= m; i++) {
31
- for (let j = 1; j <= n; j++) {
32
- const matchScore = getScore(seq1[i - 1], seq2[j - 1]);
33
- M[i][j] =
34
- Math.max(M[i - 1][j - 1], Ix[i - 1][j - 1], Iy[i - 1][j - 1]) +
35
- matchScore;
36
- Ix[i][j] = Math.max(M[i - 1][j] + gapOpen, Ix[i - 1][j] + gapExtend);
37
- Iy[i][j] = Math.max(M[i][j - 1] + gapOpen, Iy[i][j - 1] + gapExtend);
38
- }
39
- }
40
- let alignedSeq1 = '';
41
- let alignedSeq2 = '';
42
- let i = m;
43
- let j = n;
44
- const mScore = M[m][n];
45
- const ixScore = Ix[m][n];
46
- const iyScore = Iy[m][n];
47
- const score = Math.max(mScore, ixScore, iyScore);
48
- let currentMatrix = score === mScore ? 'M' : score === ixScore ? 'Ix' : 'Iy';
49
- while (i > 0 || j > 0) {
50
- if (currentMatrix === 'M' && i > 0 && j > 0) {
51
- alignedSeq1 = seq1[i - 1] + alignedSeq1;
52
- alignedSeq2 = seq2[j - 1] + alignedSeq2;
53
- const matchScore = getScore(seq1[i - 1], seq2[j - 1]);
54
- const prevM = M[i - 1][j - 1];
55
- const prevIx = Ix[i - 1][j - 1];
56
- const currentScore = M[i][j];
57
- if (currentScore === prevM + matchScore) {
58
- currentMatrix = 'M';
59
- }
60
- else if (currentScore === prevIx + matchScore) {
61
- currentMatrix = 'Ix';
62
- }
63
- else {
64
- currentMatrix = 'Iy';
65
- }
66
- i--;
67
- j--;
68
- }
69
- else if (currentMatrix === 'Ix' && i > 0) {
70
- alignedSeq1 = seq1[i - 1] + alignedSeq1;
71
- alignedSeq2 = '-' + alignedSeq2;
72
- const ixScore = Ix[i][j];
73
- const mScore = M[i - 1][j] + gapOpen;
74
- currentMatrix = ixScore === mScore ? 'M' : 'Ix';
75
- i--;
76
- }
77
- else if (j > 0) {
78
- alignedSeq1 = '-' + alignedSeq1;
79
- alignedSeq2 = seq2[j - 1] + alignedSeq2;
80
- const iyScore = Iy[i][j];
81
- const mScore = M[i][j - 1] + gapOpen;
82
- currentMatrix = iyScore === mScore ? 'M' : 'Iy';
83
- j--;
84
- }
85
- else {
86
- break;
87
- }
88
- }
89
- return { alignedSeq1, alignedSeq2, score };
90
- }
91
- function buildConsensus(alignedSeq1, alignedSeq2) {
92
- let consensus = '';
93
- for (let i = 0; i < alignedSeq1.length; i++) {
94
- const a = alignedSeq1[i];
95
- const b = alignedSeq2[i];
96
- const match = a !== '-' && b !== '-' && a.toUpperCase() === b.toUpperCase();
97
- consensus += match ? '|' : ' ';
98
- }
99
- return consensus;
100
- }
101
- export function runPairwiseAlignment(seq1, seq2) {
102
- const { alignedSeq1, alignedSeq2 } = needlemanWunsch(seq1, seq2);
103
- return {
104
- consensus: buildConsensus(alignedSeq1, alignedSeq2),
105
- alns: [
106
- { id: 'msa', seq: alignedSeq1 },
107
- { id: 'structure', seq: alignedSeq2 },
108
- ],
109
- };
110
- }
111
- export function buildAlignmentMaps(pairwiseAlignment) {
112
- const seq1 = pairwiseAlignment.alns[0].seq;
113
- const seq2 = pairwiseAlignment.alns[1].seq;
114
- if (seq1.length !== seq2.length) {
115
- throw new Error('Aligned sequences must have same length');
116
- }
117
- let pos1 = 0;
118
- let pos2 = 0;
119
- const seq1ToSeq2 = new Map();
120
- const seq2ToSeq1 = new Map();
121
- for (let i = 0; i < seq1.length; i++) {
122
- const c1 = seq1[i];
123
- const c2 = seq2[i];
124
- if (c1 !== '-' && c2 !== '-') {
125
- seq1ToSeq2.set(pos1, pos2);
126
- seq2ToSeq1.set(pos2, pos1);
127
- pos1++;
128
- pos2++;
129
- }
130
- else if (c1 === '-') {
131
- pos2++;
132
- }
133
- else {
134
- pos1++;
135
- }
136
- }
137
- return { seq1ToSeq2, seq2ToSeq1 };
138
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,111 +0,0 @@
1
- import { describe, expect, test } from 'vitest';
2
- import { buildAlignmentMaps, needlemanWunsch, runPairwiseAlignment, } from './pairwiseAlignment';
3
- describe('needlemanWunsch', () => {
4
- test('identical sequences align perfectly', () => {
5
- const result = needlemanWunsch('MKAA', 'MKAA');
6
- expect(result.alignedSeq1).toBe('MKAA');
7
- expect(result.alignedSeq2).toBe('MKAA');
8
- expect(result.score).toBeGreaterThan(0);
9
- });
10
- test('aligned sequences have same length', () => {
11
- const result = needlemanWunsch('MKAAYLSMFG', 'MKAYLSMFG');
12
- expect(result.alignedSeq1.length).toBe(result.alignedSeq2.length);
13
- });
14
- test('aligned sequences preserve original characters', () => {
15
- const seq1 = 'MKAAYLSMFG';
16
- const seq2 = 'MKAYLSMFG';
17
- const result = needlemanWunsch(seq1, seq2);
18
- expect(result.alignedSeq1.replaceAll('-', '')).toBe(seq1);
19
- expect(result.alignedSeq2.replaceAll('-', '')).toBe(seq2);
20
- });
21
- test('handles empty sequences', () => {
22
- const result = needlemanWunsch('', '');
23
- expect(result.alignedSeq1).toBe('');
24
- expect(result.alignedSeq2).toBe('');
25
- });
26
- test('handles one empty sequence', () => {
27
- const result = needlemanWunsch('MKA', '');
28
- expect(result.alignedSeq1.replaceAll('-', '')).toBe('MKA');
29
- expect(result.alignedSeq2.replaceAll('-', '')).toBe('');
30
- expect(result.alignedSeq1.length).toBe(result.alignedSeq2.length);
31
- });
32
- });
33
- describe('runPairwiseAlignment', () => {
34
- test('returns PairwiseAlignment format', () => {
35
- const result = runPairwiseAlignment('MKAA', 'MKAA');
36
- expect(result.consensus).toBeDefined();
37
- expect(result.alns).toHaveLength(2);
38
- expect(result.alns[0].id).toBe('msa');
39
- expect(result.alns[1].id).toBe('structure');
40
- });
41
- test('consensus marks matches with pipe', () => {
42
- const result = runPairwiseAlignment('MKAA', 'MKAA');
43
- expect(result.consensus).toBe('||||');
44
- });
45
- test('consensus marks gaps with space', () => {
46
- const result = runPairwiseAlignment('MKAA', 'MKA');
47
- expect(result.consensus).toContain(' ');
48
- });
49
- test('consensus marks mismatches with space', () => {
50
- const result = runPairwiseAlignment('MKAA', 'MKBA');
51
- // A vs B should be a space in consensus
52
- expect(result.consensus).toContain(' ');
53
- });
54
- });
55
- describe('buildAlignmentMaps', () => {
56
- test('builds bidirectional maps for identical sequences', () => {
57
- const alignment = runPairwiseAlignment('MKAA', 'MKAA');
58
- const { seq1ToSeq2, seq2ToSeq1 } = buildAlignmentMaps(alignment);
59
- expect(seq1ToSeq2.get(0)).toBe(0);
60
- expect(seq1ToSeq2.get(1)).toBe(1);
61
- expect(seq1ToSeq2.get(2)).toBe(2);
62
- expect(seq1ToSeq2.get(3)).toBe(3);
63
- expect(seq2ToSeq1.get(0)).toBe(0);
64
- expect(seq2ToSeq1.get(1)).toBe(1);
65
- expect(seq2ToSeq1.get(2)).toBe(2);
66
- expect(seq2ToSeq1.get(3)).toBe(3);
67
- });
68
- test('maps are inverses of each other for matched positions', () => {
69
- const alignment = runPairwiseAlignment('MKAAYLSMFG', 'MKAYLSMFG');
70
- const { seq1ToSeq2, seq2ToSeq1 } = buildAlignmentMaps(alignment);
71
- // For every mapped position, the inverse should return the original
72
- for (const [pos1, pos2] of seq1ToSeq2) {
73
- expect(seq2ToSeq1.get(pos2)).toBe(pos1);
74
- }
75
- });
76
- test('handles gaps correctly - positions without counterpart are not in map', () => {
77
- // Aligning 'MKAAA' with 'MKA' should result in gaps
78
- const alignment = runPairwiseAlignment('MKAAA', 'MKA');
79
- const { seq1ToSeq2 } = buildAlignmentMaps(alignment);
80
- // seq1 has 5 positions, seq2 has 3
81
- // Only matched positions should be in the map
82
- expect(seq1ToSeq2.size).toBeLessThanOrEqual(3);
83
- });
84
- test('handles real protein sequence alignment', () => {
85
- const msaSeq = 'MKAAYLSMFGKEDHKPFGDDEVELFRAVPGLKLKIAG';
86
- const structureSeq = 'MKAAYLSMFGKEDHKPFGDDEVELFRAVPGLKLKIAG';
87
- const alignment = runPairwiseAlignment(msaSeq, structureSeq);
88
- const { seq1ToSeq2, seq2ToSeq1 } = buildAlignmentMaps(alignment);
89
- // Identical sequences should have 1:1 mapping
90
- expect(seq1ToSeq2.size).toBe(msaSeq.length);
91
- expect(seq2ToSeq1.size).toBe(structureSeq.length);
92
- // Check a few positions
93
- expect(seq1ToSeq2.get(0)).toBe(0);
94
- expect(seq1ToSeq2.get(10)).toBe(10);
95
- expect(seq2ToSeq1.get(20)).toBe(20);
96
- });
97
- test('handles sequences with insertions/deletions', () => {
98
- // MSA sequence has an extra 'X' in the middle
99
- const msaSeq = 'MKAXYLSMFG';
100
- const structureSeq = 'MKAYLSMFG';
101
- const alignment = runPairwiseAlignment(msaSeq, structureSeq);
102
- const { seq1ToSeq2 } = buildAlignmentMaps(alignment);
103
- // Positions before the insertion should map correctly
104
- expect(seq1ToSeq2.get(0)).toBe(0); // M
105
- expect(seq1ToSeq2.get(1)).toBe(1); // K
106
- expect(seq1ToSeq2.get(2)).toBe(2); // A
107
- // The mapping should handle the offset after insertion
108
- // (exact behavior depends on alignment algorithm)
109
- expect(seq1ToSeq2.size).toBeLessThanOrEqual(structureSeq.length);
110
- });
111
- });
@@ -1,154 +0,0 @@
1
- import React, { useState } from 'react'
2
-
3
- import { Dialog, ErrorMessage } from '@jbrowse/core/ui'
4
- import { getSession } from '@jbrowse/core/util'
5
- import {
6
- Button,
7
- DialogActions,
8
- DialogContent,
9
- FormControl,
10
- InputLabel,
11
- MenuItem,
12
- Select,
13
- Typography,
14
- } from '@mui/material'
15
- import { observer } from 'mobx-react'
16
- import { makeStyles } from 'tss-react/mui'
17
-
18
- import { getProteinViews } from '../structureConnection'
19
-
20
- import type { JBrowsePluginMsaViewModel } from '../model'
21
-
22
- const useStyles = makeStyles()(theme => ({
23
- formControl: {
24
- marginBottom: theme.spacing(2),
25
- },
26
- }))
27
-
28
- const ConnectStructureDialog = observer(function ConnectStructureDialog({
29
- model,
30
- handleClose,
31
- }: {
32
- model: JBrowsePluginMsaViewModel
33
- handleClose: () => void
34
- }) {
35
- const { classes } = useStyles()
36
- const session = getSession(model)
37
- const [selectedViewId, setSelectedViewId] = useState('')
38
- const [selectedStructureIdx, setSelectedStructureIdx] = useState(0)
39
- const [selectedMsaRow, setSelectedMsaRow] = useState(model.querySeqName)
40
- const [error, setError] = useState<string>()
41
-
42
- const proteinViews = getProteinViews(session.views)
43
-
44
- const selectedView = proteinViews.find(v => v.id === selectedViewId)
45
- const structures = selectedView?.structures ?? []
46
-
47
- const msaRowNames = model.rows.map(r => r[0])
48
-
49
- const handleConnect = () => {
50
- if (!selectedViewId) {
51
- setError('Please select a protein view')
52
- return
53
- }
54
-
55
- try {
56
- model.connectToStructure(
57
- selectedViewId,
58
- selectedStructureIdx,
59
- selectedMsaRow,
60
- )
61
- handleClose()
62
- } catch (e) {
63
- setError(e instanceof Error ? e.message : String(e))
64
- }
65
- }
66
-
67
- return (
68
- <Dialog
69
- maxWidth="sm"
70
- title="Connect to Protein Structure"
71
- open
72
- onClose={handleClose}
73
- >
74
- <DialogContent>
75
- {proteinViews.length === 0 ? (
76
- <Typography color="textSecondary">
77
- No protein views are currently open. Please open a protein structure
78
- view first.
79
- </Typography>
80
- ) : (
81
- <>
82
- <FormControl fullWidth className={classes.formControl}>
83
- <InputLabel>Protein View</InputLabel>
84
- <Select
85
- value={selectedViewId}
86
- label="Protein View"
87
- onChange={e => {
88
- setSelectedViewId(e.target.value)
89
- setSelectedStructureIdx(0)
90
- }}
91
- >
92
- {proteinViews.map(view => (
93
- <MenuItem key={view.id} value={view.id}>
94
- {view.displayName ?? `ProteinView ${view.id}`}
95
- </MenuItem>
96
- ))}
97
- </Select>
98
- </FormControl>
99
-
100
- {structures.length > 1 ? (
101
- <FormControl fullWidth className={classes.formControl}>
102
- <InputLabel>Structure</InputLabel>
103
- <Select
104
- value={selectedStructureIdx}
105
- label="Structure"
106
- onChange={e => {
107
- setSelectedStructureIdx(e.target.value)
108
- }}
109
- >
110
- {structures.map((structure, idx) => (
111
- <MenuItem key={idx} value={idx}>
112
- {structure.url ?? `Structure ${idx + 1}`}
113
- </MenuItem>
114
- ))}
115
- </Select>
116
- </FormControl>
117
- ) : null}
118
-
119
- <FormControl fullWidth className={classes.formControl}>
120
- <InputLabel>MSA Row</InputLabel>
121
- <Select
122
- value={selectedMsaRow}
123
- label="MSA Row"
124
- onChange={e => {
125
- setSelectedMsaRow(e.target.value)
126
- }}
127
- >
128
- {msaRowNames.map(name => (
129
- <MenuItem key={name} value={name}>
130
- {name}
131
- </MenuItem>
132
- ))}
133
- </Select>
134
- </FormControl>
135
-
136
- {error ? <ErrorMessage error={error} /> : null}
137
- </>
138
- )}
139
- </DialogContent>
140
- <DialogActions>
141
- <Button onClick={handleClose}>Cancel</Button>
142
- <Button
143
- onClick={handleConnect}
144
- variant="contained"
145
- disabled={proteinViews.length === 0 || !selectedViewId}
146
- >
147
- Connect
148
- </Button>
149
- </DialogActions>
150
- </Dialog>
151
- )
152
- })
153
-
154
- export default ConnectStructureDialog
@@ -1,140 +0,0 @@
1
- import { describe, expect, test } from 'vitest'
2
-
3
- import {
4
- buildAlignmentMaps,
5
- needlemanWunsch,
6
- runPairwiseAlignment,
7
- } from './pairwiseAlignment'
8
-
9
- describe('needlemanWunsch', () => {
10
- test('identical sequences align perfectly', () => {
11
- const result = needlemanWunsch('MKAA', 'MKAA')
12
- expect(result.alignedSeq1).toBe('MKAA')
13
- expect(result.alignedSeq2).toBe('MKAA')
14
- expect(result.score).toBeGreaterThan(0)
15
- })
16
-
17
- test('aligned sequences have same length', () => {
18
- const result = needlemanWunsch('MKAAYLSMFG', 'MKAYLSMFG')
19
- expect(result.alignedSeq1.length).toBe(result.alignedSeq2.length)
20
- })
21
-
22
- test('aligned sequences preserve original characters', () => {
23
- const seq1 = 'MKAAYLSMFG'
24
- const seq2 = 'MKAYLSMFG'
25
- const result = needlemanWunsch(seq1, seq2)
26
- expect(result.alignedSeq1.replaceAll('-', '')).toBe(seq1)
27
- expect(result.alignedSeq2.replaceAll('-', '')).toBe(seq2)
28
- })
29
-
30
- test('handles empty sequences', () => {
31
- const result = needlemanWunsch('', '')
32
- expect(result.alignedSeq1).toBe('')
33
- expect(result.alignedSeq2).toBe('')
34
- })
35
-
36
- test('handles one empty sequence', () => {
37
- const result = needlemanWunsch('MKA', '')
38
- expect(result.alignedSeq1.replaceAll('-', '')).toBe('MKA')
39
- expect(result.alignedSeq2.replaceAll('-', '')).toBe('')
40
- expect(result.alignedSeq1.length).toBe(result.alignedSeq2.length)
41
- })
42
- })
43
-
44
- describe('runPairwiseAlignment', () => {
45
- test('returns PairwiseAlignment format', () => {
46
- const result = runPairwiseAlignment('MKAA', 'MKAA')
47
- expect(result.consensus).toBeDefined()
48
- expect(result.alns).toHaveLength(2)
49
- expect(result.alns[0].id).toBe('msa')
50
- expect(result.alns[1].id).toBe('structure')
51
- })
52
-
53
- test('consensus marks matches with pipe', () => {
54
- const result = runPairwiseAlignment('MKAA', 'MKAA')
55
- expect(result.consensus).toBe('||||')
56
- })
57
-
58
- test('consensus marks gaps with space', () => {
59
- const result = runPairwiseAlignment('MKAA', 'MKA')
60
- expect(result.consensus).toContain(' ')
61
- })
62
-
63
- test('consensus marks mismatches with space', () => {
64
- const result = runPairwiseAlignment('MKAA', 'MKBA')
65
- // A vs B should be a space in consensus
66
- expect(result.consensus).toContain(' ')
67
- })
68
- })
69
-
70
- describe('buildAlignmentMaps', () => {
71
- test('builds bidirectional maps for identical sequences', () => {
72
- const alignment = runPairwiseAlignment('MKAA', 'MKAA')
73
- const { seq1ToSeq2, seq2ToSeq1 } = buildAlignmentMaps(alignment)
74
-
75
- expect(seq1ToSeq2.get(0)).toBe(0)
76
- expect(seq1ToSeq2.get(1)).toBe(1)
77
- expect(seq1ToSeq2.get(2)).toBe(2)
78
- expect(seq1ToSeq2.get(3)).toBe(3)
79
-
80
- expect(seq2ToSeq1.get(0)).toBe(0)
81
- expect(seq2ToSeq1.get(1)).toBe(1)
82
- expect(seq2ToSeq1.get(2)).toBe(2)
83
- expect(seq2ToSeq1.get(3)).toBe(3)
84
- })
85
-
86
- test('maps are inverses of each other for matched positions', () => {
87
- const alignment = runPairwiseAlignment('MKAAYLSMFG', 'MKAYLSMFG')
88
- const { seq1ToSeq2, seq2ToSeq1 } = buildAlignmentMaps(alignment)
89
-
90
- // For every mapped position, the inverse should return the original
91
- for (const [pos1, pos2] of seq1ToSeq2) {
92
- expect(seq2ToSeq1.get(pos2)).toBe(pos1)
93
- }
94
- })
95
-
96
- test('handles gaps correctly - positions without counterpart are not in map', () => {
97
- // Aligning 'MKAAA' with 'MKA' should result in gaps
98
- const alignment = runPairwiseAlignment('MKAAA', 'MKA')
99
- const { seq1ToSeq2 } = buildAlignmentMaps(alignment)
100
-
101
- // seq1 has 5 positions, seq2 has 3
102
- // Only matched positions should be in the map
103
- expect(seq1ToSeq2.size).toBeLessThanOrEqual(3)
104
- })
105
-
106
- test('handles real protein sequence alignment', () => {
107
- const msaSeq = 'MKAAYLSMFGKEDHKPFGDDEVELFRAVPGLKLKIAG'
108
- const structureSeq = 'MKAAYLSMFGKEDHKPFGDDEVELFRAVPGLKLKIAG'
109
-
110
- const alignment = runPairwiseAlignment(msaSeq, structureSeq)
111
- const { seq1ToSeq2, seq2ToSeq1 } = buildAlignmentMaps(alignment)
112
-
113
- // Identical sequences should have 1:1 mapping
114
- expect(seq1ToSeq2.size).toBe(msaSeq.length)
115
- expect(seq2ToSeq1.size).toBe(structureSeq.length)
116
-
117
- // Check a few positions
118
- expect(seq1ToSeq2.get(0)).toBe(0)
119
- expect(seq1ToSeq2.get(10)).toBe(10)
120
- expect(seq2ToSeq1.get(20)).toBe(20)
121
- })
122
-
123
- test('handles sequences with insertions/deletions', () => {
124
- // MSA sequence has an extra 'X' in the middle
125
- const msaSeq = 'MKAXYLSMFG'
126
- const structureSeq = 'MKAYLSMFG'
127
-
128
- const alignment = runPairwiseAlignment(msaSeq, structureSeq)
129
- const { seq1ToSeq2 } = buildAlignmentMaps(alignment)
130
-
131
- // Positions before the insertion should map correctly
132
- expect(seq1ToSeq2.get(0)).toBe(0) // M
133
- expect(seq1ToSeq2.get(1)).toBe(1) // K
134
- expect(seq1ToSeq2.get(2)).toBe(2) // A
135
-
136
- // The mapping should handle the offset after insertion
137
- // (exact behavior depends on alignment algorithm)
138
- expect(seq1ToSeq2.size).toBeLessThanOrEqual(structureSeq.length)
139
- })
140
- })