The n mixed with - or / seems to be an issue.
math.simplify('2n - 1').toString();
math.simplify('16n - 1').toString();
math.simplify('16n / 1').toString();
math.simplify('8 / 5n').toString();
math.simplify('8n - 4n').toString();
math.simplify('n - 1').toString();
math.simplify('1n - 1').toString();
math.simplify('2n + 1').toString();
math.simplify('2z - 1').toString();
math.simplify('2n -- 1').toString();
math.simplify('2n - 1',[]).toString();
[]: no rules applied.
@josdejong I think I need help here. The issue seems to be triggered by simplify.js@356, which is transforming a tree. The tricky bit is that the tree is the pattern tree that is used to match expressions. Specifically, the pattern being replaced is "n", which is the pattern for "node". What appears to happen is that the transformation is recurring on the the symbol "n" which is part of the input expression. Essentially, we appear to have a conflict between a placeholder "n" and its replacement, which contains "n". Ugh. Is the original simplify author available to assist here?
Essentially, we appear to have a conflict between a placeholder "n" and its replacement, which contains "n". Ugh. Is the original simplify author available to assist here?
ha ha, yes @ericman314 created the first implementation and @tetslee did some refactoring and improvements. Apparently none of us thought about the possibility that there could be a conflict between the variables in the rules and variables in the expression itself :flushed:.
I think to solve this we can do something like:
I'm not sure but I can imagine that option (2) is more efficient since its less work to transform a large set of rules than to transform one expression.
I'll try to help, although It's been a long time since I worked on simplify. If I remember right there shouldn't be a direct string comparison between a pattern and an input expression. The n in a pattern should match a node, not a literal 'n'. Or at least that's the way it should be. Maybe this is a stupid question, but does the bug still occur with another symbol in the input expression, like x?
there shouldn't be a direct string comparison between a pattern and an input expression
that sounds even better than my suggestions...
The issue does not occur with symbols that are not used in the rules:
math.simplify('2x - 1').toString(); // OK
math.simplify('2n - 1').toString(); // NOT OK (infinite loop)
math.simplify('2n1 - 1').toString(); // NOT OK (returns 1)
Ok. I will try to take a closer look tonight.
On Sep 30, 2017 8:11 AM, "Jos de Jong" notifications@github.com wrote:
there shouldn't be a direct string comparison between a pattern and an
input expressionthat sounds even better than my suggestions...
The issue does not occur with symbols that are not used in the rules:
math.simplify('2x - 1').toString(); // OKmath.simplify('2n - 1').toString(); // NOT OK (infinite loop)math.simplify('2n1 - 1').toString(); // NOT OK (returns 1)
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/josdejong/mathjs/issues/948#issuecomment-333311118,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AGDTkQyZgFe0EYJy6-VlftNF3RiEeiynks5snkwZgaJpZM4PmtFi
.
Thanks Eric :+1:
I found the source of the bug, it does indeed occur on line 356 and has existed since simplify was first written.
My test input was 1 - n. At the point where the bug occurs, the expression being simplified is (-(1 * n)) + 1 and the rule being applied is n+-n1 -> n-n1. A match between the expression and the rule is found, and the placeholders are correctly assigned as n = 1 and n1 = 1 * n. To generate the simplified expression, the right-hand-side n-n1 is transformed by traversing the tree top-down, replacing n with 1 and n1 with 1 * n:
n - n1
1 - n1 (Replaced n with 1)
1 - (1 * n) (Replaced n1 with 1 * n)
1 - (1 * 1) (Replaced n with 1)
By the time we get to step 3, the transform happily continues deeper into the already-transformed nodes, and transforms them further. My intuition says that if we traverse the tree bottom-up instead, this won't happen. Another alternative is to add a flag to each SymbolNode in the right-hand-side of the rule, and only transform those nodes:
res.traverse(function(n, path, parent) {
if(n.isSymbolNode) {
n.TransformThisNode = true;
}
});
res = res.transform(function(n, path, parent) {
if(n.TransformThisNode) {
if(matches.placeholders.hasOwnProperty(n.name)) {
var replace = matches.placeholders[n.name].clone();
return replace;
}
}
return n;
});
Resolved by #950
Eric, thanks for the fix and insight. I woulda totally botched it.
Thanks a lot Eric!
I've added some unit tests for these cases to prevent regressions, see https://github.com/josdejong/mathjs/commit/e033697001459b220a394d5e6cd9b508da4760ff.
Will be interesting to see whether we can find a solution which does not need .TransformThisNode to prevent infinite loops (though maybe this is simply the most robust solution). Traversing depth first could be interesting to look into, should not be hard to implement ortry, see #939.
This issue should be fixed now in v3.16.4
True, a custom function that stops traversing deeper after transforming a
node would be more efficient.
On Oct 1, 2017 12:29 PM, "Jos de Jong" notifications@github.com wrote:
Thanks a lot Eric!
I've added some unit tests for these cases to prevent regressions, see
e033697
https://github.com/josdejong/mathjs/commit/e033697001459b220a394d5e6cd9b508da4760ff
.Will be interesting to see whether we can find a solution which does not
need .TransformThisNode to prevent infinite loops (though maybe this is
simply the most robust solution). Traversing depth first could be
interesting to look into, should not be hard to implement ortry, see #939
https://github.com/josdejong/mathjs/issues/939.—
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
https://github.com/josdejong/mathjs/issues/948#issuecomment-333396426,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AGDTkcBD0Xm35Ow8whzQvxkvBsxsnpz9ks5sn9oSgaJpZM4PmtFi
.
I'm don't directly see where we should apply stopping traversal. The transform part res = res.transform(...) already stops iterating when you return a replacement instead of the original node, it will not traverse new, replaced parts of the tree, so I would think that already works as we want. Should matches = _ruleMatch(rule.expanded.l, res)[0]; only return one matched rule instead of multiple or something like that?
Are you sure? It looks like the replacement is made, and then the replacement itself is transformed:
hm, you're right. Thinking about it again this does not look like the most desirable solution to me in general, maybe we should change this? (not sure if you can consider this a bug but it gives tricky behavior...)
I will write a custom traversal function.
On Oct 2, 2017 11:38 AM, "Jos de Jong" notifications@github.com wrote:
hm, you're right. Thinking about it again this does not look like the most
desirable solution to me in general, maybe we should change this? (not sure
if you can consider this a bug but it gives tricky behavior...)—
You are receiving this because you modified the open/close state.
Reply to this email directly, view it on GitHub
https://github.com/josdejong/mathjs/issues/948#issuecomment-333607612,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AGDTkaCym2x4PR7lBUrWPdnYSSbXvuOAks5soR-QgaJpZM4PmtFi
.
that's a good idea, at least for the time being. But I think it makes sense to change the behavior of the built in transform (will be a breaking change for v4).
I removed the old traversal method and replaced it with a single-pass method that stops going deeper after replacing a node. #951
:+1: