davidedc / Algebrite

Computer Algebra System in Javascript (Typescript)
http://algebrite.org
MIT License
964 stars 59 forks source link

calling Algebrite.print() throws "ReferenceError: Eval_display is not defined" #21

Open eweitnauer opened 8 years ago

eweitnauer commented 8 years ago

I am trying to turn the result of Algebrite.simplify('x+x+x') into a string. I thought Algebrite.print() might do that, but it fails, throwing the exception "ReferenceError: Eval_display is not defined".

Some background: What I am actually trying to accomplish is testing whether two expressions (this could be equations, too) are equivalent to each other. I was using Algebrite.run(expr1) == Algebrite.run(expr2), but this has side effects and does not work for equations or inequalities. I thought simplify might be the better way to go.

davidedc commented 8 years ago

I'll look into the ReferenceError: Eval_display is not defined problem.

In regards to getting text out of an Algebrite expression: .toString() should do the job.

In regards to checking equivalences, here are some starting points:

Algebrite.equal(Algebrite.eval('x+x+x'),Algebrite.eval('3*x')); // 1
Algebrite.equal(Algebrite.eval('x+x+x+1'),Algebrite.eval('3*x')); // 0

Algebrite.eval('x+x+x == 3*x').toString(); // "1"
Algebrite.eval('x+x+x > 3*x').toString(); // "0"
Algebrite.eval('x+x+x + 1 > 3*x').toString(); // "1"

Note that indeed it's best, for more complex simplifications, to unleash "simplify" on both sides, otherwise for example this equivalence is not found:

Algebrite.equal(Algebrite.simplify('cos(x)^2 + sin(x)^2'),Algebrite.simplify('1')); // 1, not found without simplify

P.S. Just to manage your expectations to determine equivalences: to do that in general is too much of a complex job for any CASs, so your mileage may vary.

P.P.S. longer version ...there can be many forms to an expression, finding a canonical form is not always possible or practical, and CASs can't practically try them all. Many equivalences (e.g. some famous ones are Ramanujan formulas who could draw amazing equivalences without always being sure of correctness or proof) took many years of mathematician's acumen to demonstrate (and development of new algorithms to be able to be handled by CASs, again related to Ramanujan see equivalences of nested radicals where a general algorithm was only found in 1989 by Susan Landau https://en.wikipedia.org/wiki/Nested_radical#Landau.27s_algorithm )

davidedc commented 8 years ago

I see now your work on https://graspablemath.com/ , I'd have been less verbose if I knew you are in the field :-)

Yes please if you find other cases you'd expect to work please let me know...

eweitnauer commented 8 years ago

Thank you for explaining!

I ended up using Algebrite.equal(Algebrite.simplify(expr1), Algebrite.simplify(expr2)), which works great for the cases I'm concerned about!

For all cases, except... When I am comparing (1+1)^(1/3) and (0.5*4)^(1/3), Algebrite tells me they are different. The reason is that (1+1)^(1/3) simplifies to 2^(1/3), while (0.5*4)^(1/3) simplifies to 1.25992. Is there a straightforward solution to this? Maybe a way to disable simplifications that lose precision? Or casting real numbers back into integers?

davidedc commented 8 years ago

Using a float anywhere in an expression (0.5 in your example) makes the system calculate the approximate values.

You could:

  1. behind the scenes also force both sides to a float with "float" and check that the difference between them is really small.
  2. OR, more clean, we could add a way to have Algebrite to parse the floats in an expression and transform them into rationals in some circumstances (maybe a flag, or a "toRational" function or similar). But that float -> rationals mechanism would have to be implemented...
eweitnauer commented 8 years ago

Brilliant! Turning the floats into rationals as a first step will work perfectly! Something very naive like turning floats into (n*10^k)/(10^k) will probably work fine. I'll do that on my side. Let me know if you want to integrate something like this into Algebrite.

davidedc commented 8 years ago

yes slightly byzantine though that it would work on 1/2 but not on 1/3 etc... You sure that you'll be working only with floats that will work? Would you consider doing the more general case (or are you in a pinch)?

On a separate note, It'd be safer to cover your use cases with tests so that I'm sure I'm not going to break them with coming versions... I.e. if you add them you'll be covered.

eweitnauer commented 8 years ago

So my idea was to run something like this piece of code

rationalize_real = function(num) {
  if (Math.round(num) === num) return num;
  var parts = num.toString().split('.');
  return '(' + (parts[0]==='0' ? parts[1] : parts[0]+parts[1]) + '/1'
             + Array(parts[1].length+1).join('0') + ')';
}

to replace each number in the input by a rational to avoid any floating point operations in Algebrite. It would not change 1/3, since only the 1 and the 3 would be passed to the function, but it would turn 0.5 into 5/10 and 3.3333 into 33333/10000.

What is the more general case you mentioned? Where should I best add the test cases?

davidedc commented 8 years ago

handling the general case would mean introducing a notation for infinite digit sequences like 0.33333... (but sequences can be slightly more complex so might need to get more creative than just using the three dots ) and implementing the algorithm that finds out which rational creates the sequence. I can't link to it easily from iPhone but it should be rather compact.

...tests are in the test folder, try to see if there is a test file that fits the type of work you do.

davidedc commented 8 years ago

links for converting floats to rationals: https://rosettacode.org/wiki/Convert_decimal_number_to_rational and http://stackoverflow.com/questions/95727/how-to-convert-floats-to-human-readable-fractions

eweitnauer commented 8 years ago

The rosettacode link does not work for me, but the algorithm in the accepted answer from stackoverflow is neat!

What is the use case for this for an Algebrite user? I personally just needed a way to force exact integer-based calculations for initial expressions that contained floats. So in a way limiting the size of the denominator to make it more human-readable is exactly what I don't want :)

Is there a scenario where a user would actually want nice looking fraction approximations instead of real numbers or ugly fractions? I guess I can see it being useful in combination with some special notation to mark repeating sequences in the digits so the conversion between real notation and fraction notation is lossless. That's not part of the existing algorithm, though.

davidedc commented 8 years ago

With some euristics I think one can guess most human-entered floats of the type you seem to get as input.

I think it's intereating to have. Ideally I'll want to make an inverse symbolic calculator but one step at a time...

Maybe if you can tell what you expect people to present as input in your cases, I'm going to use that to do the euristics.

The problem with the special notation for the exact representation is that a) floats don't have that notation and b) can we expect users who can't be bothered to input the rational in the first place to then use the special notation of the exact decimal expansion?

