From f599d0e6c86df9c700bad78c5d81963f21d53d62 Mon Sep 17 00:00:00 2001 From: Evgeny Metelkin Date: Wed, 24 Jan 2024 19:15:48 +0200 Subject: [PATCH] check units for ODE --- cases/25-units-check/.gitattributes | 34 ++++++ cases/25-units-check/.gitignore | 11 ++ cases/25-units-check/platform.json | 14 +++ cases/25-units-check/src/index.heta | 35 ++++++ cases/25-units-check/src/qsp-units.heta | 153 ++++++++++++++++++++++++ src/builder/index.js | 3 +- src/container/main.js | 5 + src/core/expression.js | 4 +- src/core/math-calc-unit.js | 4 +- src/core/species.js | 50 ++++++++ 10 files changed, 307 insertions(+), 6 deletions(-) create mode 100644 cases/25-units-check/.gitattributes create mode 100644 cases/25-units-check/.gitignore create mode 100644 cases/25-units-check/platform.json create mode 100644 cases/25-units-check/src/index.heta create mode 100644 cases/25-units-check/src/qsp-units.heta diff --git a/cases/25-units-check/.gitattributes b/cases/25-units-check/.gitattributes new file mode 100644 index 00000000..27dae8bf --- /dev/null +++ b/cases/25-units-check/.gitattributes @@ -0,0 +1,34 @@ +### Basic .gitattributes for a Heta repo. +# Note that binary is a macro for -text -diff. + +* text=auto + +# main files +# to have heta files in linux style line endings +*.heta text eol=lf +*.xlsx binary + +# auxilary files +*.bash text eol=lf +*.sh text eol=lf +*.xlsm binary +*.xls binary +*.pptx binary +*.ppt binary + +# code +*.md text +*.m text +# -diff +*.slv text eol=crlf diff=slv +*.dat text eol=crlf diff=dat +*.cpp text +*.svg text diff=xml +*.xml text diff=xml +*.sbml text diff=xml + +# LFS (large files) +large/* filter=lfs diff=lfs merge=lfs -text +literature/* filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.rar filter=lfs diff=lfs merge=lfs -text diff --git a/cases/25-units-check/.gitignore b/cases/25-units-check/.gitignore new file mode 100644 index 00000000..ce522c01 --- /dev/null +++ b/cases/25-units-check/.gitignore @@ -0,0 +1,11 @@ +### Heta specific files and dirs +/dist +/draft +/drafts +/meta + +### temporal files +*.tmp +~$*.* +*.log +.Rhistory diff --git a/cases/25-units-check/platform.json b/cases/25-units-check/platform.json new file mode 100644 index 00000000..4f258b8d --- /dev/null +++ b/cases/25-units-check/platform.json @@ -0,0 +1,14 @@ +{ + "builderVersion": "*", + "id": "template", + "notes": "platform notes", + "version": "v0.1.0", + "license": "UNLICENSED", + "options": { + "unitsCheck": true + }, + "importModule": { + "type": "heta", + "source": "src/index.heta" + } +} diff --git a/cases/25-units-check/src/index.heta b/cases/25-units-check/src/index.heta new file mode 100644 index 00000000..1ff04f2a --- /dev/null +++ b/cases/25-units-check/src/index.heta @@ -0,0 +1,35 @@ +/* + template file for creating platform +*/ +// add qsp units +include ./qsp-units.heta; +t {units: h}; + +p1 @Record .= 1 {units: uM}; +c1 @Compartment .= 3 {units: L}; +c2 @Compartment .= 4 {units: mL}; +c3 @Compartment .= 5 {units: m}; + +s1 @Species {compartment: c1, units: mM} .= 10; +s2 @Species {compartment: c2, units: mM} .= 10; +s3 @Species {compartment: c3, units: mmole/m} .= 10; + +r1 @Reaction {actors: s1=>s2, units: mmole/h} := k1 * s1 * c1; +/* +d(s1*c1)/dt = - r1; left: mMole*L/minute, right: 1/h*mMole*h !!! +d(s2*c2)/dt = r1; left: mMole*mL/minute, right: 1/h*mMole*h !!! +*/ + +k1 @Const = 1e-3 {units: 1/h}; + +// exports +#export { format: JSON, filepath: output }; +//#export { format: YAML }; +//#export { format: XLSX, omitRows: 3, splitByClass: true }; +//#export { format: SBML, version: L2V4 }; +//#export { format: SLV, eventsOff: false }; +//#export { format: DBSolve }; +//#export { format: Simbio }; +//#export { format: Mrgsolve }; +//#export { format: Matlab }; +//#export { format: Julia }; diff --git a/cases/25-units-check/src/qsp-units.heta b/cases/25-units-check/src/qsp-units.heta new file mode 100644 index 00000000..c1e86437 --- /dev/null +++ b/cases/25-units-check/src/qsp-units.heta @@ -0,0 +1,153 @@ +fmole #defineUnit { + units: [ { kind: mole, multiplier: 1e-15 } ] +}; +pmole #defineUnit { + units: [ { kind: mole, multiplier: 1e-12 } ] +}; +nmole #defineUnit { + units: [ { kind: mole, multiplier: 1e-9 } ] +}; +umole #defineUnit { + units: [ { kind: mole, multiplier: 1e-6 } ] +}; +mmole #defineUnit { + units: [ { kind: mole, multiplier: 1e-3 } ] +}; +fM #defineUnit { + units: [ { kind: mole, multiplier: 1e-15 }, { kind: litre, exponent: -1} ] +}; + +pM #defineUnit { + units: [ { kind: mole, multiplier: 1e-12 }, { kind: litre, exponent: -1} ] +}; +nM #defineUnit { + units: [ { kind: mole, multiplier: 1e-9 }, { kind: litre, exponent: -1} ] +}; +uM #defineUnit { + units: [ { kind: mole, multiplier: 1e-6 }, { kind: litre, exponent: -1} ] +}; +mM #defineUnit { + units: [ { kind: mole, multiplier: 1e-3 }, { kind: litre, exponent: -1} ] +}; +M #defineUnit { + units: [ { kind: mole }, { kind: litre, exponent: -1} ] +}; +kM #defineUnit { + units: [ { kind: mole, multiplier: 1e3 }, { kind: litre, exponent: -1 } ] +}; + +fL #defineUnit { + units: [ { kind: litre, multiplier: 1e-15 } ] +}; +pL #defineUnit { + units: [ { kind: litre, multiplier: 1e-12 } ] +}; +nL #defineUnit { + units: [ { kind: litre, multiplier: 1e-9 } ] +}; +uL #defineUnit { + units: [ { kind: litre, multiplier: 1e-6 } ] +}; +mL #defineUnit { + units: [ { kind: litre, multiplier: 1e-3 } ] +}; +dL #defineUnit { + units: [ { kind: litre, multiplier: 1e-1 } ] +}; +L #defineUnit { + units: [ { kind: litre } ] +}; + +fs #defineUnit { + units: [ { kind: second, multiplier: 1e-15 } ] +}; +ps #defineUnit { + units: [ { kind: second, multiplier: 1e-12 } ] +}; +ns #defineUnit { + units: [ { kind: second, multiplier: 1e-9 } ] +}; +us #defineUnit { + units: [ { kind: second, multiplier: 1e-6 } ] +}; +ms #defineUnit { + units: [ { kind: second, multiplier: 1e-3 } ] +}; +s #defineUnit { + units: [ { kind: second } ] +}; +h #defineUnit { + units: [ { kind: hour, multiplier: 1 } ] +}; +week #defineUnit { + units: [ { kind: day, multiplier: 7 } ] +}; + +fg #defineUnit { + units: [ { kind: kilogram, multiplier: 1e-18 } ] +}; +pg #defineUnit { + units: [ { kind: kilogram, multiplier: 1e-15 } ] +}; +ng #defineUnit { + units: [ { kind: kilogram, multiplier: 1e-12 } ] +}; +ug #defineUnit { + units: [ { kind: kilogram, multiplier: 1e-9 } ] +}; +mg #defineUnit { + units: [ { kind: kilogram, multiplier: 1e-6 } ] +}; +g #defineUnit { + units: [ { kind: kilogram, multiplier: 1e-3 } ] +}; +kg #defineUnit { + units: [ { kind: kilogram } ] +}; + +kat #defineUnit { + units: [ { kind: katal } ] +}; + +cell #defineUnit { + units: [ { kind: item } ] +}; +kcell #defineUnit { + units: [ { kind: item, multiplier: 1e3 } ] +}; + +cal #defineUnit { + units: [ { kind: joule, multiplier: 4.1868 } ] +}; +kcal #defineUnit { + units: [ { kind: joule, multiplier: 4.1868e3 } ] +}; + +fm #defineUnit { + units: [ { kind: metre, multiplier: 1e-15 } ] +}; +pm #defineUnit { + units: [ { kind: metre, multiplier: 1e-12 } ] +}; +nm #defineUnit { + units: [ { kind: metre, multiplier: 1e-9 } ] +}; +um #defineUnit { + units: [ { kind: metre, multiplier: 1e-6 } ] +}; +mm #defineUnit { + units: [ { kind: metre, multiplier: 1e-13 } ] +}; +cm #defineUnit { + units: [ { kind: metre, multiplier: 1e-2 } ] +}; +m #defineUnit { + units: [ { kind: metre } ] +}; + +UL #defineUnit { + units: [ { kind: dimensionless } ] +}; +percent #defineUnit { + units: [ { kind: dimensionless, multiplier: 1e-2 } ] +}; diff --git a/src/builder/index.js b/src/builder/index.js index da7bb32e..ceae1a42 100644 --- a/src/builder/index.js +++ b/src/builder/index.js @@ -83,7 +83,7 @@ class Builder { * 3. Loading integrated structures into `Platform`. * 4. Setting cross-references in platform's elements. * 5. Checking circular references in mathematical expressions. - * 6. Checking circular unitDef references. + * 6. Checking circular unitDef references. Checking circular functionDef rferences. * 7. Checking left and right side units compatibility for mathematical expressions. * 8. Checking unit\'s terms. * 9. Export of a platform to series of formats. @@ -116,7 +116,6 @@ class Builder { // 3. Translation this.container.loadMany(qArr, false); - //console.log([...this.container.unitDefStorage]); // XXX: debugging // 4. Binding this.logger.info('Setting references in elements, total length ' + this.container.length); diff --git a/src/container/main.js b/src/container/main.js index 57cdae43..04e0cdcc 100644 --- a/src/container/main.js +++ b/src/container/main.js @@ -254,6 +254,11 @@ class Container { checkUnits(){ this.namespaceStorage.forEach((ns) => { if (!ns.isAbstract) { + // check units for t + let timeUnits = ns.get('t').unitsParsed; + if (!timeUnits) { + this.logger.warn('No units set for "t", cannot check ODE units.'); + } // check Record.assignments ns.selectByInstanceOf('Record') .forEach((rec) => rec.checkUnits()); diff --git a/src/core/expression.js b/src/core/expression.js index 0d08c3a5..2ae20fd9 100644 --- a/src/core/expression.js +++ b/src/core/expression.js @@ -186,8 +186,8 @@ class Expression { return isBooleanOperator || isBooleanValue; } - calcUnit(record){ - return _calcUnit(this.exprParsed, record); + calcUnit(component) { // component here is used for logger and index + return _calcUnit(this.exprParsed, component); } } diff --git a/src/core/math-calc-unit.js b/src/core/math-calc-unit.js index 87d4d1ce..3fa437b6 100644 --- a/src/core/math-calc-unit.js +++ b/src/core/math-calc-unit.js @@ -1,6 +1,6 @@ /* This module calculates units based on expressions -Records of the expressions must be bound before running this method +Records of the expressions must be bound before running the method */ const { Unit } = require('./unit'); @@ -8,7 +8,7 @@ const { Unit } = require('./unit'); _this : Node record : Record */ -function _calcUnit(_this, record){ +function _calcUnit(_this, record) { const logger = record.namespace.container.logger; if (_this.type === 'ParenthesisNode') { diff --git a/src/core/species.js b/src/core/species.js index 1dbdfccd..021d462b 100644 --- a/src/core/species.js +++ b/src/core/species.js @@ -80,6 +80,56 @@ class Species extends Record { return super._references() .concat(classSpecificRefs); } + /* + Check units for left and right side of ODE + Works only for bound records + */ + checkUnits() { + super.checkUnits(); + + let logger = this.namespace?.container?.logger; + + // if not in processes do not check + let processes = this.backReferences.map(x => x._process_); + if (!processes.length) { + return; // BRAKE + } + + // if no units for t skip check, message in Container.checkUnits() + let timeUnits = this.namespace.get('t').unitsParsed; + if (!timeUnits) { + return; // BRAKE + } + + let compartmentUnits = this.compartmentObj?.unitsParsed; + if (!compartmentUnits && !this.isAmount) { + logger.warn(`No units set for compartment "${this.compartment}", cannot check units for "${this.index}" associated ODE.`); + return; // BRAKE + } + + let speciesUnits = this.unitsParsed; + if (!speciesUnits) { + logger.warn(`No units set for "${this.index}", cannot check units for associated ODE.`); + return; // BRAKE + } + + // d(s*c)/dt + let leftSideUnits = speciesUnits + .multiply(compartmentUnits) + .divide(timeUnits) + .simplify(); + // r1 + r2 + r3 + processes.forEach((proc) => { + let processUnits = proc.unitsParsed; + if (!processUnits) { + // message was in Process.checkUnits + return; // BRAKE + } + if (!leftSideUnits.equal(processUnits, true)) { + logger.warn(`Unit inconsistency for ${this.id} associated ODE. Left: ${leftSideUnits.toString()}. Right: ${processUnits.toString()} (${proc.id})`); + } + }); + } get legalTerms() { let actualCompartmentTerm = this.compartmentObj?.unitsParsed?.toTerm();