├── VERSION ├── src ├── __init__.py ├── iupac.py ├── libssw.py ├── ssw │ ├── ssw.h │ └── ssw.c └── sswobj.py ├── MANIFEST.in ├── .gitignore ├── setup.cfg ├── .travis.yml ├── README.md ├── setup.py ├── LICENSE └── tests └── test_ssw.py /VERSION: -------------------------------------------------------------------------------- 1 | 0.4.1 2 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | from . sswobj import * 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include VERSION README.md LICENSE src/ssw/* 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.sw? 2 | *.pyc 3 | build/ 4 | dist/ 5 | *.egg-info/ 6 | tarpit 7 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | 4 | [nosetests] 5 | no-byte-compile=1 6 | verbosity=3 7 | where=tests 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 2.7 4 | - 3.3 5 | - 3.4 6 | - 3.5 7 | - 3.6 8 | matrix: 9 | include: 10 | - python: 3.7 11 | dist: xenial 12 | sudo: true 13 | before_install: 14 | - date -u 15 | - uname -a 16 | - lsb_release -a 17 | - sudo apt-get -qq update 18 | install: 19 | - pip install coveralls 20 | - python setup.py install 21 | script: 22 | - nosetests --with-coverage --cover-package=ssw 23 | after_success: 24 | - coveralls 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/vishnubob/ssw.svg?branch=master)](https://travis-ci.org/vishnubob/ssw) 2 | [![Coverage Status](https://coveralls.io/repos/vishnubob/ssw/badge.svg?branch=master&service=github)](https://coveralls.io/github/vishnubob/ssw?branch=master) 3 | 4 | #SSW: A Python Wrapper for the SIMD Smith-Waterman 5 | 6 | ## Overview 7 | 8 | [SSW][ssw_repo] is a fast implementation of the Smith-Waterman algorithm, which 9 | uses the Single-Instruction Multiple-Data (SIMD) instructions to parallelize 10 | the algorithm at the CPU level. This repository wraps the SSW library into an 11 | easy to install, high-level python interface with no external library dependancies. 12 | 13 | The SSW library is written by Mengyao Zhao and Wan-Ping Lee, and this python 14 | interface is maintained by Giles Hall. 15 | 16 | ## Installation 17 | 18 | To install the SSW python package, use pip: 19 | 20 | ``` 21 | $ pip install ssw 22 | ``` 23 | 24 | ## Example Usage 25 | 26 | ``` 27 | import ssw 28 | aligner = ssw.Aligner() 29 | alignment = aligner.align(reference="ACGTGAGAATTATGGCGCTGTGATT", query="ACGTGAGAATTATGCGCTGTGATT") 30 | print(alignment.alignment_report()) 31 | Score = 45, Matches = 24, Mismatches = 0, Insertions = 0, Deletions = 1 32 | 33 | ref 1 ACGTGAGAATTATGGCGCTGTGATT 34 | ||||||||||||| ||||||||||| 35 | query 1 ACGTGAGAATTAT-GCGCTGTGATT 36 | ``` 37 | 38 | [ssw_repo]: https://github.com/mengyao/Complete-Striped-Smith-Waterman-Library 39 | 40 | -------------------------------------------------------------------------------- /src/iupac.py: -------------------------------------------------------------------------------- 1 | import six 2 | from six.moves import range 3 | 4 | __all__ = ( 5 | "NucleotideTable", 6 | "NucleotideAlphabet", 7 | "NucleotideComplementTable", 8 | "nucleotide_reverse_complement", 9 | ) 10 | 11 | def _build_compliment_table(): 12 | _ctable = list(map(chr, range(0xff + 1))) 13 | for symbol in NucleotideAlphabet: 14 | complement = NucleotideTable[symbol]["complement"] 15 | _ctable[ord(symbol)] = complement 16 | _ctable[ord(symbol.lower())] = complement.lower() 17 | return str.join('', _ctable) 18 | 19 | def _iupac_info(complement, matches): 20 | return { 21 | "complement": complement.upper(), 22 | "matches": tuple(matches.upper()) 23 | } 24 | 25 | NucleotideTable = { 26 | 'A': _iupac_info('T', 'A'), 27 | 'G': _iupac_info('C', 'G'), 28 | 'C': _iupac_info('G', 'C'), 29 | 'T': _iupac_info('A', 'T'), 30 | 'U': _iupac_info('A', 'T'), 31 | 'M': _iupac_info('K', 'AC'), 32 | 'R': _iupac_info('Y', 'AG'), 33 | 'Y': _iupac_info('R', 'CT'), 34 | 'S': _iupac_info('S', 'CG'), 35 | 'W': _iupac_info('W', 'AT'), 36 | 'K': _iupac_info('M', 'GT'), 37 | 'B': _iupac_info('V', 'CGT'), 38 | 'D': _iupac_info('H', 'AGT'), 39 | 'H': _iupac_info('D', 'ACT'), 40 | 'V': _iupac_info('B', 'ACG'), 41 | 'N': _iupac_info('N', 'AGCT') 42 | } 43 | 44 | NucleotideAlphabet = str.join('', tuple(NucleotideTable.keys())) 45 | NucleotideComplimentTable = _build_compliment_table() 46 | 47 | def nucleotide_reverse_complement(sequence): 48 | return sequence[::-1].translate(NucleotideComplimentTable) 49 | -------------------------------------------------------------------------------- /src/libssw.py: -------------------------------------------------------------------------------- 1 | import os 2 | import itertools 3 | from ctypes import * 4 | 5 | def load_ssw_library(): 6 | mod_path = os.path.split(__file__)[0] 7 | search_path = os.path.join(mod_path, "..") 8 | libname = "_libssw" 9 | for (root, dirs, files) in os.walk(search_path): 10 | for fn in files: 11 | if fn.lower().startswith(libname) and fn.lower().endswith(".so"): 12 | libpath = os.path.join(root, fn) 13 | return cdll.LoadLibrary(libpath) 14 | return cdll.LoadLibrary(libname) 15 | 16 | # load our library 17 | libssw = load_ssw_library() 18 | 19 | # Types 20 | class AlignmentResult(Structure): 21 | _fields_ = [ 22 | ("score", c_uint16), 23 | ("score2", c_uint16), 24 | ("ref_begin", c_int32), 25 | ("ref_end", c_int32), 26 | ('query_begin', c_int32), 27 | ('query_end', c_int32), 28 | ('ref_end2', c_int32), 29 | ('cigar', POINTER(c_uint32)), 30 | ('cigarLen', c_int32), 31 | ] 32 | 33 | ssw_profile_p = c_void_p 34 | matrix_type = c_int8 35 | symbol_type = c_int8 36 | 37 | # ssw_init method 38 | ssw_profile_init = libssw.ssw_init 39 | ssw_profile_init.argtypes = [POINTER(c_int8), c_int32, POINTER(c_int8), c_int32, c_int8] 40 | ssw_profile_init.restype = ssw_profile_p 41 | 42 | # init_destroy function 43 | ssw_profile_del = libssw.init_destroy 44 | ssw_profile_del.argtypes = [ssw_profile_p] 45 | ssw_profile_del.restype = None 46 | 47 | # ssw_align function 48 | ssw_align_init = libssw.ssw_align 49 | ssw_align_init.argtypes = [ssw_profile_p, POINTER(c_int8), c_int32, c_uint8, c_uint8, c_uint8, c_uint16, c_int32, c_int32] 50 | ssw_align_init.restype = POINTER(AlignmentResult) 51 | 52 | # align_destroy function 53 | ssw_align_del = libssw.align_destroy 54 | ssw_align_del.argtypes = [POINTER(AlignmentResult)] 55 | ssw_align_del.restype = None 56 | 57 | # cigar_int_to_len function 58 | cigar_int_to_len = libssw.cigar_int_to_len 59 | cigar_int_to_len.argtypes = [c_uint32] 60 | cigar_int_to_len.restype = c_uint32 61 | 62 | # cigar_int_to_op function 63 | cigar_int_to_op = libssw.cigar_int_to_op 64 | cigar_int_to_op.argtypes = [c_uint32] 65 | cigar_int_to_op.restype = c_char 66 | 67 | # flags 68 | FLAG_BEST_POS = 0 69 | FLAG_FILTER_SCORE = 1 70 | FLAG_FILTER_DISTANCE = 2 71 | FLAG_BUILD_CIGAR = 3 72 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, Extension 4 | 5 | version = open('VERSION').read().strip() 6 | download_url = "https://github.com/vishnubob/ssw/archive/v%s.tar.gz" % version 7 | long_description = \ 8 | """ssw is a fast implementation of the Smith-Waterman algorithm, which 9 | uses the Single-Instruction Multiple-Data (SIMD) instructions to parallelize 10 | the algorithm at the CPU level. This repository wraps the SSW library into an 11 | easy to install, high-level python interface with no external library dependancies. 12 | 13 | The SSW library is written by Mengyao Zhao and Wan-Ping Lee, and this python 14 | interface is maintained by Giles Hall. 15 | """ 16 | 17 | libssw_ext = { 18 | "sources": ["src/ssw/ssw.c"], 19 | "include_dirs": ["src/ssw"], 20 | } 21 | 22 | config = { 23 | "name": "ssw", 24 | "version": version, 25 | "description": "Smith-Waterman Sequence Aligner", 26 | "author": "Giles Hall", 27 | "author_email": "giles@polymerase.org", 28 | "package_dir": {"ssw": "src"}, 29 | "packages": ["ssw"], 30 | "classifiers": [ 31 | "Development Status :: 5 - Production/Stable", 32 | "Programming Language :: Python", 33 | "Programming Language :: Python :: 2", 34 | "Programming Language :: Python :: 2.7", 35 | "Programming Language :: Python :: 3", 36 | "Programming Language :: Python :: 3.3", 37 | "Programming Language :: Python :: 3.4", 38 | "Programming Language :: Python :: 3.5", 39 | "Programming Language :: Python :: 3.6", 40 | "Programming Language :: Python :: 3.7", 41 | "License :: OSI Approved :: MIT License", 42 | "Intended Audience :: Science/Research", 43 | "Topic :: Scientific/Engineering :: Bio-Informatics", 44 | ], 45 | "keywords": [ 46 | "bioinformatics", 47 | "sequence alignment", 48 | "smith-waterman", 49 | "genomics", 50 | "proteomics" 51 | ], 52 | "install_requires": [ 53 | "six", 54 | ], 55 | "platforms": "any", 56 | "ext_modules": [Extension("_libssw", **libssw_ext)], 57 | "zip_safe": False, 58 | "download_url": download_url, 59 | "url": "https://github.com/vishnubob/ssw", 60 | "long_description": long_description, 61 | "long_description_content_type": "text/x-rst", 62 | } 63 | 64 | if __name__ == "__main__": 65 | setup(**config) 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 Giles Hall 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- 22 | 23 | Copyright 2006 Michael Farrar 24 | 25 | Redistribution and use in source and binary forms, with or without 26 | modification, are permitted provided that the following conditions are met: 27 | 28 | 1. Redistributions of source code must retain the above copyright notice, this 29 | list of conditions and the following disclaimer. 30 | 31 | 2. Redistributions in binary form must reproduce the above copyright notice, 32 | this list of conditions and the following disclaimer in the documentation 33 | and/or other materials provided with the distribution. 34 | 35 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 36 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 37 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 38 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 39 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 40 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 41 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 42 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 43 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 44 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 45 | -------------------------------------------------------------------------------- /tests/test_ssw.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest 4 | import pickle 5 | import ssw 6 | from ssw import iupac 7 | 8 | class TestIUPAC(unittest.TestCase): 9 | def test_degen_revcomp(self): 10 | test_seq = "AGTCMRYSWKBDHVN" 11 | test_seq_rc = "NBDHVMWSRYKGACT" 12 | _test_seq_rc = iupac.nucleotide_reverse_complement(test_seq) 13 | self.assertEqual(test_seq_rc, _test_seq_rc) 14 | _test_seq = iupac.nucleotide_reverse_complement(test_seq_rc) 15 | self.assertEqual(test_seq, _test_seq) 16 | 17 | class TestPickle(unittest.TestCase): 18 | def test_alignment_pickle(self): 19 | reference = "GTGCGATGTGCGATGAGATC" 20 | query = reference 21 | aligner = ssw.Aligner() 22 | al = aligner.align(query, reference) 23 | orig_dict = al.__dict__.copy() 24 | clone = pickle.loads(pickle.dumps(al)) 25 | clone_dict = clone.__dict__.copy() 26 | for key in orig_dict.keys(): 27 | self.assertIn(key, clone_dict) 28 | self.assertEqual(orig_dict[key], clone_dict[key]) 29 | 30 | class TestAlignment(unittest.TestCase): 31 | def test_mixed_case(self): 32 | reference = "GTGCGATGTGCGATGAGATC" 33 | query = reference.lower() 34 | aligner = ssw.Aligner() 35 | al = aligner.align(query, reference) 36 | self.assertEqual(al.match_count, len(reference)) 37 | self.assertEqual(al.mismatch_count, 0) 38 | self.assertEqual(al.insertion_count, 0) 39 | self.assertEqual(al.deletion_count, 0) 40 | self.assertEqual(al.cigar, '20M') 41 | 42 | def test_perfect_alignment(self): 43 | reference = "GTGCGATGTGCGATGAGATC" 44 | query = reference 45 | aligner = ssw.Aligner() 46 | al = aligner.align(query, reference) 47 | self.assertEqual(al.match_count, len(reference)) 48 | self.assertEqual(al.mismatch_count, 0) 49 | self.assertEqual(al.insertion_count, 0) 50 | self.assertEqual(al.deletion_count, 0) 51 | self.assertEqual(al.cigar, '20M') 52 | 53 | def test_rc_alignment(self): 54 | reference = "GTGCGATGTGCGATGAGATC" 55 | query = "GATCTCATCGCACATCGCAC" 56 | aligner = ssw.Aligner() 57 | al = aligner.align(query, reference) 58 | self.assertEqual(al.match_count, 20) 59 | self.assertEqual(al.mismatch_count, 0) 60 | self.assertEqual(al.insertion_count, 0) 61 | self.assertEqual(al.deletion_count, 0) 62 | self.assertEqual(al.cigar, "20M") 63 | 64 | def test_insertion(self): 65 | reference = "GTGCGATGTGCGATGAGATC" 66 | query = reference[:10] + 'A' + reference[10:] 67 | aligner = ssw.Aligner() 68 | al = aligner.align(query, reference) 69 | self.assertEqual(al.match_count, 20) 70 | self.assertEqual(al.mismatch_count, 0) 71 | self.assertEqual(al.insertion_count, 1) 72 | self.assertEqual(al.deletion_count, 0) 73 | self.assertEqual(al.cigar, "10M1I10M") 74 | 75 | def test_mismatch(self): 76 | reference = "GTGCGATGTGCGATGAGATC" 77 | query = reference[:9] + 'A' + reference[10:] 78 | aligner = ssw.Aligner() 79 | al = aligner.align(query, reference) 80 | self.assertEqual(al.match_count, 19) 81 | self.assertEqual(al.mismatch_count, 1) 82 | self.assertEqual(al.insertion_count, 0) 83 | self.assertEqual(al.deletion_count, 0) 84 | self.assertEqual(al.cigar, "20M") 85 | 86 | def test_deletion(self): 87 | reference = "GTGCGATGTGCGATGAGATC" 88 | query = reference[:10] + reference[11:] 89 | aligner = ssw.Aligner() 90 | al = aligner.align(query, reference) 91 | self.assertEqual(al.match_count, 19) 92 | self.assertEqual(al.mismatch_count, 0) 93 | self.assertEqual(al.insertion_count, 0) 94 | self.assertEqual(al.deletion_count, 1) 95 | self.assertEqual(al.cigar, "10M1D9M") 96 | 97 | def test_coverage(self): 98 | bplen = 5 99 | reference = "GTGCGATGTGCGATGAGATC" 100 | query = reference[bplen:bplen*2] 101 | aligner = ssw.Aligner() 102 | al = aligner.align(query, reference) 103 | self.assertEqual(al.match_count, bplen) 104 | self.assertEqual(al.mismatch_count, 0) 105 | self.assertEqual(al.insertion_count, 0) 106 | self.assertEqual(al.deletion_count, 0) 107 | self.assertEqual(al.cigar, '%dM' % bplen) 108 | self.assertEqual(al.query_coverage, 1.0) 109 | self.assertEqual(al.reference_coverage, bplen / len(reference)) 110 | 111 | def test_degen_alignment(self): 112 | # XXX: note, this fails if seq_1 and seq_2 are switched 113 | # see: https://github.com/mengyao/Complete-Striped-Smith-Waterman-Library/issues/63 114 | seq_1 = "AGCGATCACGT" 115 | seq_2 = "MRYSWKBDHVN" 116 | aligner = ssw.Aligner() 117 | al = aligner.align(seq_1, seq_2) 118 | self.assertEqual(al.match_count, len(seq_1)) 119 | self.assertEqual(al.mismatch_count, 0) 120 | self.assertEqual(al.insertion_count, 0) 121 | self.assertEqual(al.deletion_count, 0) 122 | self.assertEqual(al.cigar, '%dM' % len(seq_1)) 123 | 124 | def test_issue_1(self): 125 | # https://github.com/vishnubob/ssw/issues/1 126 | reference = "CCC" + "AGCT" * 10 127 | query = "AGGT" * 10 128 | aligner = ssw.Aligner() 129 | alignment = aligner.align(query, reference) 130 | (r_line, m_line, q_line) = alignment.alignment 131 | self.assertEqual(r_line, "AGCTAGCTAGCTAGCTAGCTAGCTAGCTAGCTAGCTAG") 132 | self.assertEqual(m_line, "||*|||*|||*|||*|||*|||*|||*|||*|||*|||") 133 | self.assertEqual(q_line, "AGGTAGGTAGGTAGGTAGGTAGGTAGGTAGGTAGGTAG") 134 | 135 | def test_issue_3(self): 136 | # https://github.com/vishnubob/ssw/issues/3 137 | self.assertRaises(ValueError, ssw.Aligner, gap_open=1, gap_extend=2) 138 | self.assertRaises(ValueError, ssw.Aligner, gap_open=1, gap_extend=1) 139 | 140 | if __name__ == '__main__': 141 | unittest.main() 142 | -------------------------------------------------------------------------------- /src/ssw/ssw.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ssw.h 3 | * 4 | * Created by Mengyao Zhao on 6/22/10. 5 | * Copyright 2010 Boston College. All rights reserved. 6 | * Version 1.2.3 7 | * Last revision by Mengyao Zhao on 11/29/16. 8 | * 9 | */ 10 | 11 | #ifndef SSW_H 12 | #define SSW_H 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif // __cplusplus 22 | 23 | #define MAPSTR "MIDNSHP=X" 24 | #ifndef BAM_CIGAR_SHIFT 25 | #define BAM_CIGAR_SHIFT 4u 26 | #endif 27 | 28 | extern const uint8_t encoded_ops[]; 29 | 30 | /*! @typedef structure of the query profile */ 31 | struct _profile; 32 | typedef struct _profile s_profile; 33 | 34 | /*! @typedef structure of the alignment result 35 | @field score1 the best alignment score 36 | @field score2 sub-optimal alignment score 37 | @field ref_begin1 0-based best alignment beginning position on reference; ref_begin1 = -1 when the best alignment beginning 38 | position is not available 39 | @field ref_end1 0-based best alignment ending position on reference 40 | @field read_begin1 0-based best alignment beginning position on read; read_begin1 = -1 when the best alignment beginning 41 | position is not available 42 | @field read_end1 0-based best alignment ending position on read 43 | @field read_end2 0-based sub-optimal alignment ending position on read 44 | @field cigar best alignment cigar; stored the same as that in BAM format, high 28 bits: length, low 4 bits: M/I/D (0/1/2); 45 | cigar = 0 when the best alignment path is not available 46 | @field cigarLen length of the cigar string; cigarLen = 0 when the best alignment path is not available 47 | */ 48 | typedef struct { 49 | uint16_t score1; 50 | uint16_t score2; 51 | int32_t ref_begin1; 52 | int32_t ref_end1; 53 | int32_t read_begin1; 54 | int32_t read_end1; 55 | int32_t ref_end2; 56 | uint32_t* cigar; 57 | int32_t cigarLen; 58 | } s_align; 59 | 60 | /*! @function Create the query profile using the query sequence. 61 | @param read pointer to the query sequence; the query sequence needs to be numbers 62 | @param readLen length of the query sequence 63 | @param mat pointer to the substitution matrix; mat needs to be corresponding to the read sequence 64 | @param n the square root of the number of elements in mat (mat has n*n elements) 65 | @param score_size estimated Smith-Waterman score; if your estimated best alignment score is surely < 255 please set 0; if 66 | your estimated best alignment score >= 255, please set 1; if you don't know, please set 2 67 | @return pointer to the query profile structure 68 | @note example for parameter read and mat: 69 | If the query sequence is: ACGTATC, the sequence that read points to can be: 1234142 70 | Then if the penalty for match is 2 and for mismatch is -2, the substitution matrix of parameter mat will be: 71 | //A C G T 72 | 2 -2 -2 -2 //A 73 | -2 2 -2 -2 //C 74 | -2 -2 2 -2 //G 75 | -2 -2 -2 2 //T 76 | mat is the pointer to the array {2, -2, -2, -2, -2, 2, -2, -2, -2, -2, 2, -2, -2, -2, -2, 2} 77 | */ 78 | s_profile* ssw_init (const int8_t* read, const int32_t readLen, const int8_t* mat, const int32_t n, const int8_t score_size); 79 | 80 | /*! @function Release the memory allocated by function ssw_init. 81 | @param p pointer to the query profile structure 82 | */ 83 | void init_destroy (s_profile* p); 84 | 85 | // @function ssw alignment. 86 | /*! @function Do Striped Smith-Waterman alignment. 87 | @param prof pointer to the query profile structure 88 | @param ref pointer to the target sequence; the target sequence needs to be numbers and corresponding to the mat parameter of 89 | function ssw_init 90 | @param refLen length of the target sequence 91 | @param weight_gapO the absolute value of gap open penalty 92 | @param weight_gapE the absolute value of gap extension penalty 93 | @param flag bitwise FLAG; (from high to low) bit 5: when setted as 1, function ssw_align will return the best alignment 94 | beginning position; bit 6: when setted as 1, if (ref_end1 - ref_begin1 < filterd && read_end1 - read_begin1 95 | < filterd), (whatever bit 5 is setted) the function will return the best alignment beginning position and 96 | cigar; bit 7: when setted as 1, if the best alignment score >= filters, (whatever bit 5 is setted) the function 97 | will return the best alignment beginning position and cigar; bit 8: when setted as 1, (whatever bit 5, 6 or 7 is 98 | setted) the function will always return the best alignment beginning position and cigar. When flag == 0, only 99 | the optimal and sub-optimal scores and the optimal alignment ending position will be returned. 100 | @param filters score filter: when bit 7 of flag is setted as 1 and bit 8 is setted as 0, filters will be used (Please check the 101 | decription of the flag parameter for detailed usage.) 102 | @param filterd distance filter: when bit 6 of flag is setted as 1 and bit 8 is setted as 0, filterd will be used (Please check 103 | the decription of the flag parameter for detailed usage.) 104 | @param maskLen The distance between the optimal and suboptimal alignment ending position >= maskLen. We suggest to use 105 | readLen/2, if you don't have special concerns. Note: maskLen has to be >= 15, otherwise this function will NOT 106 | return the suboptimal alignment information. Detailed description of maskLen: After locating the optimal 107 | alignment ending position, the suboptimal alignment score can be heuristically found by checking the second 108 | largest score in the array that contains the maximal score of each column of the SW matrix. In order to avoid 109 | picking the scores that belong to the alignments sharing the partial best alignment, SSW C library masks the 110 | reference loci nearby (mask length = maskLen) the best alignment ending position and locates the second largest 111 | score from the unmasked elements. 112 | @return pointer to the alignment result structure 113 | @note Whatever the parameter flag is setted, this function will at least return the optimal and sub-optimal alignment score, 114 | and the optimal alignment ending positions on target and query sequences. If both bit 6 and 7 of the flag are setted 115 | while bit 8 is not, the function will return cigar only when both criteria are fulfilled. All returned positions are 116 | 0-based coordinate. 117 | */ 118 | s_align* ssw_align (const s_profile* prof, 119 | const int8_t* ref, 120 | int32_t refLen, 121 | const uint8_t weight_gapO, 122 | const uint8_t weight_gapE, 123 | const uint8_t flag, 124 | const uint16_t filters, 125 | const int32_t filterd, 126 | const int32_t maskLen); 127 | 128 | /*! @function Release the memory allocated by function ssw_align. 129 | @param a pointer to the alignment result structure 130 | */ 131 | void align_destroy (s_align* a); 132 | 133 | /*! @function: 134 | 1. Calculate the number of mismatches. 135 | 2. Modify the cigar string: 136 | differentiate matches (=), mismatches(X), and softclip(S). 137 | @param ref_begin1 0-based best alignment beginning position on the reference sequence 138 | @param read_begin1 0-based best alignment beginning position on the read sequence 139 | @param read_end1 0-based best alignment ending position on the read sequence 140 | @param ref pointer to the reference sequence 141 | @param read pointer to the read sequence 142 | @param readLen length of the read 143 | @param cigar best alignment cigar; stored the same as that in BAM format, high 28 bits: length, low 4 bits: M/I/D (0/1/2) 144 | @param cigarLen length of the cigar string 145 | @return: 146 | The number of mismatches. 147 | The cigar and cigarLen are modified. 148 | */ 149 | int32_t mark_mismatch (int32_t ref_begin1, 150 | int32_t read_begin1, 151 | int32_t read_end1, 152 | const int8_t* ref, 153 | const int8_t* read, 154 | int32_t readLen, 155 | uint32_t** cigar, 156 | int32_t* cigarLen); 157 | 158 | /*! @function Produce CIGAR 32-bit unsigned integer from CIGAR operation and CIGAR length 159 | @param length length of CIGAR 160 | @param op_letter CIGAR operation character ('M', 'I', etc) 161 | @return 32-bit unsigned integer, representing encoded CIGAR operation and length 162 | */ 163 | static inline uint32_t to_cigar_int (uint32_t length, char op_letter) { 164 | return (length << BAM_CIGAR_SHIFT) | (encoded_ops[(int)op_letter]); 165 | } 166 | 167 | /*! @function Extract CIGAR operation character from CIGAR 32-bit unsigned integer 168 | @param cigar_int 32-bit unsigned integer, representing encoded CIGAR operation and length 169 | @return CIGAR operation character ('M', 'I', etc) 170 | */ 171 | //char cigar_int_to_op (uint32_t cigar_int); 172 | //static inline char cigar_int_to_op(uint32_t cigar_int) { 173 | char cigar_int_to_op(uint32_t cigar_int) { 174 | return (cigar_int & 0xfU) > 8 ? 'M': MAPSTR[cigar_int & 0xfU]; 175 | } 176 | 177 | /*! @function Extract length of a CIGAR operation from CIGAR 32-bit unsigned integer 178 | @param cigar_int 32-bit unsigned integer, representing encoded CIGAR operation and length 179 | @return length of CIGAR operation 180 | */ 181 | //static inline uint32_t cigar_int_to_len (uint32_t cigar_int) { 182 | uint32_t cigar_int_to_len (uint32_t cigar_int) { 183 | return cigar_int >> BAM_CIGAR_SHIFT; 184 | } 185 | 186 | #ifdef __cplusplus 187 | } 188 | #endif // __cplusplus 189 | 190 | #endif // SSW_H 191 | -------------------------------------------------------------------------------- /src/sswobj.py: -------------------------------------------------------------------------------- 1 | import six 2 | from six.moves import range 3 | from . import libssw 4 | from . import iupac 5 | 6 | __all__ = ["ScoreMatrix", "NucleotideScoreMatrix", "Aligner", "Alignment"] 7 | 8 | class ScoreMatrix(object): 9 | def __init__(self, alphabet=None, match=2, mismatch=-2): 10 | self._match = match 11 | self._mismatch = mismatch 12 | self.alphabet = alphabet 13 | 14 | def __getstate__(self): 15 | state = (self._match, self._mismatch, self.alphabet) 16 | return state 17 | 18 | def __setstate__(self, state): 19 | (self._match, self._mismatch, self.alphabet) = state 20 | 21 | def __eq__(self, other): 22 | return \ 23 | (self._match == other._match) and \ 24 | (self._mismatch == other._mismatch) and \ 25 | (self._alphabet == other._alphabet) 26 | 27 | def get_mismatch(self): 28 | return self._mismatch 29 | def set_mismatch(self, val): 30 | self._mismatch = val 31 | self._init_matrix() 32 | mismatch = property(get_mismatch, set_mismatch) 33 | 34 | def get_match(self): 35 | return self._match 36 | def set_match(self, val): 37 | self._match = val 38 | self._init_matrix() 39 | match = property(get_match, set_match) 40 | 41 | def get_alphabet(self): 42 | return self._alphabet 43 | 44 | def set_alphabet(self, alphabet): 45 | self._alphabet = tuple(alphabet) if alphabet else tuple() 46 | self.symbol_map = {symbol.upper(): idx for (idx, symbol) in enumerate(self._alphabet)} 47 | self._init_matrix() 48 | alphabet = property(get_alphabet, set_alphabet) 49 | 50 | def _init_matrix(self): 51 | _matrix_type = libssw.matrix_type * (len(self.alphabet) ** 2) 52 | self._matrix = _matrix_type(*self.iter_matrix()) 53 | 54 | def iter_matrix(self): 55 | for row_symbol in self.alphabet: 56 | for col_symbol in self.alphabet: 57 | yield self._get_score(row_symbol, col_symbol) 58 | 59 | def _get_score(self, symbol_1, symbol_2): 60 | return (self.match if self.test_match(symbol_1, symbol_2) else self.mismatch) 61 | 62 | def test_match(self, symbol_1, symbol_2): 63 | return symbol_1.upper() == symbol_2.upper() 64 | 65 | def convert_sequence_to_ints(self, seq): 66 | seq = seq.upper() 67 | _seq_type = libssw.symbol_type * len(seq) 68 | _seq_instance = _seq_type() 69 | for (idx, symbol) in enumerate(seq): 70 | _seq_instance[idx] = self.symbol_map[symbol] 71 | return _seq_instance 72 | 73 | class NucleotideScoreMatrix(ScoreMatrix): 74 | def __init__(self, alphabet=None, **kw): 75 | alphabet = alphabet if alphabet is not None else iupac.NucleotideAlphabet 76 | super(NucleotideScoreMatrix, self).__init__(alphabet=alphabet, **kw) 77 | 78 | def test_match(self, symbol_1, symbol_2): 79 | _sym_1 = symbol_1.upper() 80 | _sym_2 = symbol_2.upper() 81 | if _sym_1 == _sym_2: 82 | return True 83 | if _sym_1 in iupac.NucleotideTable: 84 | matches = iupac.NucleotideTable[_sym_1]["matches"] 85 | return (_sym_2 in matches) 86 | return super(NucleotideScoreMatrix, self).test_match(symbol_1, symbol_2) 87 | 88 | class Aligner(object): 89 | def __init__(self, reference=None, matrix=None, molecule="dna", gap_open=3, gap_extend=1): 90 | self.reference = reference 91 | self.matrix = matrix 92 | self.molecule = molecule 93 | if self.matrix == None and molecule != None: 94 | if molecule == "dna": 95 | self.matrix = NucleotideScoreMatrix() 96 | else: 97 | raise ValueError("Unrecognized molecule type '%s'" % molecule) 98 | self.gap_open = gap_open 99 | self.gap_extend = gap_extend 100 | if self.gap_open <= self.gap_extend: 101 | raise ValueError("gap_open must always be greater than gap_extend") 102 | 103 | def align(self, query='', reference=None, revcomp=True): 104 | # XXX: I really don't find this part of SSW useful, which 105 | # is why i broke alignment into two stages, so you can use 106 | # the low level interface if you wish. 107 | filter_score = 0 108 | filter_distance = 0 109 | flags = 1 110 | mask_length = max(15, len(query) // 2) 111 | reference = reference if reference != None else self.reference 112 | res = self._align(query, reference, flags, filter_score, filter_distance, mask_length) 113 | if revcomp: 114 | query_rc = iupac.nucleotide_reverse_complement(query) 115 | res_rc = self._align(query_rc, reference, flags, filter_score, filter_distance, mask_length) 116 | if res_rc.score > res.score: 117 | res = res_rc 118 | return res 119 | 120 | def _align(self, query, reference, flags, filter_score, filter_distance, mask_length, score_size=2): 121 | _query = self.matrix.convert_sequence_to_ints(query) 122 | _reference = self.matrix.convert_sequence_to_ints(reference) 123 | profile = libssw.ssw_profile_init(_query, len(query), self.matrix._matrix, len(self.matrix.alphabet), score_size) 124 | if self.gap_open <= self.gap_extend: 125 | raise ValueError("gap_open must always be greater than gap_extend") 126 | alignment = libssw.ssw_align_init(profile, _reference, len(_reference), self.gap_open, self.gap_extend, flags, filter_score, filter_distance, mask_length) 127 | alignment_instance = Alignment(alignment, query, reference, self.matrix) 128 | libssw.ssw_profile_del(profile) 129 | libssw.ssw_align_del(alignment) 130 | return alignment_instance 131 | 132 | class Alignment(object): 133 | def __init__ (self, alignment, query, reference, matrix=None): 134 | self.score = alignment.contents.score 135 | self.score2 = alignment.contents.score2 136 | self.reference = reference 137 | self.reference_begin = alignment.contents.ref_begin 138 | self.reference_end = alignment.contents.ref_end 139 | self.query = query 140 | self.query_begin = alignment.contents.query_begin 141 | self.query_end = alignment.contents.query_end 142 | self.query_coverage = (self.query_end - self.query_begin + 1) / len(self.query) 143 | self.reference_coverage = (self.reference_end - self.reference_begin + 1) / len(self.reference) 144 | self.matrix = matrix 145 | self._cigar_string = [alignment.contents.cigar[idx] for idx in range(alignment.contents.cigarLen)] 146 | 147 | @property 148 | def iter_cigar(self): 149 | for val in self._cigar_string: 150 | op_len = libssw.cigar_int_to_len(val) 151 | op_char = libssw.cigar_int_to_op(val).decode("latin") 152 | yield (op_len, op_char) 153 | 154 | @property 155 | def cigar(self): 156 | cigar = "" 157 | if self.query_begin > 0: 158 | cigar += str(self.query_begin) + "S" 159 | cigar += str.join('', (str.join('', map(str, cstr)) for cstr in self.iter_cigar)) 160 | end_len = len(self.query) - self.query_end - 1 161 | if end_len != 0: 162 | cigar += str(end_len) + "S" 163 | return cigar 164 | 165 | @property 166 | def alignment(self): 167 | r_index = 0 168 | q_index = 0 169 | r_seq = self.reference[self.reference_begin: self.reference_end + 1] 170 | q_seq = self.query[self.query_begin: self.query_end + 1] 171 | r_line = m_line = q_line = '' 172 | match_flag = lambda rq: '|' if self.matrix.test_match(*rq) else '*' 173 | for (op_len, op_char) in self.iter_cigar: 174 | op_len = int(op_len) 175 | if op_char.upper() == 'M': 176 | # match between reference and query 177 | ref_chunk = r_seq[r_index: r_index + op_len] 178 | query_chunk = q_seq[q_index: q_index + op_len] 179 | r_line += ref_chunk 180 | q_line += query_chunk 181 | match_seq = str.join('', map(match_flag, zip(ref_chunk, query_chunk))) 182 | m_line += match_seq 183 | r_index += op_len 184 | q_index += op_len 185 | elif op_char.upper() == 'I': 186 | # insertion into reference 187 | r_line += '-' * op_len 188 | m_line += ' ' * op_len 189 | q_line += q_seq[q_index: q_index + op_len] 190 | # only query index change 191 | q_index += op_len 192 | elif op_char.upper() == 'D': 193 | # deletion from reference 194 | r_line += r_seq[r_index: r_index + op_len] 195 | m_line += ' ' * op_len 196 | q_line += '-' * op_len 197 | # only ref index change 198 | r_index += op_len 199 | return (r_line, m_line, q_line) 200 | 201 | # XXX: all of these count functions are ineffecient 202 | 203 | @property 204 | def match_count(self): 205 | return self.alignment[1].count('|') 206 | 207 | @property 208 | def mismatch_count(self): 209 | return self.alignment[1].count('*') 210 | 211 | @property 212 | def insertion_count(self): 213 | cnt = 0 214 | for (op_len, op_char) in self.iter_cigar: 215 | if op_char.upper() == 'I': 216 | cnt += op_len 217 | return cnt 218 | 219 | @property 220 | def deletion_count(self): 221 | cnt = 0 222 | for (op_len, op_char) in self.iter_cigar: 223 | if op_char.upper() == 'D': 224 | cnt += op_len 225 | return cnt 226 | 227 | def alignment_report(self, width=80, header=True): 228 | def window(lines, width): 229 | idx = 0 230 | while 1: 231 | res = [] 232 | for line in lines: 233 | res.append(line[idx:idx+width]) 234 | if not any(res): 235 | break 236 | yield (res, idx) 237 | idx += width 238 | 239 | margin_width = len(str(max(self.query_end, self.reference_end))) + 8 240 | rpt = '' 241 | if header: 242 | rpt += "Score = %s, Matches = %s, Mismatches = %s, Insertions = %s, Deletions = %s\n" % (self.score, self.match_count, self.mismatch_count, self.insertion_count, self.deletion_count) 243 | rpt += '\n' 244 | for (lines, offset) in window(self.alignment, width - margin_width): 245 | for (name, seq_offset, line) in zip(["ref", "", "query"], [self.reference_begin, None, self.query_begin], lines): 246 | if name: 247 | line_offset = seq_offset + offset + 1 248 | left_margin = "%s %s" % (name.ljust(5), line_offset) 249 | else: 250 | left_margin = "" 251 | rpt += "%s%s\n" % (left_margin.ljust(margin_width), line) 252 | rpt += '\n' 253 | return rpt 254 | -------------------------------------------------------------------------------- /src/ssw/ssw.c: -------------------------------------------------------------------------------- 1 | /* The MIT License 2 | 3 | Copyright (c) 2012-2015 Boston College. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | /* The 2-clause BSD License 27 | 28 | Copyright 2006 Michael Farrar. 29 | 30 | Redistribution and use in source and binary forms, with or without 31 | modification, are permitted provided that the following conditions are 32 | met: 33 | 34 | 1. Redistributions of source code must retain the above copyright 35 | notice, this list of conditions and the following disclaimer. 36 | 37 | 2. Redistributions in binary form must reproduce the above copyright 38 | notice, this list of conditions and the following disclaimer in the 39 | documentation and/or other materials provided with the distribution. 40 | 41 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 42 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 43 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 44 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 45 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 46 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 47 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 48 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 49 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 50 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 51 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 52 | */ 53 | 54 | /* 55 | * ssw.c 56 | * 57 | * Created by Mengyao Zhao on 6/22/10. 58 | * Copyright 2010 Boston College. All rights reserved. 59 | * Version 1.2.4 60 | * Last revision by Mengyao Zhao on 2019-03-04. 61 | * 62 | * The lazy-F loop implementation was derived from SWPS3, which is 63 | * MIT licensed under ETH Zürich, Institute of Computational Science. 64 | * 65 | * The core SW loop referenced the swsse2 implementation, which is 66 | * BSD licensed under Micharl Farrar. 67 | */ 68 | 69 | //#include 70 | #include 71 | #include 72 | #include 73 | #include 74 | #include 75 | #include 76 | #include "ssw.h" 77 | 78 | #ifdef __GNUC__ 79 | #define LIKELY(x) __builtin_expect((x),1) 80 | #define UNLIKELY(x) __builtin_expect((x),0) 81 | #else 82 | #define LIKELY(x) (x) 83 | #define UNLIKELY(x) (x) 84 | #endif 85 | 86 | /* Convert the coordinate in the scoring matrix into the coordinate in one line of the band. */ 87 | #define set_u(u, w, i, j) { int x=(i)-(w); x=x>0?x:0; (u)=(j)-x+1; } 88 | 89 | /* Convert the coordinate in the direction matrix into the coordinate in one line of the band. */ 90 | #define set_d(u, w, i, j, p) { int x=(i)-(w); x=x>0?x:0; x=(j)-x; (u)=x*3+p; } 91 | 92 | /*! @function 93 | @abstract Round an integer to the next closest power-2 integer. 94 | @param x integer to be rounded (in place) 95 | @discussion x will be modified. 96 | */ 97 | #define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) 98 | 99 | typedef struct { 100 | uint16_t score; 101 | int32_t ref; //0-based position 102 | int32_t read; //alignment ending position on read, 0-based 103 | } alignment_end; 104 | 105 | typedef struct { 106 | uint32_t* seq; 107 | int32_t length; 108 | } cigar; 109 | 110 | struct _profile{ 111 | __m128i* profile_byte; // 0: none 112 | __m128i* profile_word; // 0: none 113 | const int8_t* read; 114 | const int8_t* mat; 115 | int32_t readLen; 116 | int32_t n; 117 | uint8_t bias; 118 | }; 119 | 120 | /* array index is an ASCII character value from a CIGAR, 121 | element value is the corresponding integer opcode between 0 and 8 */ 122 | const uint8_t encoded_ops[] = { 123 | 0, 0, 0, 0, 124 | 0, 0, 0, 0, 125 | 0, 0, 0, 0, 126 | 0, 0, 0, 0, 127 | 0, 0, 0, 0, 128 | 0, 0, 0, 0, 129 | 0, 0, 0, 0, 130 | 0, 0, 0, 0, 131 | 0 /* */, 0 /* ! */, 0 /* " */, 0 /* # */, 132 | 0 /* $ */, 0 /* % */, 0 /* & */, 0 /* ' */, 133 | 0 /* ( */, 0 /* ) */, 0 /* * */, 0 /* + */, 134 | 0 /* , */, 0 /* - */, 0 /* . */, 0 /* / */, 135 | 0 /* 0 */, 0 /* 1 */, 0 /* 2 */, 0 /* 3 */, 136 | 0 /* 4 */, 0 /* 5 */, 0 /* 6 */, 0 /* 7 */, 137 | 0 /* 8 */, 0 /* 9 */, 0 /* : */, 0 /* ; */, 138 | 0 /* < */, 7 /* = */, 0 /* > */, 0 /* ? */, 139 | 0 /* @ */, 0 /* A */, 0 /* B */, 0 /* C */, 140 | 2 /* D */, 0 /* E */, 0 /* F */, 0 /* G */, 141 | 5 /* H */, 1 /* I */, 0 /* J */, 0 /* K */, 142 | 0 /* L */, 0 /* M */, 3 /* N */, 0 /* O */, 143 | 6 /* P */, 0 /* Q */, 0 /* R */, 4 /* S */, 144 | 0 /* T */, 0 /* U */, 0 /* V */, 0 /* W */, 145 | 8 /* X */, 0 /* Y */, 0 /* Z */, 0 /* [ */, 146 | 0 /* \ */, 0 /* ] */, 0 /* ^ */, 0 /* _ */, 147 | 0 /* ` */, 0 /* a */, 0 /* b */, 0 /* c */, 148 | 0 /* d */, 0 /* e */, 0 /* f */, 0 /* g */, 149 | 0 /* h */, 0 /* i */, 0 /* j */, 0 /* k */, 150 | 0 /* l */, 0 /* m */, 0 /* n */, 0 /* o */, 151 | 0 /* p */, 0 /* q */, 0 /* r */, 0 /* s */, 152 | 0 /* t */, 0 /* u */, 0 /* v */, 0 /* w */, 153 | 0 /* x */, 0 /* y */, 0 /* z */, 0 /* { */, 154 | 0 /* | */, 0 /* } */, 0 /* ~ */, 0 /* */ 155 | }; 156 | 157 | /* Generate query profile rearrange query sequence & calculate the weight of match/mismatch. */ 158 | static __m128i* qP_byte (const int8_t* read_num, 159 | const int8_t* mat, 160 | const int32_t readLen, 161 | const int32_t n, /* the edge length of the squre matrix mat */ 162 | uint8_t bias) { 163 | 164 | int32_t segLen = (readLen + 15) / 16; /* Split the 128 bit register into 16 pieces. 165 | Each piece is 8 bit. Split the read into 16 segments. 166 | Calculat 16 segments in parallel. 167 | */ 168 | __m128i* vProfile = (__m128i*)malloc(n * segLen * sizeof(__m128i)); 169 | int8_t* t = (int8_t*)vProfile; 170 | int32_t nt, i, j, segNum; 171 | 172 | /* Generate query profile rearrange query sequence & calculate the weight of match/mismatch */ 173 | for (nt = 0; LIKELY(nt < n); nt ++) { 174 | for (i = 0; i < segLen; i ++) { 175 | j = i; 176 | for (segNum = 0; LIKELY(segNum < 16) ; segNum ++) { 177 | *t++ = j>= readLen ? bias : mat[nt * n + read_num[j]] + bias; 178 | j += segLen; 179 | } 180 | } 181 | } 182 | return vProfile; 183 | } 184 | 185 | /* Striped Smith-Waterman 186 | Record the highest score of each reference position. 187 | Return the alignment score and ending position of the best alignment, 2nd best alignment, etc. 188 | Gap begin and gap extension are different. 189 | wight_match > 0, all other weights < 0. 190 | The returned positions are 0-based. 191 | */ 192 | static alignment_end* sw_sse2_byte (const int8_t* ref, 193 | int8_t ref_dir, // 0: forward ref; 1: reverse ref 194 | int32_t refLen, 195 | int32_t readLen, 196 | const uint8_t weight_gapO, /* will be used as - */ 197 | const uint8_t weight_gapE, /* will be used as - */ 198 | const __m128i* vProfile, 199 | uint8_t terminate, /* the best alignment score: used to terminate 200 | the matrix calculation when locating the 201 | alignment beginning point. If this score 202 | is set to 0, it will not be used */ 203 | uint8_t bias, /* Shift 0 point to a positive value. */ 204 | int32_t maskLen) { 205 | 206 | // Put the largest number of the 16 numbers in vm into m. 207 | #define max16(m, vm) (vm) = _mm_max_epu8((vm), _mm_srli_si128((vm), 8)); \ 208 | (vm) = _mm_max_epu8((vm), _mm_srli_si128((vm), 4)); \ 209 | (vm) = _mm_max_epu8((vm), _mm_srli_si128((vm), 2)); \ 210 | (vm) = _mm_max_epu8((vm), _mm_srli_si128((vm), 1)); \ 211 | (m) = _mm_extract_epi16((vm), 0) 212 | 213 | uint8_t max = 0; /* the max alignment score */ 214 | int32_t end_read = readLen - 1; 215 | int32_t end_ref = -1; /* 0_based best alignment ending point; Initialized as isn't aligned -1. */ 216 | int32_t segLen = (readLen + 15) / 16; /* number of segment */ 217 | 218 | /* array to record the largest score of each reference position */ 219 | uint8_t* maxColumn = (uint8_t*) calloc(refLen, 1); 220 | 221 | /* array to record the alignment read ending position of the largest score of each reference position */ 222 | int32_t* end_read_column = (int32_t*) calloc(refLen, sizeof(int32_t)); 223 | 224 | /* Define 16 byte 0 vector. */ 225 | __m128i vZero = _mm_set1_epi32(0); 226 | 227 | __m128i* pvHStore = (__m128i*) calloc(segLen, sizeof(__m128i)); 228 | __m128i* pvHLoad = (__m128i*) calloc(segLen, sizeof(__m128i)); 229 | __m128i* pvE = (__m128i*) calloc(segLen, sizeof(__m128i)); 230 | __m128i* pvHmax = (__m128i*) calloc(segLen, sizeof(__m128i)); 231 | 232 | int32_t i, j, k; 233 | /* 16 byte insertion begin vector */ 234 | __m128i vGapO = _mm_set1_epi8(weight_gapO); 235 | 236 | /* 16 byte insertion extension vector */ 237 | __m128i vGapE = _mm_set1_epi8(weight_gapE); 238 | 239 | /* 16 byte bias vector */ 240 | __m128i vBias = _mm_set1_epi8(bias); 241 | 242 | __m128i vMaxScore = vZero; /* Trace the highest score of the whole SW matrix. */ 243 | __m128i vMaxMark = vZero; /* Trace the highest score till the previous column. */ 244 | __m128i vTemp; 245 | int32_t edge, begin = 0, end = refLen, step = 1; 246 | 247 | /* outer loop to process the reference sequence */ 248 | if (ref_dir == 1) { 249 | begin = refLen - 1; 250 | end = -1; 251 | step = -1; 252 | } 253 | for (i = begin; LIKELY(i != end); i += step) { 254 | int32_t cmp; 255 | __m128i e, vF = vZero, vMaxColumn = vZero; /* Initialize F value to 0. 256 | Any errors to vH values will be corrected in the Lazy_F loop. 257 | */ 258 | 259 | __m128i vH = pvHStore[segLen - 1]; 260 | vH = _mm_slli_si128 (vH, 1); /* Shift the 128-bit value in vH left by 1 byte. */ 261 | const __m128i* vP = vProfile + ref[i] * segLen; /* Right part of the vProfile */ 262 | 263 | /* Swap the 2 H buffers. */ 264 | __m128i* pv = pvHLoad; 265 | pvHLoad = pvHStore; 266 | pvHStore = pv; 267 | 268 | /* inner loop to process the query sequence */ 269 | for (j = 0; LIKELY(j < segLen); ++j) { 270 | vH = _mm_adds_epu8(vH, _mm_load_si128(vP + j)); 271 | vH = _mm_subs_epu8(vH, vBias); /* vH will be always > 0 */ 272 | 273 | /* Get max from vH, vE and vF. */ 274 | e = _mm_load_si128(pvE + j); 275 | vH = _mm_max_epu8(vH, e); 276 | vH = _mm_max_epu8(vH, vF); 277 | vMaxColumn = _mm_max_epu8(vMaxColumn, vH); 278 | 279 | /* Save vH values. */ 280 | _mm_store_si128(pvHStore + j, vH); 281 | 282 | /* Update vE value. */ 283 | vH = _mm_subs_epu8(vH, vGapO); /* saturation arithmetic, result >= 0 */ 284 | e = _mm_subs_epu8(e, vGapE); 285 | e = _mm_max_epu8(e, vH); 286 | _mm_store_si128(pvE + j, e); 287 | 288 | /* Update vF value. */ 289 | vF = _mm_subs_epu8(vF, vGapE); 290 | vF = _mm_max_epu8(vF, vH); 291 | 292 | /* Load the next vH. */ 293 | vH = _mm_load_si128(pvHLoad + j); 294 | } 295 | 296 | /* Lazy_F loop: has been revised to disallow adjecent insertion and then deletion, so don't update E(i, j), learn from SWPS3 */ 297 | for (k = 0; LIKELY(k < 16); ++k) { 298 | vF = _mm_slli_si128 (vF, 1); 299 | for (j = 0; LIKELY(j < segLen); ++j) { 300 | vH = _mm_load_si128(pvHStore + j); 301 | vH = _mm_max_epu8(vH, vF); 302 | vMaxColumn = _mm_max_epu8(vMaxColumn, vH); // newly added line 303 | _mm_store_si128(pvHStore + j, vH); 304 | vH = _mm_subs_epu8(vH, vGapO); 305 | vF = _mm_subs_epu8(vF, vGapE); 306 | if (UNLIKELY(! _mm_movemask_epi8(_mm_cmpgt_epi8(vF, vH)))) goto end; 307 | } 308 | } 309 | 310 | end: 311 | 312 | vMaxScore = _mm_max_epu8(vMaxScore, vMaxColumn); 313 | vTemp = _mm_cmpeq_epi8(vMaxMark, vMaxScore); 314 | cmp = _mm_movemask_epi8(vTemp); 315 | if (cmp != 0xffff) { 316 | uint8_t temp; 317 | vMaxMark = vMaxScore; 318 | max16(temp, vMaxScore); 319 | vMaxScore = vMaxMark; 320 | 321 | if (LIKELY(temp > max)) { 322 | max = temp; 323 | if (max + bias >= 255) break; //overflow 324 | end_ref = i; 325 | 326 | /* Store the column with the highest alignment score in order to trace the alignment ending position on read. */ 327 | for (j = 0; LIKELY(j < segLen); ++j) pvHmax[j] = pvHStore[j]; 328 | } 329 | } 330 | 331 | /* Record the max score of current column. */ 332 | max16(maxColumn[i], vMaxColumn); 333 | if (maxColumn[i] == terminate) break; 334 | } 335 | 336 | /* Trace the alignment ending position on read. */ 337 | uint8_t *t = (uint8_t*)pvHmax; 338 | int32_t column_len = segLen * 16; 339 | for (i = 0; LIKELY(i < column_len); ++i, ++t) { 340 | int32_t temp; 341 | if (*t == max) { 342 | temp = i / 16 + i % 16 * segLen; 343 | if (temp < end_read) end_read = temp; 344 | } 345 | } 346 | 347 | free(pvHmax); 348 | free(pvE); 349 | free(pvHLoad); 350 | free(pvHStore); 351 | 352 | /* Find the most possible 2nd best alignment. */ 353 | alignment_end* bests = (alignment_end*) calloc(2, sizeof(alignment_end)); 354 | bests[0].score = max + bias >= 255 ? 255 : max; 355 | bests[0].ref = end_ref; 356 | bests[0].read = end_read; 357 | 358 | bests[1].score = 0; 359 | bests[1].ref = 0; 360 | bests[1].read = 0; 361 | 362 | edge = (end_ref - maskLen) > 0 ? (end_ref - maskLen) : 0; 363 | for (i = 0; i < edge; i ++) { 364 | if (maxColumn[i] > bests[1].score) { 365 | bests[1].score = maxColumn[i]; 366 | bests[1].ref = i; 367 | } 368 | } 369 | edge = (end_ref + maskLen) > refLen ? refLen : (end_ref + maskLen); 370 | for (i = edge + 1; i < refLen; i ++) { 371 | if (maxColumn[i] > bests[1].score) { 372 | bests[1].score = maxColumn[i]; 373 | bests[1].ref = i; 374 | } 375 | } 376 | 377 | free(maxColumn); 378 | free(end_read_column); 379 | return bests; 380 | } 381 | 382 | static __m128i* qP_word (const int8_t* read_num, 383 | const int8_t* mat, 384 | const int32_t readLen, 385 | const int32_t n) { 386 | 387 | int32_t segLen = (readLen + 7) / 8; 388 | __m128i* vProfile = (__m128i*)malloc(n * segLen * sizeof(__m128i)); 389 | int16_t* t = (int16_t*)vProfile; 390 | int32_t nt, i, j; 391 | int32_t segNum; 392 | 393 | /* Generate query profile rearrange query sequence & calculate the weight of match/mismatch */ 394 | for (nt = 0; LIKELY(nt < n); nt ++) { 395 | for (i = 0; i < segLen; i ++) { 396 | j = i; 397 | for (segNum = 0; LIKELY(segNum < 8) ; segNum ++) { 398 | *t++ = j>= readLen ? 0 : mat[nt * n + read_num[j]]; 399 | j += segLen; 400 | } 401 | } 402 | } 403 | return vProfile; 404 | } 405 | 406 | static alignment_end* sw_sse2_word (const int8_t* ref, 407 | int8_t ref_dir, // 0: forward ref; 1: reverse ref 408 | int32_t refLen, 409 | int32_t readLen, 410 | const uint8_t weight_gapO, /* will be used as - */ 411 | const uint8_t weight_gapE, /* will be used as - */ 412 | const __m128i* vProfile, 413 | uint16_t terminate, 414 | int32_t maskLen) { 415 | 416 | #define max8(m, vm) (vm) = _mm_max_epi16((vm), _mm_srli_si128((vm), 8)); \ 417 | (vm) = _mm_max_epi16((vm), _mm_srli_si128((vm), 4)); \ 418 | (vm) = _mm_max_epi16((vm), _mm_srli_si128((vm), 2)); \ 419 | (m) = _mm_extract_epi16((vm), 0) 420 | 421 | uint16_t max = 0; /* the max alignment score */ 422 | int32_t end_read = readLen - 1; 423 | int32_t end_ref = 0; /* 1_based best alignment ending point; Initialized as isn't aligned - 0. */ 424 | int32_t segLen = (readLen + 7) / 8; /* number of segment */ 425 | 426 | /* array to record the largest score of each reference position */ 427 | uint16_t* maxColumn = (uint16_t*) calloc(refLen, 2); 428 | 429 | /* array to record the alignment read ending position of the largest score of each reference position */ 430 | int32_t* end_read_column = (int32_t*) calloc(refLen, sizeof(int32_t)); 431 | 432 | /* Define 16 byte 0 vector. */ 433 | __m128i vZero = _mm_set1_epi32(0); 434 | 435 | __m128i* pvHStore = (__m128i*) calloc(segLen, sizeof(__m128i)); 436 | __m128i* pvHLoad = (__m128i*) calloc(segLen, sizeof(__m128i)); 437 | __m128i* pvE = (__m128i*) calloc(segLen, sizeof(__m128i)); 438 | __m128i* pvHmax = (__m128i*) calloc(segLen, sizeof(__m128i)); 439 | 440 | int32_t i, j, k; 441 | /* 16 byte insertion begin vector */ 442 | __m128i vGapO = _mm_set1_epi16(weight_gapO); 443 | 444 | /* 16 byte insertion extension vector */ 445 | __m128i vGapE = _mm_set1_epi16(weight_gapE); 446 | 447 | __m128i vMaxScore = vZero; /* Trace the highest score of the whole SW matrix. */ 448 | __m128i vMaxMark = vZero; /* Trace the highest score till the previous column. */ 449 | __m128i vTemp; 450 | int32_t edge, begin = 0, end = refLen, step = 1; 451 | 452 | /* outer loop to process the reference sequence */ 453 | if (ref_dir == 1) { 454 | begin = refLen - 1; 455 | end = -1; 456 | step = -1; 457 | } 458 | for (i = begin; LIKELY(i != end); i += step) { 459 | int32_t cmp; 460 | __m128i e, vF = vZero; /* Initialize F value to 0. 461 | Any errors to vH values will be corrected in the Lazy_F loop. 462 | */ 463 | __m128i vH = pvHStore[segLen - 1]; 464 | vH = _mm_slli_si128 (vH, 2); /* Shift the 128-bit value in vH left by 2 byte. */ 465 | 466 | /* Swap the 2 H buffers. */ 467 | __m128i* pv = pvHLoad; 468 | 469 | __m128i vMaxColumn = vZero; /* vMaxColumn is used to record the max values of column i. */ 470 | 471 | const __m128i* vP = vProfile + ref[i] * segLen; /* Right part of the vProfile */ 472 | pvHLoad = pvHStore; 473 | pvHStore = pv; 474 | 475 | /* inner loop to process the query sequence */ 476 | for (j = 0; LIKELY(j < segLen); j ++) { 477 | vH = _mm_adds_epi16(vH, _mm_load_si128(vP + j)); 478 | 479 | /* Get max from vH, vE and vF. */ 480 | e = _mm_load_si128(pvE + j); 481 | vH = _mm_max_epi16(vH, e); 482 | vH = _mm_max_epi16(vH, vF); 483 | vMaxColumn = _mm_max_epi16(vMaxColumn, vH); 484 | 485 | /* Save vH values. */ 486 | _mm_store_si128(pvHStore + j, vH); 487 | 488 | /* Update vE value. */ 489 | vH = _mm_subs_epu16(vH, vGapO); /* saturation arithmetic, result >= 0 */ 490 | e = _mm_subs_epu16(e, vGapE); 491 | e = _mm_max_epi16(e, vH); 492 | _mm_store_si128(pvE + j, e); 493 | 494 | /* Update vF value. */ 495 | vF = _mm_subs_epu16(vF, vGapE); 496 | vF = _mm_max_epi16(vF, vH); 497 | 498 | /* Load the next vH. */ 499 | vH = _mm_load_si128(pvHLoad + j); 500 | } 501 | 502 | /* Lazy_F loop: has been revised to disallow adjecent insertion and then deletion, so don't update E(i, j), learn from SWPS3 */ 503 | for (k = 0; LIKELY(k < 8); ++k) { 504 | vF = _mm_slli_si128 (vF, 2); 505 | for (j = 0; LIKELY(j < segLen); ++j) { 506 | vH = _mm_load_si128(pvHStore + j); 507 | vH = _mm_max_epi16(vH, vF); 508 | vMaxColumn = _mm_max_epi16(vMaxColumn, vH); //newly added line 509 | _mm_store_si128(pvHStore + j, vH); 510 | vH = _mm_subs_epu16(vH, vGapO); 511 | vF = _mm_subs_epu16(vF, vGapE); 512 | if (UNLIKELY(! _mm_movemask_epi8(_mm_cmpgt_epi16(vF, vH)))) goto end; 513 | } 514 | } 515 | 516 | end: 517 | vMaxScore = _mm_max_epi16(vMaxScore, vMaxColumn); 518 | vTemp = _mm_cmpeq_epi16(vMaxMark, vMaxScore); 519 | cmp = _mm_movemask_epi8(vTemp); 520 | if (cmp != 0xffff) { 521 | uint16_t temp; 522 | vMaxMark = vMaxScore; 523 | max8(temp, vMaxScore); 524 | vMaxScore = vMaxMark; 525 | 526 | if (LIKELY(temp > max)) { 527 | max = temp; 528 | end_ref = i; 529 | for (j = 0; LIKELY(j < segLen); ++j) pvHmax[j] = pvHStore[j]; 530 | } 531 | } 532 | 533 | /* Record the max score of current column. */ 534 | max8(maxColumn[i], vMaxColumn); 535 | if (maxColumn[i] == terminate) break; 536 | } 537 | 538 | /* Trace the alignment ending position on read. */ 539 | uint16_t *t = (uint16_t*)pvHmax; 540 | int32_t column_len = segLen * 8; 541 | for (i = 0; LIKELY(i < column_len); ++i, ++t) { 542 | int32_t temp; 543 | if (*t == max) { 544 | temp = i / 8 + i % 8 * segLen; 545 | if (temp < end_read) end_read = temp; 546 | } 547 | } 548 | 549 | free(pvHmax); 550 | free(pvE); 551 | free(pvHLoad); 552 | free(pvHStore); 553 | 554 | /* Find the most possible 2nd best alignment. */ 555 | alignment_end* bests = (alignment_end*) calloc(2, sizeof(alignment_end)); 556 | bests[0].score = max; 557 | bests[0].ref = end_ref; 558 | bests[0].read = end_read; 559 | 560 | bests[1].score = 0; 561 | bests[1].ref = 0; 562 | bests[1].read = 0; 563 | 564 | edge = (end_ref - maskLen) > 0 ? (end_ref - maskLen) : 0; 565 | for (i = 0; i < edge; i ++) { 566 | if (maxColumn[i] > bests[1].score) { 567 | bests[1].score = maxColumn[i]; 568 | bests[1].ref = i; 569 | } 570 | } 571 | edge = (end_ref + maskLen) > refLen ? refLen : (end_ref + maskLen); 572 | for (i = edge; i < refLen; i ++) { 573 | if (maxColumn[i] > bests[1].score) { 574 | bests[1].score = maxColumn[i]; 575 | bests[1].ref = i; 576 | } 577 | } 578 | 579 | free(maxColumn); 580 | free(end_read_column); 581 | return bests; 582 | } 583 | 584 | static cigar* banded_sw (const int8_t* ref, 585 | const int8_t* read, 586 | int32_t refLen, 587 | int32_t readLen, 588 | int32_t score, 589 | const uint32_t weight_gapO, /* will be used as - */ 590 | const uint32_t weight_gapE, /* will be used as - */ 591 | int32_t band_width, 592 | const int8_t* mat, /* pointer to the weight matrix */ 593 | int32_t n) { 594 | 595 | uint32_t *c = (uint32_t*)malloc(16 * sizeof(uint32_t)), *c1; 596 | int32_t i, j, e, f, temp1, temp2, s = 16, s1 = 8, l, max = 0; 597 | int64_t s2 = 1024; 598 | char op, prev_op; 599 | int32_t width, width_d, *h_b, *e_b, *h_c; 600 | int8_t *direction, *direction_line; 601 | cigar* result = (cigar*)malloc(sizeof(cigar)); 602 | h_b = (int32_t*)malloc(s1 * sizeof(int32_t)); 603 | e_b = (int32_t*)malloc(s1 * sizeof(int32_t)); 604 | h_c = (int32_t*)malloc(s1 * sizeof(int32_t)); 605 | direction = (int8_t*)malloc(s2 * sizeof(int8_t)); 606 | 607 | do { 608 | width = band_width * 2 + 3, width_d = band_width * 2 + 1; 609 | while (width >= s1) { 610 | ++s1; 611 | kroundup32(s1); 612 | h_b = (int32_t*)realloc(h_b, s1 * sizeof(int32_t)); 613 | e_b = (int32_t*)realloc(e_b, s1 * sizeof(int32_t)); 614 | h_c = (int32_t*)realloc(h_c, s1 * sizeof(int32_t)); 615 | } 616 | while (width_d * readLen * 3 >= s2) { 617 | ++s2; 618 | kroundup32(s2); 619 | if (s2 < 0) { 620 | fprintf(stderr, "Alignment score and position are not consensus.\n"); 621 | exit(1); 622 | } 623 | direction = (int8_t*)realloc(direction, s2 * sizeof(int8_t)); 624 | } 625 | direction_line = direction; 626 | for (j = 1; LIKELY(j < width - 1); j ++) h_b[j] = 0; 627 | for (i = 0; LIKELY(i < readLen); i ++) { 628 | int32_t beg = 0, end = refLen - 1, u = 0, edge; 629 | j = i - band_width; beg = beg > j ? beg : j; // band start 630 | j = i + band_width; end = end < j ? end : j; // band end 631 | edge = end + 1 < width - 1 ? end + 1 : width - 1; 632 | f = h_b[0] = e_b[0] = h_b[edge] = e_b[edge] = h_c[0] = 0; 633 | direction_line = direction + width_d * i * 3; 634 | 635 | for (j = beg; LIKELY(j <= end); j ++) { 636 | int32_t b, e1, f1, d, de, df, dh; 637 | set_u(u, band_width, i, j); set_u(e, band_width, i - 1, j); 638 | set_u(b, band_width, i, j - 1); set_u(d, band_width, i - 1, j - 1); 639 | set_d(de, band_width, i, j, 0); 640 | set_d(df, band_width, i, j, 1); 641 | set_d(dh, band_width, i, j, 2); 642 | 643 | temp1 = i == 0 ? -weight_gapO : h_b[e] - weight_gapO; 644 | temp2 = i == 0 ? -weight_gapE : e_b[e] - weight_gapE; 645 | e_b[u] = temp1 > temp2 ? temp1 : temp2; 646 | direction_line[de] = temp1 > temp2 ? 3 : 2; 647 | 648 | temp1 = h_c[b] - weight_gapO; 649 | temp2 = f - weight_gapE; 650 | f = temp1 > temp2 ? temp1 : temp2; 651 | direction_line[df] = temp1 > temp2 ? 5 : 4; 652 | 653 | e1 = e_b[u] > 0 ? e_b[u] : 0; 654 | f1 = f > 0 ? f : 0; 655 | temp1 = e1 > f1 ? e1 : f1; 656 | temp2 = h_b[d] + mat[ref[j] * n + read[i]]; 657 | h_c[u] = temp1 > temp2 ? temp1 : temp2; 658 | 659 | if (h_c[u] > max) max = h_c[u]; 660 | 661 | if (temp1 <= temp2) direction_line[dh] = 1; 662 | else direction_line[dh] = e1 > f1 ? direction_line[de] : direction_line[df]; 663 | } 664 | for (j = 1; j <= u; j ++) h_b[j] = h_c[j]; 665 | } 666 | band_width *= 2; 667 | } while (LIKELY(max < score)); 668 | band_width /= 2; 669 | 670 | // trace back 671 | i = readLen - 1; 672 | j = refLen - 1; 673 | e = 0; // Count the number of M, D or I. 674 | l = 0; // record length of current cigar 675 | op = prev_op = 'M'; 676 | temp2 = 2; // h 677 | while (LIKELY(i > 0) || LIKELY(j > 0)) { 678 | set_d(temp1, band_width, i, j, temp2); 679 | switch (direction_line[temp1]) { 680 | case 1: 681 | --i; 682 | --j; 683 | temp2 = 2; 684 | direction_line -= width_d * 3; 685 | op = 'M'; 686 | break; 687 | case 2: 688 | --i; 689 | temp2 = 0; // e 690 | direction_line -= width_d * 3; 691 | op = 'I'; 692 | break; 693 | case 3: 694 | --i; 695 | temp2 = 2; 696 | direction_line -= width_d * 3; 697 | op = 'I'; 698 | break; 699 | case 4: 700 | --j; 701 | temp2 = 1; 702 | op = 'D'; 703 | break; 704 | case 5: 705 | --j; 706 | temp2 = 2; 707 | op = 'D'; 708 | break; 709 | default: 710 | fprintf(stderr, "Trace back error: %d.\n", direction_line[temp1 - 1]); 711 | free(direction); 712 | free(h_c); 713 | free(e_b); 714 | free(h_b); 715 | free(c); 716 | free(result); 717 | return 0; 718 | } 719 | if (op == prev_op) ++e; 720 | else { 721 | ++l; 722 | while (l >= s) { 723 | ++s; 724 | kroundup32(s); 725 | c = (uint32_t*)realloc(c, s * sizeof(uint32_t)); 726 | } 727 | c[l - 1] = to_cigar_int(e, prev_op); 728 | prev_op = op; 729 | e = 1; 730 | } 731 | } 732 | if (op == 'M') { 733 | ++l; 734 | while (l >= s) { 735 | ++s; 736 | kroundup32(s); 737 | c = (uint32_t*)realloc(c, s * sizeof(uint32_t)); 738 | } 739 | c[l - 1] = to_cigar_int(e + 1, op); 740 | }else { 741 | l += 2; 742 | while (l >= s) { 743 | ++s; 744 | kroundup32(s); 745 | c = (uint32_t*)realloc(c, s * sizeof(uint32_t)); 746 | } 747 | c[l - 2] = to_cigar_int(e, op); 748 | c[l - 1] = to_cigar_int(1, 'M'); 749 | } 750 | 751 | // reverse cigar 752 | c1 = (uint32_t*)malloc(l * sizeof(uint32_t)); 753 | s = 0; 754 | e = l - 1; 755 | while (LIKELY(s <= e)) { 756 | c1[s] = c[e]; 757 | c1[e] = c[s]; 758 | ++ s; 759 | -- e; 760 | } 761 | result->seq = c1; 762 | result->length = l; 763 | 764 | free(direction); 765 | free(h_c); 766 | free(e_b); 767 | free(h_b); 768 | free(c); 769 | return result; 770 | } 771 | 772 | static int8_t* seq_reverse(const int8_t* seq, int32_t end) /* end is 0-based alignment ending position */ 773 | { 774 | int8_t* reverse = (int8_t*)calloc(end + 1, sizeof(int8_t)); 775 | int32_t start = 0; 776 | while (LIKELY(start <= end)) { 777 | reverse[start] = seq[end]; 778 | reverse[end] = seq[start]; 779 | ++ start; 780 | -- end; 781 | } 782 | return reverse; 783 | } 784 | 785 | s_profile* ssw_init (const int8_t* read, const int32_t readLen, const int8_t* mat, const int32_t n, const int8_t score_size) { 786 | s_profile* p = (s_profile*)calloc(1, sizeof(struct _profile)); 787 | p->profile_byte = 0; 788 | p->profile_word = 0; 789 | p->bias = 0; 790 | 791 | if (score_size == 0 || score_size == 2) { 792 | /* Find the bias to use in the substitution matrix */ 793 | int32_t bias = 0, i; 794 | for (i = 0; i < n*n; i++) if (mat[i] < bias) bias = mat[i]; 795 | bias = abs(bias); 796 | 797 | p->bias = bias; 798 | p->profile_byte = qP_byte (read, mat, readLen, n, bias); 799 | } 800 | if (score_size == 1 || score_size == 2) p->profile_word = qP_word (read, mat, readLen, n); 801 | p->read = read; 802 | p->mat = mat; 803 | p->readLen = readLen; 804 | p->n = n; 805 | return p; 806 | } 807 | 808 | void init_destroy (s_profile* p) { 809 | free(p->profile_byte); 810 | free(p->profile_word); 811 | free(p); 812 | } 813 | 814 | s_align* ssw_align (const s_profile* prof, 815 | const int8_t* ref, 816 | int32_t refLen, 817 | const uint8_t weight_gapO, 818 | const uint8_t weight_gapE, 819 | const uint8_t flag, // (from high to low) bit 5: return the best alignment beginning position; 6: if (ref_end1 - ref_begin1 <= filterd) && (read_end1 - read_begin1 <= filterd), return cigar; 7: if max score >= filters, return cigar; 8: always return cigar; if 6 & 7 are both setted, only return cigar when both filter fulfilled 820 | const uint16_t filters, 821 | const int32_t filterd, 822 | const int32_t maskLen) { 823 | 824 | alignment_end* bests = 0, *bests_reverse = 0; 825 | __m128i* vP = 0; 826 | int32_t word = 0, band_width = 0, readLen = prof->readLen; 827 | int8_t* read_reverse = 0; 828 | cigar* path; 829 | s_align* r = (s_align*)calloc(1, sizeof(s_align)); 830 | r->ref_begin1 = -1; 831 | r->read_begin1 = -1; 832 | r->cigar = 0; 833 | r->cigarLen = 0; 834 | if (maskLen < 15) { 835 | fprintf(stderr, "When maskLen < 15, the function ssw_align doesn't return 2nd best alignment information.\n"); 836 | } 837 | 838 | // Find the alignment scores and ending positions 839 | if (prof->profile_byte) { 840 | bests = sw_sse2_byte(ref, 0, refLen, readLen, weight_gapO, weight_gapE, prof->profile_byte, -1, prof->bias, maskLen); 841 | if (prof->profile_word && bests[0].score == 255) { 842 | free(bests); 843 | bests = sw_sse2_word(ref, 0, refLen, readLen, weight_gapO, weight_gapE, prof->profile_word, -1, maskLen); 844 | word = 1; 845 | } else if (bests[0].score == 255) { 846 | fprintf(stderr, "Please set 2 to the score_size parameter of the function ssw_init, otherwise the alignment results will be incorrect.\n"); 847 | free(r); 848 | return NULL; 849 | } 850 | }else if (prof->profile_word) { 851 | bests = sw_sse2_word(ref, 0, refLen, readLen, weight_gapO, weight_gapE, prof->profile_word, -1, maskLen); 852 | word = 1; 853 | }else { 854 | fprintf(stderr, "Please call the function ssw_init before ssw_align.\n"); 855 | free(r); 856 | return NULL; 857 | } 858 | r->score1 = bests[0].score; 859 | r->ref_end1 = bests[0].ref; 860 | r->read_end1 = bests[0].read; 861 | if (maskLen >= 15) { 862 | r->score2 = bests[1].score; 863 | r->ref_end2 = bests[1].ref; 864 | } else { 865 | r->score2 = 0; 866 | r->ref_end2 = -1; 867 | } 868 | free(bests); 869 | if (flag == 0 || (flag == 2 && r->score1 < filters)) goto end; 870 | 871 | // Find the beginning position of the best alignment. 872 | read_reverse = seq_reverse(prof->read, r->read_end1); 873 | if (word == 0) { 874 | vP = qP_byte(read_reverse, prof->mat, r->read_end1 + 1, prof->n, prof->bias); 875 | bests_reverse = sw_sse2_byte(ref, 1, r->ref_end1 + 1, r->read_end1 + 1, weight_gapO, weight_gapE, vP, r->score1, prof->bias, maskLen); 876 | } else { 877 | vP = qP_word(read_reverse, prof->mat, r->read_end1 + 1, prof->n); 878 | bests_reverse = sw_sse2_word(ref, 1, r->ref_end1 + 1, r->read_end1 + 1, weight_gapO, weight_gapE, vP, r->score1, maskLen); 879 | } 880 | free(vP); 881 | free(read_reverse); 882 | r->ref_begin1 = bests_reverse[0].ref; 883 | r->read_begin1 = r->read_end1 - bests_reverse[0].read; 884 | free(bests_reverse); 885 | if ((7&flag) == 0 || ((2&flag) != 0 && r->score1 < filters) || ((4&flag) != 0 && (r->ref_end1 - r->ref_begin1 > filterd || r->read_end1 - r->read_begin1 > filterd))) goto end; 886 | 887 | // Generate cigar. 888 | refLen = r->ref_end1 - r->ref_begin1 + 1; 889 | readLen = r->read_end1 - r->read_begin1 + 1; 890 | band_width = abs(refLen - readLen) + 1; 891 | path = banded_sw(ref + r->ref_begin1, prof->read + r->read_begin1, refLen, readLen, r->score1, weight_gapO, weight_gapE, band_width, prof->mat, prof->n); 892 | if (path == 0) { 893 | free(r); 894 | r = NULL; 895 | } 896 | else { 897 | r->cigar = path->seq; 898 | r->cigarLen = path->length; 899 | free(path); 900 | } 901 | 902 | end: 903 | return r; 904 | } 905 | 906 | void align_destroy (s_align* a) { 907 | free(a->cigar); 908 | free(a); 909 | } 910 | 911 | uint32_t* add_cigar (uint32_t* new_cigar, int32_t* p, int32_t* s, uint32_t length, char op) { 912 | if ((*p) >= (*s)) { 913 | ++(*s); 914 | kroundup32(*s); 915 | new_cigar = (uint32_t*)realloc(new_cigar, (*s)*sizeof(uint32_t)); 916 | } 917 | new_cigar[(*p) ++] = to_cigar_int(length, op); 918 | return new_cigar; 919 | } 920 | 921 | uint32_t* store_previous_m (int8_t choice, // 0: current not M, 1: current match, 2: current mismatch 922 | uint32_t* length_m, 923 | uint32_t* length_x, 924 | int32_t* p, 925 | int32_t* s, 926 | uint32_t* new_cigar) { 927 | 928 | if ((*length_m) && (choice == 2 || !choice)) { 929 | new_cigar = add_cigar (new_cigar, p, s, (*length_m), '='); 930 | (*length_m) = 0; 931 | } else if ((*length_x) && (choice == 1 || !choice)) { 932 | new_cigar = add_cigar (new_cigar, p, s, (*length_x), 'X'); 933 | (*length_x) = 0; 934 | } 935 | return new_cigar; 936 | } 937 | 938 | /*! @function: 939 | 1. Calculate the number of mismatches. 940 | 2. Modify the cigar string: 941 | differentiate matches (=) and mismatches(X); add softclip(S) at the beginning and ending of the original cigar. 942 | @return: 943 | The number of mismatches. 944 | The cigar and cigarLen are modified. 945 | */ 946 | int32_t mark_mismatch (int32_t ref_begin1, 947 | int32_t read_begin1, 948 | int32_t read_end1, 949 | const int8_t* ref, 950 | const int8_t* read, 951 | int32_t readLen, 952 | uint32_t** cigar, 953 | int32_t* cigarLen) { 954 | 955 | int32_t mismatch_length = 0, p = 0, i, length, j, s = *cigarLen + 2; 956 | uint32_t *new_cigar = (uint32_t*)malloc(s*sizeof(uint32_t)), length_m = 0, length_x = 0; 957 | char op; 958 | 959 | ref += ref_begin1; 960 | read += read_begin1; 961 | if (read_begin1 > 0) new_cigar[p ++] = to_cigar_int(read_begin1, 'S'); 962 | for (i = 0; i < (*cigarLen); ++i) { 963 | op = cigar_int_to_op((*cigar)[i]); 964 | length = cigar_int_to_len((*cigar)[i]); 965 | if (op == 'M') { 966 | for (j = 0; j < length; ++j) { 967 | if (*ref != *read) { 968 | ++ mismatch_length; 969 | // the previous is match; however the current one is mismatche 970 | new_cigar = store_previous_m (2, &length_m, &length_x, &p, &s, new_cigar); 971 | ++ length_x; 972 | } else { 973 | // the previous is mismatch; however the current one is matche 974 | new_cigar = store_previous_m (1, &length_m, &length_x, &p, &s, new_cigar); 975 | ++ length_m; 976 | } 977 | ++ ref; 978 | ++ read; 979 | } 980 | }else if (op == 'I') { 981 | read += length; 982 | mismatch_length += length; 983 | new_cigar = store_previous_m (0, &length_m, &length_x, &p, &s, new_cigar); 984 | new_cigar = add_cigar (new_cigar, &p, &s, length, 'I'); 985 | }else if (op == 'D') { 986 | ref += length; 987 | mismatch_length += length; 988 | new_cigar = store_previous_m (0, &length_m, &length_x, &p, &s, new_cigar); 989 | new_cigar = add_cigar (new_cigar, &p, &s, length, 'D'); 990 | } 991 | } 992 | new_cigar = store_previous_m (0, &length_m, &length_x, &p, &s, new_cigar); 993 | 994 | length = readLen - read_end1 - 1; 995 | if (length > 0) new_cigar = add_cigar(new_cigar, &p, &s, length, 'S'); 996 | 997 | (*cigarLen) = p; 998 | free(*cigar); 999 | (*cigar) = new_cigar; 1000 | return mismatch_length; 1001 | } 1002 | 1003 | --------------------------------------------------------------------------------