davidedc commented 8 years ago

so, to the point, the use case for this is to straighten "lazily"-entered floats into guessed rationals. 0.5 to 1/2 but also 0.33333 to 1/3. So in other words it guesses normal textbook fractions, nothing too convoluted.

davidedc commented 8 years ago

so the name of the function could be, aptly, something like "guessSimpleFraction" . It's a convenience for input, nothing more.

murkle commented 8 years ago

There's a nice algorithm here by John Kennedy to convert double -> rational https://github.com/geogebra/geogebra/blob/master/common/src/main/java/org/geogebra/common/kernel/algos/AlgoFractionText.java

If you also want to convert double -> surd then you can use the PSLQ algorithm, eg https://github.com/geogebra/geogebra/blob/master/common/src/main/java/org/geogebra/common/kernel/cas/AlgoSurdText.java

murkle commented 8 years ago

Just realised I've written a JavaScript version which you're welcome to use under MIT licence

` DecimalToFraction: method(function(decimal, AccuracyFactor) { var FractionNumerator, FractionDenominator; var DecimalSign; var Z; var PreviousDenominator; var ScratchValue;

        var ret = [0,0];
        if (isNaN(decimal)) return ret; // return 0/0 

        if (decimal === Infinity) {
            ret[0] = 1;
            ret[1] = 0 ; // 1/0
            return ret;
        }
        if (decimal === -Infinity) {
            ret[0] = -1;
            ret[1] = 0; // -1/0
            return ret;
        }

        if (decimal < 0.0) DecimalSign = -1.0; else DecimalSign = 1.0;

        decimal = Math.abs(decimal);

        if (Math.abs(decimal - Math.floor(decimal)) < AccuracyFactor) { // handles exact integers including 0 
            FractionNumerator = decimal * DecimalSign;
            FractionDenominator = 1.0;

            ret[0] = FractionNumerator;
            ret[1] = FractionDenominator;
            return ret;
        }
        if (decimal < 1.0E-19) { // X = 0 already taken care of 
            FractionNumerator = DecimalSign;
            FractionDenominator = 9999999999999999999.0;

            ret[0] = FractionNumerator;
            ret[1] = FractionDenominator;
            return ret;
        }
        if (decimal > 1.0E19) {
            FractionNumerator = 9999999999999999999.0 * DecimalSign;
            FractionDenominator = 1.0;

            ret[0] = FractionNumerator;
            ret[1] = FractionDenominator;
            return ret;
        }

        Z = decimal;
        PreviousDenominator = 0.0;
        FractionDenominator = 1.0;
        do {
            Z = 1.0/(Z - Math.floor(Z));
            ScratchValue = FractionDenominator;
            FractionDenominator = FractionDenominator * Math.floor(Z) + PreviousDenominator;
            PreviousDenominator = ScratchValue;
            FractionNumerator = Math.floor(decimal * FractionDenominator + 0.5); // Rounding Function
        } while ( Math.abs((decimal - (FractionNumerator /FractionDenominator))) > AccuracyFactor && Z !== Math.floor(Z));
        FractionNumerator = DecimalSign*FractionNumerator;

        ret[0] = FractionNumerator;
        ret[1] = FractionDenominator;
        return ret;
    })

`

murkle commented 8 years ago

https://web.archive.org/web/20111027100847/http://homepage.smc.edu/kennedy_john/DEC2FRAC.PDF

davidedc commented 8 years ago

thanks a lot! First shot at this: https://github.com/davidedc/Algebrite/commit/11013a397435687294f8dd2ef385ba14bb7d2626 . I'm going to credit John Kennedy immediately in the next commit.

davidedc commented 8 years ago

credited

davidedc commented 8 years ago

approxratio now works on entire expressions: https://github.com/davidedc/Algebrite/blob/eb1ed2e6a3720435f60ad88100329d58b733b88f/tests/approxratio.coffee . So this should cater for typical ratios entered as floats. Hopefully most people would still enter irrational and transcendental numbers with an appropriate expression rather than as a float. Those other cases to be picked up later.