Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitdb8e27d

Browse files
authored
Merge pull request#241 from samueltlg/canonical-forms-2
Fix/test: amendments to#238 (CanonicalForms PR), 'Number' form tests, some arch.
2 parents6d7fcb1 +8f7fad4 commitdb8e27d

File tree

11 files changed

+605
-156
lines changed

11 files changed

+605
-156
lines changed

‎CHANGELOG.md‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,59 @@
11
##[Unreleased]
22

3+
###? ? ?
4+
5+
- More consistency with BoxedSymbol properties causing symbol-binding: e.g.
6+
`isFinite` and`isInfinity` cause binding, in a similar way to`isOdd`,
7+
`isEven`.
8+
39
###Breaking Changes
410

511
- The`expr.value` property is now equivalent to`expr.valueOf()`. It was
612
previously equivalent to`expr.N().valueOf()`, however the implicit evaluation
713
of the expression produced some unexpected results, for example when the
814
expression was not pure.
915

16+
-`BoxedExpr.sgn` now returns_undefined_ for complex numbers, or symbols with a
17+
complex-number value.
18+
19+
-
20+
1021
###New Features and Improvements
1122

1223
- Added a rule to solve the equation`a^x + b = 0`
1324
- The LaTeX parser now supports the`\placeholder[]{}`,`\phantom{}`,
1425
`\hphantom{}`,`\vphantom{}`,`\mathstrut`,`\strut` and`\smash{}` commands.
1526

