From 34f991949d00473f6446829c9ebe9be6c7ba86c2 Mon Sep 17 00:00:00 2001 From: Eric Date: Sat, 28 Nov 2015 20:09:22 +0000 Subject: [PATCH 1/2] Added support for complex units --- lib/function/arithmetic/cbrt.js | 51 ++++++++++-------- lib/function/arithmetic/multiplyScalar.js | 4 +- lib/type/unit/Unit.js | 24 ++++++--- lib/type/unit/function/unit.js | 2 +- test/function/arithmetic/abs.test.js | 3 ++ test/function/arithmetic/addScalar.test.js | 2 + test/function/arithmetic/cbrt.test.js | 3 ++ test/function/arithmetic/divide.test.js | 6 +++ test/function/arithmetic/multiply.test.js | 12 +++-- test/function/arithmetic/sign.test.js | 3 ++ test/function/arithmetic/sqrt.test.js | 5 +- test/function/arithmetic/subtract.test.js | 3 ++ test/function/arithmetic/unaryMinus.test.js | 2 + test/function/trigonometry/cos.test.js | 2 + test/function/trigonometry/cosh.test.js | 2 + test/function/trigonometry/cot.test.js | 2 + test/function/trigonometry/coth.test.js | 2 + test/function/trigonometry/csc.test.js | 2 + test/function/trigonometry/csch.test.js | 2 + test/function/trigonometry/sec.test.js | 2 + test/function/trigonometry/sech.test.js | 2 + test/function/trigonometry/sin.test.js | 2 + test/function/trigonometry/sinh.test.js | 2 + test/function/trigonometry/tan.test.js | 2 + test/function/trigonometry/tanh.test.js | 2 + test/type/unit/Unit.test.js | 59 ++++++++++++++++++++- 26 files changed, 164 insertions(+), 39 deletions(-) diff --git a/lib/function/arithmetic/cbrt.js b/lib/function/arithmetic/cbrt.js index 6fca77d9e..07cf61371 100644 --- a/lib/function/arithmetic/cbrt.js +++ b/lib/function/arithmetic/cbrt.js @@ -172,30 +172,39 @@ function factory (type, config, load, typed) { * @private */ function _cbrtUnit(x) { - var negate = isNegative(x.value); - if (negate) { - x.value = unaryMinus(x.value); - } - - // TODO: create a helper function for this - var third; - if (x.value && x.value.isBigNumber) { - third = new type.BigNumber(1).div(3); - } - else if (x.value && x.value.isFraction) { - third = new type.Fraction(1, 3); + if(x.value && x.value.isComplex) { + var result = x.clone(); + result.value = 1.0; + result = result.pow(1.0/3); // Compute the units + result.value = _cbrtComplex(x.value); // Compute the value + return result; } else { - third = 1/3; + var negate = isNegative(x.value); + if (negate) { + x.value = unaryMinus(x.value); + } + + // TODO: create a helper function for this + var third; + if (x.value && x.value.isBigNumber) { + third = new type.BigNumber(1).div(3); + } + else if (x.value && x.value.isFraction) { + third = new type.Fraction(1, 3); + } + else { + third = 1/3; + } + + var result = x.pow(third); + + if (negate) { + result.value = unaryMinus(result.value); + } + + return result; } - - var result = x.pow(third); - - if (negate) { - result.value = unaryMinus(result.value); - } - - return result; } cbrt.toTex = '\\sqrt[3]{${args[0]}}'; diff --git a/lib/function/arithmetic/multiplyScalar.js b/lib/function/arithmetic/multiplyScalar.js index 7351303c3..caacfa70e 100644 --- a/lib/function/arithmetic/multiplyScalar.js +++ b/lib/function/arithmetic/multiplyScalar.js @@ -36,13 +36,13 @@ function factory(type, config, load, typed) { return x.mul(y); }, - 'number | Fraction | BigNumber, Unit': function (x, y) { + 'number | Fraction | BigNumber | Complex, Unit': function (x, y) { var res = y.clone(); res.value = (res.value === null) ? res._normalize(x) : multiplyScalar(res.value, x); return res; }, - 'Unit, number | Fraction | BigNumber': function (x, y) { + 'Unit, number | Fraction | BigNumber | Complex': function (x, y) { var res = x.clone(); res.value = (res.value === null) ? res._normalize(y) : multiplyScalar(res.value, y); return res; diff --git a/lib/type/unit/Unit.js b/lib/type/unit/Unit.js index b0bd84fd6..11aec8f85 100644 --- a/lib/type/unit/Unit.js +++ b/lib/type/unit/Unit.js @@ -29,7 +29,7 @@ function factory (type, config, load, typed) { * var c = math.in(a, new Unit(null, 'm'); // 0.05 m * var d = new Unit(9.81, "m/s^2"); // 9.81 m/s^2 * - * @param {number | BigNumber | Fraction | boolean} [value] A value like 5.2 + * @param {number | BigNumber | Fraction | Complex | boolean} [value] A value like 5.2 * @param {string} [name] A unit name like "cm" or "inch", or a derived unit of the form: "u1[^ex1] [u2[^ex2] ...] [/ u3[^ex3] [u4[^ex4]]]", such as "kg m^2/s^2", where each unit appearing after the forward slash is taken to be in the denominator. "kg m^2 s^-2" is a synonym and is also acceptable. Any of the units can include a prefix. */ function Unit(value, name) { @@ -37,8 +37,8 @@ function factory (type, config, load, typed) { throw new Error('Constructor must be called with the new operator'); } - if (value != undefined && !isNumeric(value)) { - throw new TypeError('First parameter in Unit constructor must numeric'); + if (!(value === undefined || isNumeric(value) || value.isComplex)) { + throw new TypeError('First parameter in Unit constructor must be number, BigNumber, Fraction, Complex, or undefined'); } if (name != undefined && (typeof name !== 'string' || name == '')) { throw new TypeError('Second parameter in Unit constructor must be a string'); @@ -692,9 +692,10 @@ function factory (type, config, load, typed) { res.value = pow(res.value, p); // only allow numeric output, we don't want to return a Complex number - if (!isNumeric(res.value)) { - res.value = NaN; - } + //if (!isNumeric(res.value)) { + // res.value = NaN; + //} + // Update: Complex supported now } else { res.value = null; @@ -959,8 +960,8 @@ function factory (type, config, load, typed) { this.simplifyUnitListLazy(); // Now apply the best prefix - // Units must have only one unit and not have the fixPrefix flag set - if (this.units.length === 1 && !this.fixPrefix) { + // Units must have only one unit and not have the fixPrefix flag set and not be Complex + if (this.units.length === 1 && !this.fixPrefix && !(this.value && this.value.isComplex)) { // Units must have integer powers, otherwise the prefix will change the // outputted value by not-an-integer-power-of-ten if (Math.abs(this.units[0].power - Math.round(this.units[0].power)) < 1e-14) { @@ -972,6 +973,9 @@ function factory (type, config, load, typed) { var value = this._denormalize(this.value); var str = (this.value !== null) ? format(value, options || {}) : ''; var unitStr = this.formatUnits(); + if(this.value && this.value.isComplex) { + str = "(" + str + ")"; // Surround complex values with ( ) + } if(unitStr.length > 0 && str.length > 0) { str += " "; } @@ -2549,6 +2553,10 @@ function factory (type, config, load, typed) { return new type.Fraction(x); }, + Complex: function (x) { + return x; + }, + number: function (x) { return x; } diff --git a/lib/type/unit/function/unit.js b/lib/type/unit/function/unit.js index 8d3a8493c..9eccc4b37 100644 --- a/lib/type/unit/function/unit.js +++ b/lib/type/unit/function/unit.js @@ -40,7 +40,7 @@ function factory (type, config, load, typed) { return type.Unit.parse(x); // a unit with value, like '5cm' }, - 'number | BigNumber | Fraction, string': function (value, unit) { + 'number | BigNumber | Fraction | Complex, string': function (value, unit) { return new type.Unit(value, unit); }, diff --git a/test/function/arithmetic/abs.test.js b/test/function/arithmetic/abs.test.js index b1658ef1b..d439e41f8 100644 --- a/test/function/arithmetic/abs.test.js +++ b/test/function/arithmetic/abs.test.js @@ -81,6 +81,9 @@ describe('abs', function () { u = math.abs(math.unit(math.fraction(2,3), 'm')); assert.equal(u.toString(), '2/3 m'); + + u = math.abs(math.unit(math.complex(-4, 3), 'in')); + assert.equal(u.toString(), '5 in'); }); it('should throw an error in case of invalid number of arguments', function() { diff --git a/test/function/arithmetic/addScalar.test.js b/test/function/arithmetic/addScalar.test.js index cb603d61a..91bbd4658 100644 --- a/test/function/arithmetic/addScalar.test.js +++ b/test/function/arithmetic/addScalar.test.js @@ -96,6 +96,8 @@ describe('add', function() { approx.deepEqual(add(math.unit(5, 'km'), math.unit(100, 'mile')), math.unit(165.93, 'km')); approx.deepEqual(add(math.unit(math.fraction(1,3), 'm'), math.unit(math.fraction(1,3), 'm')).toString(), '2/3 m'); + + approx.deepEqual(add(math.unit(math.complex(-3, 2), 'g'), math.unit(math.complex(5, -6), 'g')).toString(), '(2 - 4i) g'); }); it('should throw an error for two measures of different units', function() { diff --git a/test/function/arithmetic/cbrt.test.js b/test/function/arithmetic/cbrt.test.js index dda5d1d55..bf66287c9 100644 --- a/test/function/arithmetic/cbrt.test.js +++ b/test/function/arithmetic/cbrt.test.js @@ -106,6 +106,9 @@ describe('cbrt', function() { assert.deepEqual(cbrt(math.unit(math.bignumber(27), 'm^3')).value, math.bignumber(3)); assert(cbrt(math.unit(math.bignumber(-27), 'm^3')).value.isBigNumber); assert.deepEqual(cbrt(math.unit(math.bignumber(-27), 'm^3')).value, math.bignumber(-3)); + + assert(cbrt(math.unit(math.complex(-46, 9), 's^3')).value.isComplex); + approx.deepEqual(cbrt(math.unit(math.complex(-46, 9), 's^3')).value, math.complex(2, 3)); }); it('should throw an error when used with a string', function() { diff --git a/test/function/arithmetic/divide.test.js b/test/function/arithmetic/divide.test.js index 023ef159c..123c641ab 100644 --- a/test/function/arithmetic/divide.test.js +++ b/test/function/arithmetic/divide.test.js @@ -130,6 +130,8 @@ describe('divide', function() { assert.equal(divide(10, math.unit('4 mg/s')).toString(), '2.5 s / mg'); assert.equal(divide(10, math.unit(math.fraction(4), 'mg/s')).toString(), '5/2 s / mg'); + + approx.equal(math.format(divide(10, math.unit(math.complex(1,2), 'm/s')), 14), '(2 - 4i) s / m'); }); it('should divide two units', function() { @@ -138,6 +140,10 @@ describe('divide', function() { var a = math.unit(math.fraction(75), 'mi/h'); var b = math.unit(math.fraction(40), 'mi/gal'); assert.equal(divide(a, b).to('gal/minute').toString(), '1/32 gal / minute'); + + var c = math.unit(math.complex(21, 1), 'kg'); + var d = math.unit(math.complex(2, -3), 's'); + assert.equal(divide(c, d).toString(), "(3 + 5i) kg / s"); }); it('should divide one valued unit by a valueless unit and vice-versa', function() { diff --git a/test/function/arithmetic/multiply.test.js b/test/function/arithmetic/multiply.test.js index 8bf04b846..4a05d9cd9 100644 --- a/test/function/arithmetic/multiply.test.js +++ b/test/function/arithmetic/multiply.test.js @@ -148,6 +148,9 @@ describe('multiply', function() { assert.equal(multiply(3, unit(math.fraction(1,4), 'm')).toString(), '3/4 m'); assert.equal(multiply(math.fraction(1,4), unit(3, 'm')).toString(), '3/4 m'); assert.equal(multiply(unit(3, 'm'), math.fraction(1,4)).toString(), '3/4 m'); + + assert.equal(multiply(unit(math.complex(9, 8), 'm'), 2).toString(), '(18 + 16i) m'); + assert.equal(math.format(multiply(unit(math.complex(2, 3), 'g'), math.complex(4, 5)), 14), '(-7 + 22i) g'); }); it('should multiply a number and a unit without value correctly', function() { @@ -165,6 +168,7 @@ describe('multiply', function() { assert.equal(multiply(unit('65 mi/h'), unit('2 h')).to('mi').toString(), '130 mi'); assert.equal(multiply(unit('2 L'), unit('1 s^-1')).toString(), '2 L / s'); assert.equal(multiply(unit('2 m/s'), unit('0.5 s/m')).toString(), '1'); + assert.equal(multiply(unit(math.complex(3,-4), 'N'), unit(math.complex(7,-2), 'm')).toString(), '(13 - 34i) J'); }); it('should multiply valueless units correctly', function() { @@ -193,12 +197,12 @@ describe('multiply', function() { assert.equal(multiply(unit('inch'), bignumber(2)).toString(), '2 inch'); }); - it('should throw an error in case of unit non-numeric argument', function() { - // Multiplying two units is supported now --ericman314 + // Multiplying two units is supported now //assert.throws(function () {multiply(math.unit('5cm'), math.unit('4cm'));}, /TypeError: Unexpected type/); - assert.throws(function () {multiply(math.unit('5cm'), math.complex('2+3i'));}, /TypeError: Unexpected type/); - assert.throws(function () {multiply(math.complex('2+3i'), math.unit('5cm'));}, /TypeError: Unexpected type/); + // Complex units are supported now + //assert.throws(function () {multiply(math.unit('5cm'), math.complex('2+3i'));}, /TypeError: Unexpected type/); + //assert.throws(function () {multiply(math.complex('2+3i'), math.unit('5cm'));}, /TypeError: Unexpected type/); }); diff --git a/test/function/arithmetic/sign.test.js b/test/function/arithmetic/sign.test.js index 3dd05f271..3ade5dc20 100644 --- a/test/function/arithmetic/sign.test.js +++ b/test/function/arithmetic/sign.test.js @@ -4,6 +4,7 @@ var approx = require('../../../tools/approx'); var math = require('../../../index'); var bignumber = math.bignumber; var fraction = math.fraction; +var complex = math.complex; describe('sign', function() { it('should calculate the sign of a boolean', function () { @@ -51,6 +52,8 @@ describe('sign', function() { assert.deepEqual(math.sign(math.unit(bignumber(-5), 'cm')), bignumber(-1)); assert.deepEqual(math.sign(math.unit(fraction(5), 'cm')), fraction(1)); assert.deepEqual(math.sign(math.unit(fraction(-5), 'cm')), fraction(-1)); + + assert.deepEqual(math.sign(math.unit(complex(3,4), 'mi')), complex(0.6,0.8)); }); it('should throw an error when used with a string', function() { diff --git a/test/function/arithmetic/sqrt.test.js b/test/function/arithmetic/sqrt.test.js index e37ebdae4..1fa1b4cc7 100644 --- a/test/function/arithmetic/sqrt.test.js +++ b/test/function/arithmetic/sqrt.test.js @@ -69,9 +69,10 @@ describe('sqrt', function() { assert.equal(sqrt(math.unit('4 kg')).toString(), '2 kg^0.5'); }); - it('should return NaN when computing the square root of a negative unit', function() { + it('should return a Unit with a Complex value when computing the square root of a negative unit', function() { // Update this when support for complex units is added - assert.equal(sqrt(math.unit('-25 m^2/s^2')).toString(), 'NaN m / s'); + //assert.equal(sqrt(math.unit('-25 m^2/s^2')).toString(), 'NaN m / s'); + assert.equal(math.format(sqrt(math.unit('-25 m^2/s^2')), 14), '(5i) m / s'); }); it('should throw an error when used with a string', function() { diff --git a/test/function/arithmetic/subtract.test.js b/test/function/arithmetic/subtract.test.js index 965960b94..57284dcc5 100644 --- a/test/function/arithmetic/subtract.test.js +++ b/test/function/arithmetic/subtract.test.js @@ -94,6 +94,9 @@ describe('subtract', function() { approx.deepEqual(subtract(math.unit(5, 'km'), math.unit(100, 'mile')), math.unit(-155.93, 'km')); assert.deepEqual(subtract(math.unit(math.bignumber(5), 'km'), math.unit(math.bignumber(2), 'km')), math.unit(math.bignumber(3), 'km')); + + assert.deepEqual(subtract(math.unit(math.complex(10,10), 'K'), math.unit(math.complex(3,4), 'K')), math.unit(math.complex(7,6), 'K')); + assert.deepEqual(subtract(math.unit(math.complex(10,10), 'K'), math.unit(3, 'K')), math.unit(math.complex(7,10), 'K')); }); it('should throw an error if subtracting two quantities of different units', function() { diff --git a/test/function/arithmetic/unaryMinus.test.js b/test/function/arithmetic/unaryMinus.test.js index 812202698..75fbfc275 100644 --- a/test/function/arithmetic/unaryMinus.test.js +++ b/test/function/arithmetic/unaryMinus.test.js @@ -3,6 +3,7 @@ var assert = require('assert'); var math = require('../../../index'); var bignumber = math.bignumber; var fraction = math.fraction; +var complex = math.complex; describe('unaryMinus', function() { it('should return unary minus of a boolean', function () { @@ -52,6 +53,7 @@ describe('unaryMinus', function() { it('should perform unary minus of a unit', function() { assert.equal(math.unaryMinus(math.unit(5, 'km')).toString(), '-5 km'); assert.equal(math.unaryMinus(math.unit(fraction(2/3), 'km')).toString(), '-2/3 km'); + assert.equal(math.unaryMinus(math.unit(complex(2,-4), 'gal')).toString(), '(-2 + 4i) gal'); }); it('should perform element-wise unary minus on a matrix', function() { diff --git a/test/function/trigonometry/cos.test.js b/test/function/trigonometry/cos.test.js index 714e64285..939e01f96 100644 --- a/test/function/trigonometry/cos.test.js +++ b/test/function/trigonometry/cos.test.js @@ -99,6 +99,8 @@ describe('cos', function() { assert(cos(unit(math.bignumber(45), 'deg')).isBigNumber); approx.equal(cos(unit(math.bignumber(45), 'deg')).toNumber(), 0.707106781186548); + + approx.deepEqual(cos(unit(complex(1,1), 'rad')), complex(0.833730025131149, -0.988897705762865)); }); it('should throw an error if called with an invalid unit', function() { diff --git a/test/function/trigonometry/cosh.test.js b/test/function/trigonometry/cosh.test.js index f38b87961..adccea206 100644 --- a/test/function/trigonometry/cosh.test.js +++ b/test/function/trigonometry/cosh.test.js @@ -64,6 +64,8 @@ describe('cosh', function() { assert(cosh(unit(math.bignumber(90), 'deg')).isBigNumber); approx.equal(cosh(unit(math.bignumber(90), 'deg')).toNumber(), 2.5091784786581); + + approx.deepEqual(cosh(math.unit(complex('2 + i'), 'rad')), complex(2.0327230070197, 3.0518977991518)); }); it('should throw an error if called with an invalid unit', function() { diff --git a/test/function/trigonometry/cot.test.js b/test/function/trigonometry/cot.test.js index 6ae661bf6..d14516cf0 100644 --- a/test/function/trigonometry/cot.test.js +++ b/test/function/trigonometry/cot.test.js @@ -82,6 +82,8 @@ describe('cot', function() { assert(cot(unit(math.bignumber(45), 'deg')).isBigNumber); approx.equal(cot(unit(math.bignumber(45), 'deg')).toNumber(), 1); + + approx.deepEqual(cot(math.unit(complex('1+i'), 'rad')), complex(0.217621561854403, -0.868014142895925)); }); it('should throw an error if called with an invalid unit', function() { diff --git a/test/function/trigonometry/coth.test.js b/test/function/trigonometry/coth.test.js index 90b782819..9ae94b408 100644 --- a/test/function/trigonometry/coth.test.js +++ b/test/function/trigonometry/coth.test.js @@ -52,6 +52,8 @@ describe('coth', function() { assert(coth(unit(math.bignumber(90), 'deg')).isBigNumber); approx.equal(coth(unit(math.bignumber(90), 'deg')).toNumber(), 1.0903314107274); + + approx.deepEqual(coth(math.unit(complex('2 + i'), 'rad')), complex(0.98432922645819, -0.032797755533753)); }); it('should throw an error if called with an invalid unit', function() { diff --git a/test/function/trigonometry/csc.test.js b/test/function/trigonometry/csc.test.js index f3698a3ee..5516c32ed 100644 --- a/test/function/trigonometry/csc.test.js +++ b/test/function/trigonometry/csc.test.js @@ -73,6 +73,8 @@ describe('csc', function() { assert(csc(unit(math.bignumber(45), 'deg')).isBigNumber); approx.equal(csc(unit(math.bignumber(45), 'deg')).toNumber(), 1.41421356237310); + + approx.deepEqual(csc(unit(complex('1+i'), 'rad')), complex(0.621518017170428, -0.303931001628426)); }); it('should throw an error if called with an invalid unit', function() { diff --git a/test/function/trigonometry/csch.test.js b/test/function/trigonometry/csch.test.js index 352de58c6..6915d4206 100644 --- a/test/function/trigonometry/csch.test.js +++ b/test/function/trigonometry/csch.test.js @@ -55,6 +55,8 @@ describe('csch', function() { assert(csch(unit(math.bignumber(90), 'deg')).isBigNumber); approx.equal(csch(unit(math.bignumber(90), 'deg')).toNumber(), 0.4345372080947); + + approx.deepEqual(csch(unit(complex('2 + i'), 'rad')), complex(0.14136302161241, -0.22837506559969)); }); it('should throw an error if called with an invalid unit', function() { diff --git a/test/function/trigonometry/sec.test.js b/test/function/trigonometry/sec.test.js index 5258a8b7d..33c417a45 100644 --- a/test/function/trigonometry/sec.test.js +++ b/test/function/trigonometry/sec.test.js @@ -82,6 +82,8 @@ describe('sec', function() { assert(sec(unit(math.bignumber(45), 'deg')).isBigNumber); approx.equal(sec(unit(math.bignumber(45), 'deg')).toNumber(), 1.41421356237310); + + approx.deepEqual(sec(unit(complex('1+i'), 'rad')), complex(0.498337030555187, 0.591083841721045)); }); it('should throw an error if called with an invalid unit', function() { diff --git a/test/function/trigonometry/sech.test.js b/test/function/trigonometry/sech.test.js index 7df3afc81..75c6a3ef2 100644 --- a/test/function/trigonometry/sech.test.js +++ b/test/function/trigonometry/sech.test.js @@ -53,6 +53,8 @@ describe('sech', function() { assert(sech(unit(math.bignumber(90), 'deg')).isBigNumber); approx.equal(sech(unit(math.bignumber(90), 'deg')).toNumber(), 0.39853681533839); + + approx.deepEqual(sech(unit(complex('2 + i'), 'rad')), complex(0.15117629826558, -0.22697367539372)); }); it('should throw an error if called with an invalid unit', function() { diff --git a/test/function/trigonometry/sin.test.js b/test/function/trigonometry/sin.test.js index 4cc552c39..eccb91e00 100644 --- a/test/function/trigonometry/sin.test.js +++ b/test/function/trigonometry/sin.test.js @@ -91,6 +91,8 @@ describe('sin', function() { assert(sin(unit(math.bignumber(45), 'deg')).isBigNumber); approx.equal(sin(unit(math.bignumber(45), 'deg')).toNumber(), 0.707106781186548); + + approx.deepEqual(sin(unit(complex('1+i'), 'rad')), complex(1.298457581415977, 0.634963914784736)); }); it('should throw an error if called with an invalid unit', function() { diff --git a/test/function/trigonometry/sinh.test.js b/test/function/trigonometry/sinh.test.js index c96066c88..34b2711af 100644 --- a/test/function/trigonometry/sinh.test.js +++ b/test/function/trigonometry/sinh.test.js @@ -67,6 +67,8 @@ describe('sinh', function() { assert(sinh(unit(math.bignumber(90), 'deg')).isBigNumber); approx.equal(sinh(unit(math.bignumber(90), 'deg')).toNumber(), 2.3012989023073); + + approx.deepEqual(sinh(unit(complex('2 + i'), 'rad')), complex(1.9596010414216, 3.1657785132162)); }); it('should throw an error if called with an invalid unit', function() { diff --git a/test/function/trigonometry/tan.test.js b/test/function/trigonometry/tan.test.js index 8daa15b1e..37797ac27 100644 --- a/test/function/trigonometry/tan.test.js +++ b/test/function/trigonometry/tan.test.js @@ -76,6 +76,8 @@ describe('tan', function() { assert(tan(unit(math.bignumber(60), 'deg')).isBigNumber); approx.equal(tan(unit(math.bignumber(60), 'deg')).toNumber(), math.sqrt(3)); + + approx.deepEqual(tan(unit(complex('1+i'), 'rad')), complex(0.271752585319512, 1.083923327338695)); }); it('should throw an error if called with an invalid unit', function() { diff --git a/test/function/trigonometry/tanh.test.js b/test/function/trigonometry/tanh.test.js index 335a5029e..fbb34e6dd 100644 --- a/test/function/trigonometry/tanh.test.js +++ b/test/function/trigonometry/tanh.test.js @@ -63,6 +63,8 @@ describe('tanh', function() { assert(tanh(unit(math.bignumber(90), 'deg')).isBigNumber); approx.equal(tanh(unit(math.bignumber(90), 'deg')).toNumber(), 0.91715233566727); + + approx.deepEqual(tanh(unit(complex('2 + i'), 'rad')), complex(1.0147936161466, 0.033812826079897)); }); it('should throw an error if called with an invalid unit', function() { diff --git a/test/type/unit/Unit.test.js b/test/type/unit/Unit.test.js index 50a5570bd..bdb3ebce8 100644 --- a/test/type/unit/Unit.test.js +++ b/test/type/unit/Unit.test.js @@ -39,6 +39,12 @@ describe('Unit', function() { assert.equal(unit1.units[0].unit.name, 'm'); }); + it('should create a unit with Complex value', function () { + var unit1 = new Unit(math.complex(500, 600), 'cm'); + assert.deepEqual(unit1.value, math.complex(5, 6)); + assert.equal(unit1.units[0].unit.name, 'm'); + }); + it('should create square meter correctly', function() { var unit1 = new Unit(0.000001, 'km2'); assert.equal(unit1.value, 1); @@ -155,6 +161,16 @@ describe('Unit', function() { assert.equal(new Unit(100, 'cm').equals(new Unit(math.fraction(2), 'm')), false); }); + it('should test whether two Complex units are equal', function() { + assert.equal(new Unit(math.complex(3, 4), 'km').equals(new Unit(math.complex(3000, 4000), 'm')), true); + assert.equal(new Unit(math.complex(3, 4), 'km').equals(new Unit(math.complex(3000, 10), 'm')), false); + }); + + it('should test whether a Complex unit and a unit with a number are equal', function() { + assert.equal(new Unit(math.complex(3, 0), 'km').equals(new Unit(3000, 'm')), true); + assert.equal(new Unit(math.complex(3, 4), 'km').equals(new Unit(3000, 'm')), false); + }); + }); describe('clone', function() { @@ -190,6 +206,14 @@ describe('Unit', function() { assert(u1.value !== u2.value); // should be cloned }); + it('should clone a Complex unit', function() { + var u1 = new Unit(math.complex(1,3), 'cm'); + var u2 = u1.clone(); + assert(u1 !== u2); + assert.deepEqual(u1, u2); + assert(u1.value !== u2.value); // should be cloned + }); + }); describe('toNumber', function() { @@ -218,7 +242,7 @@ describe('Unit', function() { }); }); - describe('toNumberic', function() { + describe('toNumeric', function() { it ('should convert a unit to a numeric value', function () { var u = new Unit(math.fraction(1,3), 'cm'); assert.deepEqual(u.toNumeric('mm'), math.fraction(10,3)); @@ -268,6 +292,13 @@ describe('Unit', function() { assert.equal(u2.fixPrefix, true); }); + it ('should convert a Complex unit', function() { + var u1 = new Unit(math.complex(300,400), 'kPa'); + var u2 = u1.to('lbf/in^2'); + approx.deepEqual(u2.value, math.complex(300000, 400000)); + assert.deepEqual(u2.toString(), "(43.511321319062766 + 58.01509509208369i) lbf / in^2"); + }); + it ('should convert a unit to a fixed unit', function () { var u1 = new Unit(5000, 'cm'); assert.equal(u1.value, 50); @@ -447,6 +478,10 @@ describe('Unit', function() { it('should convert a unit with Fraction to string properly', function() { assert.equal(new Unit(math.fraction(9/10), 'mm').toString(), '9/10 mm'); }); + + it('should convert a Complex unit to string properly', function() { + assert.equal(new Unit(math.complex(-1,-2), 'J / (mol K)').toString(), '(-1 - 2i) J / (mol K)'); + }); }); describe('simplifyUnitListLazy', function() { @@ -531,9 +566,19 @@ describe('Unit', function() { unit: 'cm', fixPrefix: false }); + approx.deepEqual(new Unit(math.complex(2, 4), 'g').toJSON(), + { + mathjs: 'Unit', + value: math.complex(2, 4), + unit: 'g', + fixPrefix: false + }); var str = JSON.stringify(new Unit(math.fraction(0.375), 'cm')); assert.deepEqual(str, '{"mathjs":"Unit","value":{"mathjs":"Fraction","n":3,"d":8},"unit":"cm","fixPrefix":false}'); + + var cmpx = JSON.stringify(new Unit(math.complex(2, 4), 'g')); + assert.equal(cmpx, '{"mathjs":"Unit","value":{"mathjs":"Complex","re":2,"im":4},"unit":"g","fixPrefix":false}'); }); it('fromJSON', function () { @@ -559,6 +604,14 @@ describe('Unit', function() { fixPrefix: false }); assert.deepEqual(u7, new Unit(math.fraction(0.375), 'cm')) + + var u8 = Unit.fromJSON({ + mathjs: 'Unit', + value: math.complex(2, 4), + unit: 'g', + fixPrefix: false + }); + assert.deepEqual(u8, new Unit(math.complex(2,4), 'g')); }); it('toJSON -> fromJSON should recover an "equal" unit', function() { @@ -598,6 +651,10 @@ describe('Unit', function() { assert.equal(new Unit(math.fraction(4/5), 'm').format(), '4/5 m'); }); + it('should format a Complex unit', function() { + assert.equal(new Unit(math.complex(-2, 4.5), 'mm').format(14), '(-2 + 4.5i) mm'); + }); + it('should ignore properties in Object.prototype when finding the best prefix', function() { Object.prototype.foo = 'bar'; From a14b8b7a681c3b1061e42d31fafc9fdf517fbb06 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 24 Dec 2015 05:45:13 +0000 Subject: [PATCH 2/2] Added VA, VAR, and custom logic to convert between them. --- lib/function/arithmetic/abs.js | 6 +-- lib/type/complex/Complex.js | 5 +- lib/type/unit/Unit.js | 83 +++++++++++++++++++++++++++++++--- test/type/unit/Unit.test.js | 13 ++++++ 4 files changed, 93 insertions(+), 14 deletions(-) diff --git a/lib/function/arithmetic/abs.js b/lib/function/arithmetic/abs.js index f609dff79..9f191d4d8 100644 --- a/lib/function/arithmetic/abs.js +++ b/lib/function/arithmetic/abs.js @@ -63,11 +63,7 @@ function factory (type, config, load, typed) { }, 'Unit': function(x) { - // This gives correct, but unexpected, results for units with an offset. - // For example, abs(-283.15 degC) = -263.15 degC !!! - var ret = x.clone(); - ret.value = abs(ret.value); - return ret; + return x.abs(); } }); diff --git a/lib/type/complex/Complex.js b/lib/type/complex/Complex.js index 6c0778224..3f8a7002d 100644 --- a/lib/type/complex/Complex.js +++ b/lib/type/complex/Complex.js @@ -5,7 +5,8 @@ var format = require('../../utils/number').format; function factory (type, config, load, typed) { // TODO: remove dependency on Unit, not good for modularization - var Unit = load(require('./../unit/Unit')); + // Update: Unit.hasBase now accepts a String, so no need to use Unit.BASE_UNITS + // var Unit = load(require('./../unit/Unit')); /** * @constructor Complex @@ -317,7 +318,7 @@ function factory (type, config, load, typed) { var r = arguments[0], phi = arguments[1]; if (isNumber(r)) { - if (phi && phi.isUnit && phi.hasBase(Unit.BASE_UNITS.ANGLE)) { + if (phi && phi.isUnit && phi.hasBase('ANGLE')) { // convert unit to a number in radians phi = phi.toNumber('rad'); } diff --git a/lib/type/unit/Unit.js b/lib/type/unit/Unit.js index 11aec8f85..d11f1922e 100644 --- a/lib/type/unit/Unit.js +++ b/lib/type/unit/Unit.js @@ -9,11 +9,13 @@ function factory (type, config, load, typed) { var multiply = load(require('../../function/arithmetic/multiplyScalar')); var divide = load(require('../../function/arithmetic/divideScalar')); var pow = load(require('../../function/arithmetic/pow')); + var abs = load(require('../../function/arithmetic/abs')); var equal = load(require('../../function/relational/equal')); var isNumeric = load(require('../../function/utils/isNumeric')); var format = load(require('../../function/utils/format')); var getTypeOf = load(require('../../function/utils/typeof')); var toNumber = load(require('../../type/number')); + var Complex = load(require('../../type/complex/Complex')); /** * @constructor Unit @@ -564,10 +566,18 @@ function factory (type, config, load, typed) { /** * check if this unit has given base unit * If this unit is a derived unit, this will ALWAYS return false, since by definition base units are not derived. - * @param {BASE_UNITS | undefined} base + * @param {BASE_UNITS | STRING | undefined} base */ Unit.prototype.hasBase = function (base) { + if(typeof(base) === "string") { + base = BASE_UNITS[base]; + } + + if(!base) + return false; + + // All dimensions must be the same for(var i=0; i 1e-12) { @@ -706,6 +716,25 @@ function factory (type, config, load, typed) { return res; }; + /** + * Calculate the absolute value of a unit + * @param {number | Fraction | BigNumber} x + * @returns {Unit} The result: |x|, absolute value of x + */ + Unit.prototype.abs = function () { + // This gives correct, but unexpected, results for units with an offset. + // For example, abs(-283.15 degC) = -263.15 degC !!! + var ret = this.clone(); + ret.value = abs(ret.value); + + for(var i in ret.units) { + if(ret.units[i].unit.name === 'VA' || ret.units[i].unit.name === 'VAR') { + ret.units[i].unit = UNITS["W"]; + } + } + + return ret; + }; /** * Convert the unit to a specific unit name. @@ -959,9 +988,30 @@ function factory (type, config, load, typed) { // Simplfy the unit list, if necessary this.simplifyUnitListLazy(); + // Apply some custom logic for handling VA and VAR. The goal is to express the value of the unit as a real value, if possible. Otherwise, use a real-valued unit instead of a complex-valued one. + var isImaginary = false; + var isReal = true; + if(typeof(this.value) !== 'undefined' && this.value !== null && this.value.isComplex) { + // TODO: Make this better, for example, use relative magnitude of re and im rather than absolute + isImaginary = Math.abs(this.value.re) < 1e-14; + isReal = Math.abs(this.value.im) < 1e-14; + } + + for(var i in this.units) { + if(this.units[i].unit) { + if(this.units[i].unit.name === 'VA' && isImaginary) { + this.units[i].unit = UNITS["VAR"]; + } + else if(this.units[i].unit.name === 'VAR' && !isImaginary) { + this.units[i].unit = UNITS["VA"]; + } + } + } + + // Now apply the best prefix - // Units must have only one unit and not have the fixPrefix flag set and not be Complex - if (this.units.length === 1 && !this.fixPrefix && !(this.value && this.value.isComplex)) { + // Units must have only one unit and not have the fixPrefix flag set + if (this.units.length === 1 && !this.fixPrefix) { // Units must have integer powers, otherwise the prefix will change the // outputted value by not-an-integer-power-of-ten if (Math.abs(this.units[0].power - Math.round(this.units[0].power)) < 1e-14) { @@ -974,7 +1024,7 @@ function factory (type, config, load, typed) { var str = (this.value !== null) ? format(value, options || {}) : ''; var unitStr = this.formatUnits(); if(this.value && this.value.isComplex) { - str = "(" + str + ")"; // Surround complex values with ( ) + str = "(" + str + ")"; // Surround complex values with ( ) to enable better parsing } if(unitStr.length > 0 && str.length > 0) { str += " "; @@ -1004,14 +1054,16 @@ function factory (type, config, load, typed) { // Note: the units value can be any numeric type, but to find the best // prefix it's enough to work with limited precision of a regular number - var absValue = Math.abs(toNumber(this.value)); + // Update: using mathjs abs since we also allow complex numbers + var absValue = abs(this.value); + var absUnitValue = abs(this.units[0].unit.value); var bestPrefix = this.units[0].prefix; if (absValue === 0) { return bestPrefix; } var power = this.units[0].power; var bestDiff = Math.abs( - Math.log(absValue / Math.pow(bestPrefix.value * this.units[0].unit.value, power)) / Math.LN10 - 1.2); + Math.log(absValue / Math.pow(bestPrefix.value * absUnitValue, power)) / Math.LN10 - 1.2); var prefixes = this.units[0].unit.prefixes; for (var p in prefixes) { @@ -1020,7 +1072,7 @@ function factory (type, config, load, typed) { if (prefix.scientific) { var diff = Math.abs( - Math.log(absValue / Math.pow(prefix.value * this.units[0].unit.value, power)) / Math.LN10 - 1.2); + Math.log(absValue / Math.pow(prefix.value * absUnitValue, power)) / Math.LN10 - 1.2); if (diff < bestDiff || (diff === bestDiff && prefix.name.length < bestPrefix.name.length)) { @@ -2180,6 +2232,23 @@ function factory (type, config, load, typed) { offset: 0 }, + // Electrical power units + VAR: { + name: 'VAR', + base: BASE_UNITS.POWER, + prefixes: PREFIXES.SHORT, + value: new Complex(0,1), + offset: 0 + }, + + VA: { + name: 'VA', + base: BASE_UNITS.POWER, + prefixes: PREFIXES.SHORT, + value: 1, + offset: 0 + }, + // Pressure Pa: { name: 'Pa', diff --git a/test/type/unit/Unit.test.js b/test/type/unit/Unit.test.js index bdb3ebce8..6c6b64152 100644 --- a/test/type/unit/Unit.test.js +++ b/test/type/unit/Unit.test.js @@ -655,6 +655,19 @@ describe('Unit', function() { assert.equal(new Unit(math.complex(-2, 4.5), 'mm').format(14), '(-2 + 4.5i) mm'); }); + it('should format units with VA and VAR correctly', function() { + assert.equal(math.eval('4000 VAR + 3000 VA').format(), "(3 + 4i) kVA"); + assert.equal(math.eval('3000 VA + 4000 VAR').format(), "(3 + 4i) kVA"); + assert.equal(math.eval('4000 VAR').format(), "(4) kVAR"); + assert.equal(math.eval('4000i VA').format(), "(4) kVAR"); + assert.equal(math.eval('4000i VAR').format(), "(-4) kVA"); + assert.equal(math.eval('abs(4000 VAR + 3000 VA)').format(), "5 kW"); + assert.equal(math.eval('abs(3000 VA + 4000 VAR)').format(), "5 kW"); + assert.equal(math.eval('abs(4000 VAR)').format(), "4 kW"); + assert.equal(math.eval('abs(4000i VA)').format(), "4 kW"); + assert.equal(math.eval('abs(4000i VAR)').format(), "4 kW"); + }); + it('should ignore properties in Object.prototype when finding the best prefix', function() { Object.prototype.foo = 'bar';