AlignmentReference.ts 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import {cloneDeep} from "lodash";
  2. import {
  3. AlignedRegion,
  4. AlignmentResponse,
  5. } from "@rcsb/rcsb-api-tools/build/RcsbGraphQL/Types/Borrego/GqlTypes";
  6. import {Alignment} from "./alignment-response";
  7. import {RcsbRequestContextManager, TagDelimiter} from "@rcsb/rcsb-saguaro-app";
  8. import {
  9. InstanceSequenceInterface
  10. } from "@rcsb/rcsb-saguaro-app/build/dist/RcsbCollectTools/DataCollectors/MultipleInstanceSequencesCollector";
  11. type AlignmentRefType = (number|undefined)[];
  12. type AlignmentMemberType = {
  13. id:string;
  14. map: AlignmentRefType;
  15. ref: AlignmentRefType;
  16. target: AlignmentRefType;
  17. };
  18. export class AlignmentReference {
  19. private alignmentRefMap: AlignmentRefType;
  20. private refId:string;
  21. private readonly alignmentRefGaps: Record<number, number> = {};
  22. private readonly memberRefList: AlignmentMemberType[] = [];
  23. private readonly alignmentMap = new Map<string,{entryId: string; instanceId: string; alignmentId: string; sequence: string; alignmentIndex:number; pairIndex: number;}>();
  24. public async init(result: Alignment) {
  25. this.alignmentMap.clear();
  26. this.refId = this.addUniqueAlignmentId(result,0,0);
  27. const length = (await this.getSequences())[0].sequence.length;
  28. this.alignmentRefMap = Array(length).fill(0).map((v,n)=>n+1);
  29. }
  30. public addAlignment(id:string, alignment: AlignmentRefType, target: AlignmentRefType): void {
  31. const gaps = findGaps(alignment);
  32. for (const gapBeg in gaps) {
  33. if(this.alignmentRefGaps[gapBeg])
  34. this.extendGap(parseInt(gapBeg), gaps[gapBeg])
  35. else
  36. this.addGap(parseInt(gapBeg), gaps[gapBeg])
  37. }
  38. const beg = alignment.filter(a=>(a && this.alignmentRefMap[0] && a<this.alignmentRefMap[0])) as number[];
  39. if(beg.length > 0)
  40. this.addBeg(beg);
  41. const n = this.alignmentRefMap[this.alignmentRefMap.length-1] as number;
  42. const end = alignment.filter(a=>(a && n && a>n)) as number[];
  43. if(end.length > 0)
  44. this.addEnd(end);
  45. this.addRef(id,alignment,target);
  46. }
  47. public buildAlignments(): AlignmentResponse {
  48. return buildAlignments(this.refId, this.alignmentRefMap, this.memberRefList);
  49. }
  50. public addUniqueAlignmentId(result: Alignment, alignmentIndex: number, pairIndex: 0|1 = 1): string {
  51. const res = result.structures[pairIndex];
  52. if(!res.selection)
  53. throw new Error("Missing entry_id and name from result");
  54. let entryId: string | undefined = undefined;
  55. const asymId = 'asym_id' in res.selection ? res.selection.asym_id : undefined;
  56. if("entry_id" in res && res.entry_id && res.selection && "asym_id" in res.selection)
  57. entryId = res.entry_id;
  58. else if("name" in res && res.selection &&"asym_id" in res.selection)
  59. entryId = res.name;
  60. if(!entryId || !asymId)
  61. throw new Error("Missing entry_id and name from result");
  62. if(!this.alignmentMap.has(`${entryId}${TagDelimiter.instance}${asymId}`)) {
  63. this.alignmentMap.set(`${entryId}${TagDelimiter.instance}${asymId}`, {
  64. entryId,
  65. instanceId: asymId,
  66. sequence: result.sequence_alignment?.[pairIndex].sequence ?? "",
  67. alignmentIndex: alignmentIndex,
  68. pairIndex: pairIndex,
  69. alignmentId: `${entryId}${TagDelimiter.instance}${asymId}`
  70. });
  71. return `${entryId}${TagDelimiter.instance}${asymId}`;
  72. }else{
  73. let tag = 1;
  74. while(this.alignmentMap.has(`${entryId}[${tag}]${TagDelimiter.instance}${asymId}`)){
  75. tag ++;
  76. }
  77. this.alignmentMap.set(`${entryId}[${tag}]${TagDelimiter.instance}${asymId}`, {
  78. entryId,
  79. instanceId: asymId,
  80. sequence: result.sequence_alignment?.[pairIndex].sequence ?? "",
  81. alignmentIndex: alignmentIndex,
  82. pairIndex: pairIndex,
  83. alignmentId: `${entryId}[${tag}]${TagDelimiter.instance}${asymId}`
  84. });
  85. return `${entryId}[${tag}]${TagDelimiter.instance}${asymId}`;
  86. }
  87. }
  88. public getAlignmentEntry(alignmentId: string): {entryId: string; instanceId: string; sequence: string; alignmentIndex:number; pairIndex: number; alignmentId:string;} {
  89. const pdb = this.alignmentMap.get(alignmentId)
  90. if(pdb)
  91. return pdb;
  92. throw new Error("Alignment Id not found");
  93. }
  94. public async getSequences(): Promise<InstanceSequenceInterface[]> {
  95. const out = Array.from(this.alignmentMap.values()).filter(v=>v.sequence.length > 0).map(v=>({
  96. rcsbId: v.alignmentId,
  97. sequence: v.sequence
  98. }));
  99. const missingSeq = await RcsbRequestContextManager.getInstanceSequences(
  100. Array.from(this.alignmentMap.values()).filter(v=>v.sequence.length == 0).map(
  101. v=>`${v.entryId}${TagDelimiter.instance}${v.instanceId}`
  102. ).filter((value,index,list)=> list.indexOf(value) === index)
  103. );
  104. return out.concat(
  105. Array.from(this.alignmentMap.values()).filter(v=>v.sequence.length == 0).map(v=>({
  106. rcsbId: v.alignmentId,
  107. sequence: missingSeq.find(s=>s.rcsbId === `${v.entryId}${TagDelimiter.instance}${v.instanceId}`)?.sequence ?? ""
  108. }))
  109. );
  110. }
  111. private addRef(id: string, alignment: AlignmentRefType, target: AlignmentRefType): void {
  112. const map: AlignmentRefType = Array(this.alignmentRefMap.length).fill(undefined);
  113. alignment.forEach((v,n)=>{
  114. if(typeof v === "undefined")
  115. return;
  116. const index = this.alignmentRefMap.findIndex(e=>e===v);
  117. if(index>=0)
  118. map[index] = n;
  119. });
  120. const gaps = findGaps(alignment);
  121. for (const gapBeg in gaps) {
  122. const index = this.alignmentRefMap.findIndex(v=>v===parseInt(gapBeg));
  123. for(let i = 1;i<=gaps[gapBeg];i++){
  124. map[index+i] = (map[index] as number)+i;
  125. }
  126. }
  127. this.memberRefList.push({
  128. id,
  129. map,
  130. ref: cloneDeep(alignment),
  131. target: cloneDeep(target)
  132. });
  133. }
  134. private addEnd(indexList: number[]): void {
  135. const last = this.alignmentRefMap.length;
  136. this.alignmentRefMap.splice(last, 0, ...indexList);
  137. this.memberRefList.forEach(mr=>{
  138. mr.map.splice(last,0, ...Array(indexList.length).fill(undefined));
  139. });
  140. }
  141. private addBeg(indexList: number[]): void {
  142. this.alignmentRefMap.splice(0, 0, ...indexList);
  143. this.memberRefList.forEach(mr=>{
  144. mr.map.splice(0,0, ...Array(indexList.length).fill(undefined));
  145. });
  146. }
  147. private addGap(gapBeg: number, gapLength: number): void {
  148. this.alignmentRefGaps[gapBeg] = gapLength;
  149. const i = this.alignmentRefMap.findIndex((v)=>v===gapBeg)+1;
  150. this.alignmentRefMap.splice(i,0, ...Array(gapLength).fill(undefined));
  151. this.memberRefList.forEach(mr=>{
  152. mr.map.splice(i,0, ...Array(gapLength).fill(undefined));
  153. });
  154. }
  155. private extendGap(gapBeg: number, gapLength: number): void {
  156. if(!this.alignmentRefGaps[gapBeg] || this.alignmentRefGaps[gapBeg] >= gapLength)
  157. return;
  158. const delta = gapLength - this.alignmentRefGaps[gapBeg];
  159. this.alignmentRefGaps[gapBeg] += delta;
  160. const i = this.alignmentRefMap.findIndex((v)=>v===gapBeg)+1;
  161. this.alignmentRefMap.splice(i,0, ...Array(delta).fill(undefined));
  162. this.memberRefList.forEach(mr=>{
  163. mr.map.splice(i,0, ...Array(delta).fill(undefined));
  164. });
  165. }
  166. }
  167. function buildAlignments(refId:string, alignmentRefMap: AlignmentRefType, alignmentMembers: AlignmentMemberType[]): AlignmentResponse {
  168. const out: AlignmentResponse = {};
  169. out.target_alignment = [];
  170. out.target_alignment.push({
  171. aligned_regions: buildRegions(alignmentRefMap),
  172. target_id: refId
  173. });
  174. alignmentMembers.forEach(am=>{
  175. out.target_alignment?.push({
  176. target_id: am.id,
  177. aligned_regions: buildRegions(am.map.map((v,n)=> typeof v === "number" ? am.target[v] : undefined))
  178. });
  179. })
  180. return out;
  181. }
  182. function buildRegions(alignment:AlignmentRefType): AlignedRegion[] {
  183. const out: AlignedRegion[] = [];
  184. let begIndex = 0;
  185. let begPos = 0;
  186. alignment.forEach((v,n)=>{
  187. if(!v){
  188. if(begIndex>0){
  189. out.push({
  190. query_begin: begIndex,
  191. target_begin: begPos,
  192. query_end: n,
  193. target_end: begPos+(n-begIndex)
  194. });
  195. begIndex = 0;
  196. begPos = 0;
  197. }
  198. }else{
  199. if(begIndex == 0){
  200. begIndex = n+1;
  201. begPos = v;
  202. }
  203. }
  204. });
  205. if(begPos>0){
  206. const n = alignment.length;
  207. out.push({
  208. query_begin: begIndex,
  209. target_begin: begPos,
  210. query_end: n,
  211. target_end: alignment[n-1] as number
  212. });
  213. }
  214. return out;
  215. }
  216. function findGaps(alignment: AlignmentRefType): Record<number,number> {
  217. const out: Record<number, number> = {};
  218. let gapBeg = 0;
  219. let gapLength = 0;
  220. alignment.forEach((v,n)=>{
  221. if(!v){
  222. if(gapBeg == 0)
  223. gapBeg = alignment[n-1] as number;
  224. gapLength++;
  225. }else{
  226. if(gapLength > 0){
  227. out[gapBeg]=gapLength;
  228. gapBeg = 0;
  229. gapLength = 0;
  230. }
  231. }
  232. })
  233. if(gapLength > 0)
  234. out[gapBeg]=gapLength;
  235. return out;
  236. }