diff --git a/TODO.md b/TODO.md index e2b59b8c..8d825d0f 100644 --- a/TODO.md +++ b/TODO.md @@ -47,7 +47,6 @@ ## ideas - generarion of 'platform.yml' by `heta init` -- use the syntax 1//3 for rational numbers - deprecated `include` statement - `#move`, `#moveNS` - `#deleteNS` action diff --git a/package.json b/package.json index 83dc3239..f842e471 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Programming platform for Quantitative Systems Pharmacology modeling in NodeJS", "main": "src/index.js", "scripts": { - "test:dev": "mocha test/module-system --config=./test/.mocharc.json", + "test:dev": "mocha test/check-units/calc-unit --config=./test/.mocharc.json", "test": "mocha test --config=./test/.mocharc.json", "jsdoc": "jsdoc -r -c .jsdoc.json --readme api-references.md -d docs/dev src", "test:cov": "nyc --reporter=lcov npm run test", diff --git a/src/core/math-calc-unit.js b/src/core/math-calc-unit.js index fa9bc074..87d4d1ce 100644 --- a/src/core/math-calc-unit.js +++ b/src/core/math-calc-unit.js @@ -63,11 +63,15 @@ function _calcUnit(_this, record){ logger.warn(`Units inconsistency for "${record.index}" for logical operators here"${_this.toString()}", some of them is not dimensionless : "${argUnit}"`); } return new Unit(); - } else if (_this.fn === 'pow') { - if (_this.args[1].type === 'ConstantNode') { // pow(x, 3) - let n = _this.args[1].value; - return argUnit[0].power(n); - } else { // pow(x, y) + } else if (_this.fn === 'pow') { // ^ + let pArg = _this.args[1]; + if (pArg.type === 'ConstantNode') { // x ^ 3 + return argUnit[0].power(pArg.value); + } else if (pArg.type === 'ParenthesisNode' && pArg.content?.fn === 'divide' && pArg.content.args[0]?.type === 'ConstantNode' && pArg.content.args[1]?.type === 'ConstantNode') { // x ^ (1/2) + let numerator = pArg.content.args[0].value; + let denominator = pArg.content.args[1].value; + return argUnit[0].power(numerator).root(denominator); + } else { // x ^ y if (!argUnitDimensionless[0] || !argUnitDimensionless[1]) { let unitExpr = argUnit[0].toString() + '^' + argUnit[1].toString(); logger.warn(`Units inconsistency for "${record.index}": power arguments must be dimensionless or second argument should be a number: "${_this.toString()}" : "${unitExpr}"`); @@ -122,11 +126,15 @@ function _calcUnit(_this, record){ } else if (_this.fn.name === 'cube') { // cube() return argUnit[0].power(3); } else if (_this.fn.name === 'sqrt') { // sqrt() - return argUnit[0].power(0.5); + return argUnit[0].root(2); } else if (_this.fn.name === 'pow') { // pow() - if (_this.args[1].type === 'ConstantNode') { // pow(x, 2) - let n = _this.args[1].value; - return argUnit[0].power(n); + let pArg = _this.args[1]; + if (pArg.type === 'ConstantNode') { // pow(x, 2) + return argUnit[0].power(pArg.value); + } else if (pArg.fn === 'divide' && pArg.args[0]?.type === 'ConstantNode' && pArg.args[1]?.type === 'ConstantNode') { // pow(x, 1/2) + let numerator = pArg.args[0].value; + let denominator = pArg.args[1].value; + return argUnit[0].power(numerator).root(denominator); } else { // pow(x, y) if (!argUnitDimensionless[0] || !argUnitDimensionless[1]) { let unitExpr = argUnit[0].toString() + '^' + argUnit[1].toString(); @@ -136,11 +144,11 @@ function _calcUnit(_this, record){ return argUnit[0]; } } else if (_this.fn.name === 'nthRoot' && _this.args.length === 1) { // nthRoot() - return argUnit[0].power(0.5); + return argUnit[0].root(2); } else if (_this.fn.name === 'nthRoot') { if (_this.args[1].type === 'ConstantNode') { // nthRoot(x, 3) let n = _this.args[1].value; - return argUnit[0].power(1/n); + return argUnit[0].root(n); } else { // nthRoot(x, y) if (!argUnitDimensionless[0] || !argUnitDimensionless[1]) { let unitExpr = argUnit[0].toString() + '^' + argUnit[1].toString(); diff --git a/src/core/unit.js b/src/core/unit.js index a453c694..dd4825fc 100644 --- a/src/core/unit.js +++ b/src/core/unit.js @@ -150,7 +150,14 @@ class Unit extends Array { if (typeof n !== 'number') throw new TypeError('n in power must be a Number, got' + n); return this.map((item) => { - return Object.assign({}, item, {exponent: n * item.exponent}); + return Object.assign({}, item, {exponent: item.exponent * n}); + }); + } + root(n = 1) { + if (typeof n !== 'number') throw new TypeError('n in power must be a Number, got' + n); + + return this.map((item) => { + return Object.assign({}, item, {exponent: item.exponent / n}); }); } /** diff --git a/test/check-units/calc-unit.js b/test/check-units/calc-unit.js index c7eb97d3..374ab941 100644 --- a/test/check-units/calc-unit.js +++ b/test/check-units/calc-unit.js @@ -34,6 +34,10 @@ let qArr = [ {id: 'y12', class: 'Record', assignments: {start_: 'piecewise(k3, x1 >= k2, k2)'}}, {id: 'y13', class: 'Record', assignments: {start_: 'piecewise(k3, x1 >= k2)'}}, {id: 'y14', class: 'Record', assignments: {start_: 'piecewise(1, x1 >= k2, 2, x1 >= 2*k2, 0)'}}, + // fraction in power + {id: 'x6', class: 'Record', units: 'metre^3', assignments: {start_: '1'}}, + {id: 'y15', class: 'Record', assignments: {start_: 'x6^(2/3)'}}, + {id: 'y16', class: 'Record', assignments: {start_: 'pow(x6, 2/3)'}}, ]; describe('Testing checkUnits() for components', () => { @@ -200,6 +204,20 @@ describe('Testing checkUnits() for components', () => { expect(unit).to.be.equal('dimensionless'); }); + it('pow with fractions x^(1/2)', () => { + let y15 = p.namespaceStorage.get('nameless').get('y15'); + let expr = y15.assignments.start_; + let unit = expr.calcUnit(y15).toString(); + expect(unit).to.be.equal('metre^2'); + }); + + it('pow with fractions pow(x , 1/2)', () => { + let y16 = p.namespaceStorage.get('nameless').get('y16'); + let expr = y16.assignments.start_; + let unit = expr.calcUnit(y16).toString(); + expect(unit).to.be.equal('metre^2'); + }); + it('No warnings', () => { let warnings = p.defaultLogs.filter(x=>x.level==='warn'); //console.log(p.defaultLogs)