import { euclideanDistance, cosineDistance, minkowskiDistance, jaccardDistance } from "./Distance" import { SimilarityFunction } from "./types" /** * transforms score from [0,1] scale to [-1,1] */ const transformScale = (score: number, xref_min: number, xref_max: number) => 2*(score-xref_min)/(xref_max-xref_min) - 1 /** * Generalizing elements in Sequences to nodes * This class generalizes the character elements in sequences where each element * is represented by a character from some alphabet. It represents elements in the sequence * as nodes. * * @member label: arbitrary type, id/char/description of an event * @member transTime number, transition time between events */ export class GenericSeqNode { public label: LabelType public transTime: number | undefined constructor(label: LabelType, transTime?: number) { this.label = label this.transTime = transTime } computeSimilarity(seqNode: GenericSeqNode): number { throw new Error("to be implemented in child class") } /** * implement temporal penalty function -- in case of using temporal alignment */ computeTemporalPenalty(accumTseq1: number, accumTseq2: number, T_penalty: number = 0.25) { if(accumTseq1 || accumTseq2) { return T_penalty * Math.abs(accumTseq1-accumTseq2) / Math.max(accumTseq1, accumTseq2) } return 0 } } /** * Character node in a sequence */ export class CharSeqNode extends GenericSeqNode { /** * get the score based on the scoring matrix/substitution matrix */ computeSimilarity(seqNode: GenericSeqNode, scoreMatrix: Map<[LabelType, LabelType], number> = new Map<[LabelType, LabelType], number>()): number { // score matrix comes from args return scoreMatrix.get([this.label, seqNode.label]) || 0 } } /** * generate sequence nodes from character elements in a string * @param s string of character elements * @param transTimes representing the transition time between elements in the sequence. * @returns a list of CharSeqNode elements */ export function generateCharSeqNodes(s: string, transTimes: number[] = []): CharSeqNode[] { return [...s].map((value: string, index: number) => { if (transTimes[index]) return new CharSeqNode(value, transTimes[index]) else return new CharSeqNode(value) }) } /** * Node in a sequence * * This class generalizes the character elements in sequences where usually each element * is represented by a character from some alphabet. * The aim of this class is to represent elements in the sequence as nodes where each node * is represented by certain properties in a feature vector. * * @member label string, id/char/description of an event * @member transTime number, transition time between events * @member featureVector numpy array, feature vector */ export class SeqNode extends GenericSeqNode { featureVector: number[] constructor(label: LabelType, featureVector: number[], transTime=0) { super(label, transTime) this.featureVector = featureVector } /** * compute similarity between two feature vector representations * @param seqNode instance of `GenericSeqNode`, `CharSeqNode` or `SeqNode` * @param scoreType Euclidean, Minkowski, Cosine or Jaccard * @param changeScale bool, transform score to -1,1 range * @returns similarity score */ computeSimilarity(seqNode: SeqNode, scoreType: SimilarityFunction = SimilarityFunction.Euclidean, changeScale: boolean = false): number { // compute a score let featvec_a = this.featureVector let featvec_b = seqNode.featureVector let distance: number let score: number = 0 // make sure to pick score metric supporting vectors with negative components if (this.featureVector.find((el: number) => el < 0) || seqNode.featureVector.find((el: number) => el < 0)) { if (scoreType === SimilarityFunction.Cosine) scoreType = SimilarityFunction.AngularCosine else if (scoreType === SimilarityFunction.Jaccard) { scoreType = SimilarityFunction.AngularCosine console.log('changing the scoring type to angular_cosine since feature vector includes negative components') } } if (scoreType === SimilarityFunction.Euclidean) { distance = euclideanDistance(featvec_a, featvec_b) score = 1/(1+distance) } else if (scoreType === SimilarityFunction.Minkowski) { distance = minkowskiDistance(featvec_a, featvec_b, 1) score = 1/(1+distance) } else if(scoreType == SimilarityFunction.Cosine) { // wäre spannend beim Vergleich von zwei ähnlichen Interpretationen ("plagiat") try { distance = cosineDistance(featvec_a, featvec_b) score = 1-distance } catch (e/*: ZeroDivisionError*/) { console.log('dividing by zero ...') } return 0 } else if(scoreType == SimilarityFunction.AngularCosine) { try { distance = cosineDistance(featvec_a, featvec_b) score = 1-Math.acos(1-distance) / Math.PI } catch(e/*: ZeroDivisionError*/) { console.log('dividing by zero..') } return 0; } else if(scoreType === SimilarityFunction.Jaccard) { // wird auch bei automatischer Texterkennung eingesetzt // get activated features distance = jaccardDistance(featvec_a, featvec_b) score = 1-distance } if(changeScale) score = transformScale(score, 0, 1) return score } } /** * retrieve common substitution matrices (i.e. BlOSUM, PAM ..) */ function getCommonScoringMatrix(matrixName: string) { /* -`wikipedia page `__ - http://biopython.org/DIST/docs/api/Bio.SubsMat.MatrixInfo-module.html - https://web.archive.org/web/19991014010917/http://www.embl-heidelberg.de/%7Evogt/matrices/mlist1.html */ return 0 } export type Sequence = GenericSeqNode[]