UNPKG

slv-utils

Version:

Utilities for working with SLV and DAT files.

243 lines (221 loc) 6.9 kB
const _ = require('lodash'); /* This function creates Space object based on SLVJS object */ function slv2hetajs(slvjs){ let space = new Space(); // compartments let additionalSettings = getByKey(slvjs, '<COMMENTS 7'); let compartmentNames = additionalSettings['compartmentName'] .map((x) => x.trim()); compartmentNames.forEach((x) => { space.push({ id: x, class: 'Compartment' }); }); // compounds let compoundNames = _.flatten(getByKey(slvjs, '>Compound Names')) .map((x) => x.value); let compartmentConsistency = additionalSettings['metabolitesCompartment'] .map((x) => x.trim()); compoundNames.forEach((x, i) => { space.push({ id: x, class: 'Species', compartment: compartmentConsistency[i] }); }); // reactions let reactionNames = getByKey(slvjs, '>Reaction Names') .map((x) => x.value); let stoichiometricMatrix = getByKey(slvjs, 'Stoichiometric Matrix ') .map((x) => x.value)[0]; /* reactionConsistency = { '1': [[1, 1]], '2': [[1, -1], [2, -1], [3, 1], [4, 1]], '3': [[2, 1], [4, -1]], '4': [[3, -1]] } */ let reactionConsistency = _.chain(stoichiometricMatrix) .dropRight() .groupBy((x) => x[0]) .mapValues((x) => x.map(y => y.slice(1))) .mapValues((x) => x.map(y => { return { target: compoundNames[y[0]-1], stoichiometry: y[1] }; })) .mapValues((x) => { let left = x .filter(y => y.stoichiometry < 0) .map(y => y.stoichiometry===-1 ? y.target : `${-y.stoichiometry}*${y.target}` ) .join(' + '); let right = x .filter(y => y.stoichiometry > 0) .map(y => y.stoichiometry===1 ? y.target : `${y.stoichiometry}*${y.target}`) .join(' + '); return left + ' => ' + right; }) .value(); reactionNames.forEach((x, i) => { space.push({ id: x, class: 'Reaction', actors: reactionConsistency[i+1] }); }); // RHS let rhsParsed = getByKey(slvjs, '<RHS 1'); let rhsArray = _.chain(rhsParsed) // store unique expressions .flatten() .filter((x) => ['expression', 'numeric'].indexOf(x.type) !== -1) // select only y = 1*2 and y = 1.1 .filter((x) => !/F\[.+\]/.test(x.value.lhs)) // remove F[1] .filter((x) => compoundNames.indexOf(x.value.lhs) === -1) // remove pools .map((x) => { // analyze left part // analysis of reactions id in old format: V[1] let checker = /^V\[(\d+)\]$/; if (checker.test(x.value.lhs)) { let reactionNum = x.value.lhs .match(checker)[1]; if (reactionNum > reactionNames.length) throw new Error('index in V[i] is larger than number of reactions'); x.value.lhs = reactionNames[reactionNum-1]; } return x; }) .map((x) => {// analyze right part let checker = /V\[(\d+)\]/g; if (checker.test(x.value.rhs)) { x.value.rhs = x.value.rhs .replace(checker, (match, reactionNum) => { if (reactionNum > reactionNames.length) throw new Error('index in V[i] is larger than number of reactions'); return reactionNames[reactionNum-1]; }); } return x; }) .reverse().uniqBy((x) => x.value.lhs).reverse() // use only unique right side (from buttom to top scan) .forEach((x) => { x.isRecord = true; x.isRule = true; if (compartmentNames.indexOf(x.value.lhs) !== -1) x.isCompartment = true; if (reactionNames.indexOf(x.value.lhs) !== -1) x.isReaction = true; }) .value(); // push rules to namespace let recordsNames = []; // names of all values in RHS rhsArray.forEach((x) => { if ( x.isCompartment | x.isSpecies | x.isReaction ) { space.push({ id: x.value.lhs, assignments: { ode_: x.value.rhs } }); } else { recordsNames.push(x.value.lhs); space.push({ id: x.value.lhs, class: 'Record', // create Record instance assignments: { ode_: x.value.rhs } }); } }); // estimate which components is used in events let eventedRecordsNames = []; if (additionalSettings['useEvents'] === '1') { let evtArray = additionalSettings['eventsDataManager']; eventedRecordsNames = _.uniq( evtArray.map((x) => x[0]) ); } // initial values let ivParsed = getByKey(slvjs, '<INI 1'); _.flatten(ivParsed).forEach((x) => { if (x.type === 'expression'){ throw new Error(`Expressions in Initial values is not supported, see ${x.value.lhs} = ${x.value.rhs}`); } }); let ivArray = _.chain(ivParsed) .flatten() .filter((x) => x.type === 'numeric') // use only expressions k1 = 1.1; .reverse().uniqBy((x) => x.value.lhs).reverse() // select unique from the end .forEach((x) => { if (compartmentNames.indexOf(x.value.lhs) !== -1) x.isCompartment = true; if (compoundNames.indexOf(x.value.lhs) !== -1) x.isSpecies = true; if (reactionNames.indexOf(x.value.lhs) !== -1) x.isReaction = true; if (eventedRecordsNames.indexOf(x.value.lhs) !== -1) x.isInEvent = true; if (recordsNames.indexOf(x.value.lhs) !== -1) x.isRule = true; }) .value(); ivArray.forEach((x) => { if ( x.isCompartment | x.isSpecies | x.isReaction ) { space.push({ id: x.value.lhs, assignments: { start_: x.value.rhs } }); } else if (x.isRule) { space.push({ id: x.value.lhs, assignments: { start_: x.value.rhs } }); } else if(x.isInEvent) { space.push({ id: x.value.lhs, class: 'Record', assignments: { start_: x.value.rhs } }); } else { space.push({ id: x.value.lhs, class: 'Const', // create Const instance num: parseFloat(x.value.rhs) }); } }); // events if (additionalSettings['useEvents'] === '1') { let evtArray = additionalSettings['eventsDataManager']; evtArray.forEach((x, i) => { if (x[5] === '1') { // TimeSwitcher let evtID = `evt${i}_`; let evt_i = { id: evtID, class: 'TimeSwitcher', start: x[3], period: x[4] }; if (x[4]==='0') evt_i.repeatCount = 0; space.push(evt_i); // Record space.push({ id: x[0], assignments: {[evtID]: `${x[1]} * ${x[0]} + ${x[2]}`} }); } }); } return space; } function getByKey(slvjs, key){ let res = slvjs.content .map.find((x) => x.key === key) if (!res) throw new Error(`This is no key "${key}" in slvjs.`); return res.parsedValue; } class Space extends Array { getById(id){ return this.find((x) => x.id === id); } selectByClass(className){ this.filter((x) => x.class === className); } toArray(){ return [...this]; } } module.exports = { slv2hetajs, Space };