27+
- The range of recognized sign values, i.e. as returned from
28+
`BoxedExpression.sgn` has been simplified (e.g. '...-infinity' and 'nan' have
29+
been removed)
30+
31+
- The Power canonical-form is less aggressive - only carrying-out ops. as listed
32+
in doc. - is much more careful in its consideration of operand types &
33+
values... (for example, typically, exponents are required to be_numbers_:
34+
e.g.`x^1` will simplify, but`x^y` (where y===0), or`x^{1+0}`, will not)
35+
36+
###Issues Resolved
37+
38+
- Ensure expression LaTeX serialization is based on MathJSON generated with
39+
matching 'pretty' formatting (or not), therefore resulting in LaTeX with less
40+
prettification, where`prettify == false` (#daef87f)
41+
42+
- Symbols declare with a`constant` flag are now not marked as 'inferred'
43+
44+
- Some BoxedSymbols properties now more consistently return 'undefined', instead
45+
of a 'boolean' (i.e. because the symbol is non-bound)
46+
47+
- Some`expr.root()` computations
48+
49+
- Canonical-forms
50+
- Fixes the`Number` form
51+
- Forms (at least, 'Number', 'Power') do not mistakenly_fully_ canonicalize
52+
operands
53+
- This (partial canonicalization) now substitutes symbols (constants) with a
54+
`holdUntil` value of_never_ during/prior-to canonicalization (i.e. just
55+
like for full canonicalization)
56+
1657
##0.29.1_2025-03-31_
1758

1859
-**#231** During evaluation, some numbers, for example`10e-15` were

‎src/compute-engine/boxed-expression/arithmetic-power.ts‎

Lines changed: 37 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -60,26 +60,32 @@ export function canonicalPower(
6060
constunchanged=()=>
6161
ce._fn('Power',[a,b],{canonical:fullyCanonical});
6262

63-
// Exclude cases - which may otherwise be valid - of the exponent either: being a function (e.g.
64-
// '0 + 0'), a non-constant value (symbols with 'assumed' values, impure functions), or a
65-
// non-numeric type (no concern for this).
63+
if(a.operator==='Power'){
64+
const[base,aPow]=a.ops!;
65+
returnce._fn('Power',[
66+
base,
67+
ce.box(['Multiply',aPow,b],{
68+
canonical:fullyCanonical||'Power',
69+
}),
70+
]);
71+
}
72+
73+
// Onwards, the focus on operations is where is a *numeric* exponent.
74+
// Therefore, exclude cases - which may otherwise be valid - of the exponent either: being a function (e.g.
75+
// '0 + 0'), a symbol, or of a non-numeric type.
6676
//
6777
//@consider:possible exceptions where function-expressions are reasonable :Rational,Half,
6878
// Negate... (However, provided that canonicalNumber provided prior, should not be missing anything
6979
// here)
7080
if(
7181
b.isFunctionExpression||
72-
!b.isConstant||//(!notably, should exclude symbol assumptions)
82+
b.symbol!==null||
7383
!b.type.matches('number'asType)
7484
)
7585
returnunchanged();
7686

77-
// 'a' is constant and of type 'number'. Used often.
78-
// (note that this could still be a numeric expression, too...)
79-
constaIsNum=a.isConstant&&a.type.matches('number'asNumericType);
80-
8187
// Zero as base
82-
if(aIsNum&&a.is(0)){
88+
if(a.isNumberLiteral&&a.is(0)){
8389
if(b.type.matches('imaginary'asNumericType)||b.isNaN)returnce.NaN;
8490

8591
if(b.is(0))returnce.NaN;
@@ -98,9 +104,19 @@ export function canonicalPower(
98104
returnunchanged();// No other canonicalization cases with this base
99105
}
100106

107+
// 'a'/base has an associated number value (excludes numeric functions)
108+
// (this should at this stage include library-defined symbols such as 'Pi')
109+
//@note: include 'Negate', because this could be wrapped around a number-valued symbol, such as
110+
//'Pi'...
111+
//^there could exist other exceptions: perhaps consider a util. such as 'maybeNumber'?
112+
constaIsNum=
113+
a.type.matches('number'asNumericType)&&
114+
(a.isFunctionExpression===false||a.operator==='Negate');
115+
101116
// Zero as exponent
102117
if(b.is(0)){
103-
if(aIsNum)returna.isFinite ?ce.One :ce.NaN;
118+
// If 'isFinite' is a boolean, then 'a' has a value.
119+
if(aIsNum&&a.isFinite!==undefined)returna.isFinite ?ce.One :ce.NaN;
104120
returnunchanged();
105121
}
106122

@@ -110,7 +126,8 @@ export function canonicalPower(
110126
if(aIsNum&&a.is(1))returnb.isFinite ?ce.One :ce.NaN;
111127

112128
// One as exponent
113-
if(b.is(1))returna;
129+
// (Permit the base to be a FN-expr. here, too...)
130+
if(b.is(1)&&a.type.matches('number'asNumericType))returna;
114131

115132
// -1 exponent
116133
if(b.is(-1)){
@@ -177,8 +194,8 @@ export function canonicalPower(
177194
returnce.NaN;
178195
}
179196

180-
//'Infinity^Complex'
181-
if(a.isInfinity){
197+
//'AnyInfinity^{~oo}' (i.e. ComplexInfinity)
198+
if(a.isNumberLiteral&&a.isInfinity){
182199
// If the exponent is pure imaginary, the result is NaN
183200
//(↓fix?:ensure both these cases narrow down to 'b' being a num./symbol literal)
184201
if(b.type.matches('imaginary'asNumericType))returnce.NaN;
@@ -273,93 +290,14 @@ export function pow(
273290

274291
if(typeofexp!=='number')exp=exp.canonical;
275292

276-
conste=typeofexp==='number' ?exp :exp.im===0 ?exp.re :undefined;
277-
278-
// x^0 = 1
279-
if(e===0)returnce.One;
280-
// x^1 = x
281-
if(e===1)returnx;
282-
283-
if(e===-1){
284-
// (-∞)^-1 = 0
285-
if(x.isInfinity&&x.isNegative)returnce.Zero;
286-
287-
// (-1)^-1 = -1
288-
if(x.is(-1))returnce.NegativeOne;
289-
290-
// 0^-1 = ~∞
291-
// This is not strictly true, as 0^-1 may be undefined, but is convenient in some contexts where the base is assumed to be positive.
292-
if(x.is(0))returnce.ComplexInfinity;
293+
// 'canonicalPower' deals with a set of basic operations.
294+
// If the result is not 'Power', can assume an op. has occurred
295+
// In some cases, an op. may apply, but a 'Power' expr. is still the result ('(a^b)^c -> a^(b*c)'
296+
// for instance). For these cases, proceed.
297+
constcanonicalResult=canonicalPower(x,ce.box(exp));
298+
if(canonicalResult.operator!=='Power')returncanonicalResult;
293299

294-
// 1^-1 = 1
295-
if(x.is(1))returnce.One;
296-
297-
// ∞^-1 = 0
298-
if(x.isInfinity&&x.isPositive)returnce.Zero;
299-
300-
returnx.inv();
301-
}
302-
303-
if(e===Number.POSITIVE_INFINITY){
304-
// 0^∞ = 0
305-
// Because for all complex numbers z near 0, z^∞ -> 0.
306-
if(x.is(0))returnce.Zero;
307-
308-
// 1^∞ = NaN
309-
// Because there are various cases where lim(x(t),t)=1, lim(y(t),t)=∞ (or -∞), but lim( x(t)^y(t), t) != 1.
310-
if(x.is(1))returnce.NaN;
311-
312-
// (-1)^∞ = NaN
313-
// Because of oscillations in the limit.
314-
if(x.is(-1))returnce.NaN;
315-
316-
if(x.isInfinity){
317-
if(x.isPositive)returnce.PositiveInfinity;
318-
if(x.isNegative)returnce.NaN;
319-
}
320-
}
321-
322-
if(e===Number.NEGATIVE_INFINITY){
323-
if(x.is(-1))returnce.NaN;
324-
if(x.isInfinity){
325-
if(x.isPositive)returnce.Zero;
326-
if(x.isNegative)returnce.NegativeInfinity;
327-
}
328-
}
329-
330-
if(typeofexp!=='number'){
331-
if(exp.isInfinity&&!exp.isPositive&&!exp.isNegative){
332-
// b^~∞ = NaN
333-
// Because b^z has no limit as z -> ~∞.
334-
returnce.NaN;
335-
}
336-
337-
if(x.isInfinity){
338-
// If the exponent is pure imaginary, the result is NaN
339-
if(exp.type.matches('imaginary'))returnce.NaN;
340-
if(exp.type.matches('complex')&&!isNaN(exp.re)){
341-
if(exp.re>0)returnce.ComplexInfinity;
342-
if(exp.re<0)returnce.Zero;
343-
}
344-
}
345-
}
346-
347-
// // if (this.isNegative) {
348-
// // if (exp % 2 === 1) return this.neg().pow(exp).neg();
349-
// // if (exp % 2 === 0) return this.neg().pow(exp);
350-
// //}
351-
352-
if(e===Number.POSITIVE_INFINITY){
353-
if(x.isGreater(1))returnce.PositiveInfinity;
354-
if(x.isPositive&&x.isLess(1))returnce.Zero;
355-
}
356-
if(e===Number.NEGATIVE_INFINITY){
357-
if(x.isGreater(1))returnce.Zero;
358-
if(x.isPositive&&x.isLess(1))returnce.PositiveInfinity;
359-
}
360-
361-
if(typeofexp!=='number'&&exp.operator==='Negate')
362-
returnpow(x,exp.op1,{ numericApproximation}).inv();
300+
conste=typeofexp==='number' ?exp :exp.im===0 ?exp.re :undefined;
363301

364302
//@todo: this should be canonicalized to a number, so it should never happen here
365303
if(x.symbol==='ComplexInfinity')returnce.NaN;

‎src/compute-engine/boxed-expression/box.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,13 @@ export function box(
382382
}
383383

384384
if(!isValidIdentifier(expr))returnce.error('invalid-identifier',expr);
385-
returnce.symbol(expr,{ canonical});
385+
// Let 'partial' canonicalization fetch the canonical variant of symbols: in order that at
386+
// minimum, they may be substituted with associated definition values (when its def. 'holdUntil'
387+
// is 'never')
388+
//@note: alternatively, this could be signalled by a 'Symbol' CanonicalForm: but this way is
389+
// more predicatable, & ensures substitution as per above
390+
constcanonicalSymbol=canonical||options.canonical!==false;
391+
returnce.symbol(expr,{canonical:canonicalSymbol});
386392
}
387393

388394
//

‎src/compute-engine/boxed-expression/boxed-function.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,7 @@ export class BoxedFunction extends _BoxedExpression {
679679
}
680680

681681
// root(sqrt(a), c) -> root(a, 2*c)
682-
if(this.operator==='Sqrt'||this.operator==='Root'){
682+
if(this.operator==='Sqrt'){
683683
if(e!==undefined)returnthis.op1.root(e*2);
684684
if(typeofexp!=='number')returnthis.op1.root(exp.mul(2));
685685
}

‎src/compute-engine/boxed-expression/boxed-symbol-definition.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ export class _BoxedSymbolDefinition implements BoxedSymbolDefinition {
110110
returnthis.constant;
111111
}
112112

113-
/** The symbol was previously inferred, but now it has a declaration. Update the def accordingly (we can't replace defs, as other expressions may be referencing them) */
114113
update(def:SymbolDefinition):void{
115114
if(def.wikidata)this.wikidata=def.wikidata;
116115
if(def.description)this.description=def.description;

‎src/compute-engine/boxed-expression/canonical.ts‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ export function canonicalForm(
2222

2323
if(typeofforms==='string')forms=[forms];
2424

25+
// Like for full canonicalization, request the canonical form of symbols.
26+
// Automatically, this involves the substitution of the symbol with its value, if it is a
27+
// constant-flagged symbol, with a 'holdUntil' attribute of 'never'
28+
// (@note: the reasoning for carrying this out here is because in some sense, firstly,
29+
// 'CanonicalForm' can be regarded as producing a 'canonical' expression, (albeit a 'custom' one;
30+
// notably also with the resulting expr. having an 'isCanonical' value of 'true'). Secondly &
31+
// following from this, symbol canonicalization (and substitution where appropriate) facilitates
32+
// several simplifications which would otherwise not be made: for example 'x^y' where 'y=0', for
33+
// canonicalPower.
34+
expr=symbolForm(expr);
35+
2536
// Apply each form in turn
2637
for(constformofforms){
2738
switch(form){
@@ -124,6 +135,14 @@ function invisibleOperatorForm(expr: BoxedExpression) {
124135
*
125136
* <!--
126137
* (!note: the procedure outlined is a contracted one of that affixed to function 'box')
138+
*
139+
*@wip ?
140+
* -As discussed in compute-engine/pull/238, other possible transformations here:
141+
* -Promotion of 'complex-numbers': ['Multiply', 2, 'ImaginaryUnit'] -> 2i)
142+
* -^or even for 'InvisibleOperator',too...
143+
* -Creation of complex: e.g. from `a + ib` or `ai + b` ('Add' instances)
144+
*
145+
* ^I.e., a cross-selection of ops. from 'Add','Multiply', 'InvisibleOpeator'...
127146
* -->
128147
*
129148
*@param expr
@@ -254,6 +273,21 @@ function powerForm(expr: BoxedExpression) {
254273
returnexpr.engine._fn(expr.operator,ops,{canonical:false});
255274
}
256275

276+
/**
277+
* Replace symbols within expr. with canonical variants, *recursively*.
278+
*
279+
*@param expr
280+
*@returns
281+
*/
282+
functionsymbolForm(expr:BoxedExpression):BoxedExpression{
283+
if(expr.symbol!==null)returnexpr.canonical;
284+
if(!expr.isFunctionExpression)returnexpr;
285+
286+
returnexpr.engine._fn(expr.operator,expr.ops!.map(symbolForm),{
287+
canonical:false,
288+
});
289+
}
290+
257291
functiondivideForm(expr:BoxedExpression){
258292
// If this is a divide, canonicalize it
259293
if(expr.operator==='Divide')

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp