Repository navigation
Expand file tree
/
Copy pathIntegration.Definition.cs
More file actions
1489 lines (1453 loc) · 121 KB
/
Copy pathIntegration.Definition.cs
File metadata and controls
1489 lines (1453 loc) · 121 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//
// Copyright (c) 2019-2022 Angouri.
// AngouriMath is licensed under MIT.
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
// Website: https://am.angouri.org.
//
using AngouriMath.Convenience;
using AngouriMath.Core.Transformations;
using AngouriMath.Extensions;
using AngouriMath.Functions.Algebra;
using PeterO.Numbers;
namespace AngouriMath
{
partial record Entity
{
/// <summary>
/// Integrates indefinitely the given expression over the `x` variable, if can.
/// May return an unresolved <see cref="Integralf"/> node.
/// </summary>
/// <param name="x">Over which variable to integrate</param>
/// <returns>
/// An integrated expression. It might remain the same or be transformed into nodes with no integrals.
/// </returns>
/// <remarks>
/// The antiderivative is inner-simplified before it is returned, as the definite
/// overload below has always done. Without it the answer kept branches whose guard
/// was a comparison of two literals -- <c>1/sqrt(x^2 - 1)</c> came back as a
/// three-branch piecewise on <c>1 * 1 ^ 2</c>, two of them dead -- and a caller that
/// inspects the answer rather than evaluating it saw all three. `Piecewise` already
/// drops a branch whose guard is decidably false; nothing was asking it to.
/// https://github.com/asc-community/AngouriMath/issues/772
/// </remarks>
public Entity Integrate(Variable x) =>
Transformation.Integration(x).Apply(this).Output is { } antiderivative
? antiderivative + (antiderivative.VarsAndConsts.Contains("C") ? Variable.CreateUnique(antiderivative, "C") : "C")
: new Integralf(this, x, null);
/// <summary>
/// Integrates definitely the given expression over the `x` variable, if can.
/// May return an unresolved <see cref="Integralf"/> node.
/// </summary>
/// <param name="x">Over which variable to integrate</param>
/// <param name="from">The lower bound for integrating</param>
/// <param name="to">The upper bound for integrating</param>
/// <returns>
/// An integrated expression. It might remain the same or be transformed into nodes with no integrals.
/// </returns>
public Entity Integrate(Variable x, Entity from, Entity to) =>
// An integrand that can jump -- a piecewise whose conditions mention x, a floor or a
// ceiling of x -- never goes through an antiderivative between two bounds: the rules
// integrate it with the case or the step taken for a constant, which is right
// between two jumps and wrong across one. (x - floor(x))^2 from 0 to 4 came back as
// 0 that way, where it is 4/3, and the tent map over [0, 1] as 1, where it is 1/2.
// It is split at the jumps instead, and where that cannot be done it is left as
// written. See BreakpointIntegration.
Functions.Algebra.BreakpointIntegration.HasABreak(this, x)
? Functions.Algebra.BreakpointIntegration.Split(this, x, from, to) ?? new Integralf(this, x, (from, to))
: Transformation.Integration(x).Apply(this).Output is { } antiderivative
? AcrossTheRange(antiderivative, x, from, to) ?? new Integralf(this, x, (from, to))
: new Integralf(this, x, (from, to));
/// <summary>
/// <c>F(b) - F(a)</c> through <paramref name="antiderivative"/>, taken piece by piece
/// where it breaks between the bounds, or <see langword="null"/> where a piece's limit at
/// a break is not decided.
/// </summary>
/// <remarks>
/// At each break the piece below takes its limit from the left and the piece above from
/// the right, so a jump in the antiderivative is not counted and a pole is: <c>1/x^2</c>
/// over <c>[-1, 1]</c> is <c>+oo</c>, and <c>1/x</c> over the same range, whose pieces are
/// <c>-oo</c> and <c>+oo</c>, has no value. Where the bounds are not numbers, or the breaks
/// cannot all be listed, the antiderivative is taken at the bounds alone, as it was.
/// https://github.com/asc-community/AngouriMath/issues/1508
/// </remarks>
private static Entity? AcrossTheRange(Entity antiderivative, Variable x, Entity from, Entity to)
{
if (from.Evaled is not Number.Real { IsNaN: false } lower || to.Evaled is not Number.Real { IsNaN: false } upper
|| lower == upper
|| (lower < upper
? AntiderivativeBreaks.Inside(antiderivative, x, lower, upper)
: AntiderivativeBreaks.Inside(antiderivative, x, upper, lower)) is not { Count: > 0 } breaks)
return AtTheBound(antiderivative, x, to, from) - AtTheBound(antiderivative, x, from, to);
if (lower > upper)
breaks.Reverse();
var total = -AtTheBound(antiderivative, x, from, to);
foreach (var (at, _) in breaks)
{
// Approached from the side the range comes from, then left on the other.
var (arriving, leaving) = lower < upper ? (ApproachFrom.Left, ApproachFrom.Right) : (ApproachFrom.Right, ApproachFrom.Left);
var before = antiderivative.Limit(x, at, arriving);
var after = antiderivative.Limit(x, at, leaving);
if (before.Nodes.Any(node => node is Limitf) || after.Nodes.Any(node => node is Limitf))
return null;
total += before - after;
}
return total + AtTheBound(antiderivative, x, to, from);
}
/// <summary>
/// The antiderivative's value at one bound of a definite integral: its value there, and
/// where that is undefined, its limit as the bound is approached from inside the range,
/// which is what an improper integral is.
/// </summary>
/// <remarks>
/// <c>-(x + 1) e^(-x)</c> at <c>+oo</c> is <c>-oo * 0</c>, which is <c>NaN</c>, so the integral
/// of <c>x e^(-x)</c> from 0 to <c>+oo</c> was <c>NaN</c> where it is 1; and <c>x ln(x) - x</c>
/// at 0 is <c>0 * -oo</c>, so the integral of <c>ln(x)</c> from 0 to 1 was <c>NaN</c> where it
/// is -1. The limit is taken only where the value is undefined, so a bound the substitution
/// answered is answered as before. Its side is known at an infinity, and at a finite bound
/// from the other bound where both have numeric values. Elsewhere, and where the limit is not
/// decided, the value stays undefined, as it is for <c>sin(x)</c> from 0 to <c>+oo</c>,
/// whose antiderivative has no limit there.
/// </remarks>
private static Entity AtTheBound(Entity antiderivative, Variable x, Entity bound, Entity otherBound)
{
var value = antiderivative.Substitute(x, bound);
if (!value.InnerSimplified.IsNaN)
return value;
// The bounds' values, so that a bound written as pi or 2 pi is placed as well as 3.
ApproachFrom? side = (bound.Evaled, otherBound.Evaled) switch
{
(Number.Real { EDecimal: var at }, _) when at.IsPositiveInfinity() => ApproachFrom.Left,
(Number.Real { EDecimal: var at }, _) when at.IsNegativeInfinity() => ApproachFrom.Right,
(Number.Real at, Number.Real other) when at > other => ApproachFrom.Left,
(Number.Real at, Number.Real other) when at < other => ApproachFrom.Right,
_ => null
};
if (side is not { } inside)
return value;
var limit = antiderivative.Limit(x, bound, inside);
return limit.IsNaN || limit.Nodes.Any(node => node is Limitf) ? value : limit;
}
/// <summary>
/// Integrates numerically over <paramref name="x"/> between two bounds, without
/// looking for an antiderivative. Only works for one-variable functions.
/// </summary>
/// <param name="x">The variable to integrate over.</param>
/// <param name="from">The lower bound.</param>
/// <param name="to">The upper bound.</param>
/// <param name="stepCount">How many steps to take; more is more accurate and slower.</param>
/// <returns>The value of the integral.</returns>
/// <remarks>
/// This is the instance method the obsolete <c>MathS.Compute.DefiniteIntegral</c>
/// said to use and which did not exist — every other member of that group had a
/// counterpart on <see cref="Entity"/> and this one did not, so removing the group
/// would have taken numeric definite integration with it.
/// </remarks>
public Number.Complex DefiniteIntegral(Variable x, Number.Complex from, Number.Complex to, int stepCount = 100)
=> Integration.IntegrateNumerically(this, x, from, to, stepCount);
/// <summary>
/// Integrates numerically over <paramref name="x"/> between two real bounds.
/// </summary>
/// <param name="x">The variable to integrate over.</param>
/// <param name="from">The lower bound.</param>
/// <param name="to">The upper bound.</param>
/// <param name="stepCount">How many steps to take; more is more accurate and slower.</param>
/// <returns>The value of the integral.</returns>
public Number.Complex DefiniteIntegral(Variable x, EDecimal from, EDecimal to, int stepCount = 100)
=> Integration.IntegrateNumerically(this, x,
Number.Complex.Create(from, 0), Number.Complex.Create(to, 0), stepCount);
}
}
namespace AngouriMath.Functions.Algebra
{
internal static partial class Integration
{
/// <summary>
/// Brings the two powers of one base in a product together, so that an integrand
/// which is a power in disguise is recognised as one.
/// </summary>
/// <remarks>
/// <para>
/// This has to happen on every recursive call rather than once on the way in. The
/// shape is produced by the integrator itself -- distributing a product over a sum
/// turns <c>sin(x)^4 * (5 - 6*sin(x)^2)</c> into a term <c>sin(x)^4 * (-6) * sin(x)^2</c>
/// -- so normalising only the caller's input would miss every case the integrator
/// generates for itself. https://github.com/asc-community/AngouriMath/issues/781
/// </para>
/// <para>
/// Deliberately not followed by <c>InnerSimplified</c>: that rewrites <c>x^(-2)</c>
/// back into <c>1/x^2</c>, and <see cref="IndefiniteIntegralSolver.SolveAsPolynomialTerm"/>
/// rewrites a <c>1/x^n</c> it is handed into <c>Pow(x, -n)</c>, so the pair recurse
/// into each other until the stack runs out. The gathering folds the exponents it
/// builds and leaves the rest of the tree alone.
/// </para>
/// </remarks>
private static Entity Normalized(Entity expr, Entity.Variable x) =>
WithItsPiecewiseSumsCombined(expr.Replace(Patterns.GatherPowersOfOneBase).Replace(node => IndefiniteIntegralSolver.GatherSignsAndModuli(node, x))
// A whole power of a fractional power is the power of the product of the
// exponents, exact for every base: `((c^2 x^2 + 1)^(-1/2))^2` is
// `(c^2 x^2 + 1)^(-1)`, and left as a square of a root it was handed to Euler's
// substitution as a radical beside `(c^2 x^2 + 1)^(-1)`, which answered it
// wrongly -- the second step of parts on `acsch(c x)^2/x^4` was that. The
// gathering above joins two powers of one base beside each other and not one
// inside the other. A fractional inner power only: `((1 + x^2)^(-1))^4` is
// spelled so by the rules that read it, and flattened it sent Timofeev's
// `x^3 arctan(x)^2/(1 + x^2)^3` from 0.1 s to 8.
.Replace(node => node is Entity.Powf(Entity.Powf(var @base, var inner), Entity.Number.Integer whole) && inner is Entity.Number.Rational && inner is not Entity.Number.Integer && @base.ContainsNode(x)
? MathS.Pow(@base, (inner * whole).InnerSimplified)
: node)
// A zeroth power of anything with the variable in it is one but where its base is
// zero, a point the antiderivative passes through as the generic case does every
// removable one: `x/(x (a + b x^2 + c x^4))` arrives as `x^0/(a + b x^2 + c x^4)`,
// and no rule reads `x^0` as a polynomial, so it was declined where
// `1/(a + b x^2 + c x^4)` is answered.
.Replace(node => node is Entity.Powf(var zeroth, Entity.Number.Integer { IsZero: true }) && zeroth.ContainsNode(x)
? Entity.Number.Integer.One
: node));
/// <summary>
/// <paramref name="expr"/> with every sum of piecewises in it combined case by case, as
/// the evaluator did before an answer's sum of piecewises was left a sum: an integrand
/// is searched as the one piecewise, and taken apart term by term it was searched once a
/// term. The sums are written anew, so that no simplification of them is remembered from
/// outside the scope.
/// https://github.com/asc-community/AngouriMath/issues/1874
/// </summary>
private static Entity WithItsPiecewiseSumsCombined(Entity expr)
{
if (!expr.Nodes.Any(node => node is Entity.Sumf or Entity.Minusf && node.DirectChildren.Any(child => child is Entity.Piecewise)))
return expr;
var combining = Entity.CombinesEveryPiecewiseSum;
Entity.CombinesEveryPiecewiseSum = true;
try
{
return expr.Replace(node => node switch
{
Entity.Sumf(var augend, var addend) => new Entity.Sumf(augend, addend),
Entity.Minusf(var minuend, var subtrahend) => new Entity.Minusf(minuend, subtrahend),
_ => node
}).InnerSimplified;
}
finally
{
Entity.CombinesEveryPiecewiseSum = combining;
}
}
/// <summary>
/// The integrals already answered under the settings in force, so that the same question
/// is not worked out again.
/// </summary>
/// <remarks>
/// <para>
/// <b>Why an integral is asked for twice at all.</b> Two layers of it. Within one call the
/// solvers overlap — substitution, partial fractions, splitting a sum and integration by
/// parts each decompose the integrand differently and produce pieces that coincide — and
/// across calls <c>Simplify</c> asks about one integral through every
/// rewritten candidate it generates. Traced on <c>sin(x)/(x^2 + 1)^2</c>, which has no
/// elementary antiderivative and so runs the search to exhaustion, that is <b>562
/// top-level calls for 3 distinct integrands</b>, one of them asked 500 times; on
/// <c>e^x/(x^2 + 1)^2</c> a single call enters the integrator <b>5330 times for 23</b>.
/// </para>
/// <para>
/// <b>The answers are held across calls and not only within one</b>, which is where the
/// repetition is: discarding them at the end of each top-level call leaves the 562 to be
/// paid in full. Measured on those three integrands, keeping them takes a
/// <c>Simplify</c> from 115, 86 and 51 seconds to about 1.2, 1.5 and 0.2, and a single
/// cold <c>Integrate</c> of <c>sqrt(tan(x))</c> from 155 ms to 79.
/// https://github.com/asc-community/AngouriMath/issues/1156
/// </para>
/// <para>
/// <b>Why it is sound to hold them.</b> An answer depends on the ambient settings —
/// <see cref="MathS.Settings.Codomain"/> decides whether the logarithms carry an
/// <c>abs</c>, <c>MaxExpansionTermCount</c> bounds the by-parts recursion — and a setting
/// is scoped to a flow rather than to a thread, so "the settings have not changed" is not
/// something a per-thread cache can assume. <see cref="SettingsState"/> answers it
/// exactly, by comparing what every setting reads as rather than by counting changes —
/// which matters, because the library opens and closes thousands of balanced scopes while
/// simplifying and a change count is therefore never still.
/// </para>
/// <para>
/// <b>A decline is held with the scope it was made in.</b> Rules answer only the question
/// asked (<see cref="AnsweringTheQuestionAsked"/>), or it and one or two levels below it
/// (<see cref="AnsweringTheQuestionAskedOrOneBelow"/>,
/// <see cref="AnsweringTheQuestionAskedOrTwoBelow"/>), and decline the same integrand
/// deeper, so a <see langword="null"/> computed at one depth says nothing about a
/// shallower one. Held without the scope it was served there: <c>sec(x)^3</c>, tried and
/// declined inside another rule's search, then asked for directly and declined from the
/// cache in two milliseconds. And held with the top told only from the rest, a decline at
/// depth three was served at depth two: the search for <c>1/(x^(3/2) (a + c x^4))</c>
/// declines the root's substitution <c>2/(w^2 (a + c w^8))</c> at depth three and asks it
/// again at depth two, and the integral was declined. The key carries the depth as far as
/// a scope reaches, <see cref="ScopeDepth"/>, and a lookup takes a decline only from its
/// own depth and an answer from any, since an antiderivative that was found is right
/// wherever it is asked for.
/// </para>
/// </remarks>
[System.ThreadStatic] private static Dictionary<(Entity, Entity.Variable, bool, int), Entity?>? answered;
/// <summary>The settings <see cref="answered"/> was filled under.</summary>
[System.ThreadStatic] private static object?[]? answeredUnder;
/// <summary>
/// How many answers to hold before starting over. Comfortably above what one call needs
/// — the worst measured is 23 distinct integrands within a call and 3 across the
/// candidates of one <c>Simplify</c> — and low enough that a process integrating
/// different things all day does not accumulate them.
/// </summary>
private const int MostAnswersKept = 4096;
/// <summary>
/// How deep the recursive descent may go before it declines to go further.
/// </summary>
/// <remarks>
/// <para>
/// Without this the descent has **no bound at all**, and a stack overflow is not an
/// exception a caller can handle: it takes the process down, so anything the process had
/// not finished is lost. Found by <c>work/intbench</c> against Rubi's independent test
/// suites, where the run died with <c>SIGABRT</c> partway through; the trace was thousands
/// of frames alternating <see cref="ComputeIndefiniteIntegral"/> with
/// <see cref="IndefiniteIntegralSolver.SolveBySubstitution"/>.
/// https://github.com/asc-community/AngouriMath/issues/1232
/// </para>
/// <para>
/// <b>Why the memo above does not already stop it, which is the part worth stating.</b>
/// <c>SolveBySubstitution</c> names its new variable with
/// <c>Variable.CreateUnique</c>, so every level integrates with respect to a *fresh*
/// variable. The key <c>(expr, x, integrateByParts)</c> therefore differs at every level
/// even when the level is the same problem renamed, and <see cref="answered"/> can never
/// fire on a cycle. Neither could a set of shapes already visited, for the same reason —
/// the shapes are alpha-equivalent rather than equal. A depth bound does not care what
/// the levels are called.
/// </para>
/// <para>
/// <b>The number, which is measured rather than picked.</b> Instrumenting the descent over
/// twenty-three integrands chosen from the hard end of the corpus — the ones that take
/// substitutions, by-parts chains and partial fractions — the deepest any *answered*
/// integral reaches is <b>13</b>, for <c>x^5*cosh(x)</c>. Next are <c>x^2*sqrt(5-x^2)</c>
/// at 8 and <c>e^(x^(1/3))</c> at 7; everything else sits at 4 or less. 32 is therefore
/// about two and a half times the deepest real descent seen.
/// </para>
/// <para>
/// <b>Why not far more, since a bound only has to stop the overflow.</b> Because this
/// descent branches: the remark on <see cref="answered"/> above records a single call
/// entering the integrator 5,330 times for 23 distinct integrands. Depth that is never
/// legitimately used is still searched before it is abandoned, so a bound of 64 stopped
/// the crash and left the run crawling. A bound has to be tight enough to be a bound.
/// </para>
/// <para>
/// Declining is a legitimate answer here and a wrong one is not: an unevaluated
/// <c>integral(...)</c> says "I could not settle this", which is true, where an aborted
/// process says nothing at all.
/// </para>
/// </remarks>
private const int DeepestDescent = 32;
/// <summary>How deep the current descent is. Per thread, like <see cref="answered"/>.</summary>
[System.ThreadStatic] private static int descentDepth;
/// <summary>
/// Whether the integrand now being solved is the one the caller asked for, rather than
/// one a rule produced on the way to answering something else.
/// </summary>
/// <remarks>
/// <para>
/// For a rule that is the right answer to a question and a distraction as an offer. A
/// rule which answers a sub-integral that used to come back <see langword="null"/> does
/// not only add its own work: it lets the search that asked stop failing and carry on,
/// which is where the cost turns up — on integrands the rule never fires on.
/// </para>
/// <para>
/// Measured on the secant power reduction: <c>sec(x)^6*tan(x)^3</c> is declined in 637 ms
/// without it and does not return in 400 s with it, while the rule itself fires exactly
/// twice and terminates both times. The work is not the rule's; the continuation is.
/// https://github.com/asc-community/AngouriMath/issues/1265
/// </para>
/// <para>
/// One is the top: the depth is incremented before the solvers run, so a rule consulted
/// about the caller's own integrand sees exactly one.
/// </para>
/// </remarks>
internal static bool AnsweringTheQuestionAsked => descentDepth == 1;
/// <summary>
/// The question asked or one step below it: the remainder by parts leaves, the
/// integrand a substitution makes. For a rewriting rule that lands on the chain and is
/// wanted there and no deeper, where the same rewriting fed a search that did not return.
/// </summary>
internal static bool AnsweringTheQuestionAskedOrOneBelow => descentDepth <= 2;
/// <summary>
/// The question asked or one of the two steps below it: the remainder of a step of
/// parts on a power of an inverse function, and the remainder of the step on that.
/// </summary>
internal static bool AnsweringTheQuestionAskedOrTwoBelow => descentDepth <= 3;
/// <summary>
/// The depth as far as a scope tells one depth from another: 1 for the question asked, 2
/// and 3 for the levels <see cref="AnsweringTheQuestionAskedOrOneBelow"/> and
/// <see cref="AnsweringTheQuestionAskedOrTwoBelow"/> reach, and 4 for every level past
/// them, where every scoped rule declines alike.
/// </summary>
private static int ScopeDepth => System.Math.Min(descentDepth, DeepestScope);
/// <summary>The deepest <see cref="ScopeDepth"/>.</summary>
private const int DeepestScope = 4;
/// <summary>
/// <paramref name="expr"/> integrated as a question in its own right rather than as a
/// step in the search for the current one: the term of a sum asked at the top is asked at
/// the top.
/// </summary>
/// <remarks>
/// Linearity splits the integrand before anything else, and every term it produced was
/// one level down — where the five scoped rules decline. So <c>(x^3 - 1)/(2 + x^3)^(1/3)</c>
/// was declined although each of its two terms is a binomial differential the rule
/// answers when asked directly. A term of a top-level sum is strictly smaller than the
/// sum and is not a continuation of any rule's search, which is what the scope exists to
/// stop; taken at the top it costs nothing the scope was measured to save. Only from the
/// top: one level down the terms stay one level down, as before.
/// </remarks>
internal static Entity? ComputeAsAQuestionOfItsOwn(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!AnsweringTheQuestionAsked)
return ComputeIndefiniteIntegral(expr, x, integrateByParts);
// Past the entry check on purpose: a depth of zero there means a fresh question,
// and empties the set of integrals in progress and forgets that the descent was
// truncated. This is not a fresh question -- the integrand this one was made from
// is still open above it, and its place in that set is what stops the search below
// from reaching it again and following it round.
descentDepth--;
try
{
return ComputeIndefiniteIntegralGuarded(expr, x, integrateByParts);
}
finally
{
descentDepth++;
}
}
/// <summary>
/// <paramref name="expr"/> integrated as the same question as the one being answered:
/// the integrand with a constant factor taken off it. <c>b f(x)</c> and <c>f(x)</c> are
/// one question, and a rule scoped to the question asked or one below it -- a power of
/// a quotient written apart -- was never consulted about the remainder by parts left
/// from <c>asech(a + b x)/x^2</c>, because taking its <c>b</c> out, and then a <c>1</c>,
/// had carried it three levels down before any rule saw it. Past the entry check for
/// the reason <see cref="ComputeAsAQuestionOfItsOwn"/> gives.
/// </summary>
internal static Entity? ComputeAsTheSameQuestion(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (descentDepth == 0)
return ComputeIndefiniteIntegral(expr, x, integrateByParts);
descentDepth--;
try
{
return ComputeIndefiniteIntegralGuarded(expr, x, integrateByParts);
}
finally
{
descentDepth++;
}
}
/// <summary>
/// Whether anything in the current top-level call gave up on <see cref="DeepestDescent"/>
/// rather than on the mathematics. A <c>null</c> produced that way must not be cached as
/// "this cannot be integrated", because the same key may well be answerable when it is
/// not reached from so deep.
/// </summary>
[System.ThreadStatic] private static bool descentTruncated;
/// <summary>
/// Marks what is being worked out as declined for the scope it was asked in rather than
/// for its mathematics, as running out of descent is, so that the decline is not
/// remembered: the same question asked where that scope does not bind may be answered.
/// </summary>
internal static void DeclinedForItsScope() => descentTruncated = true;
/// <summary>
/// The integrals this thread is part-way through, so that asking for one again while it is
/// still being worked out is recognised as a cycle rather than followed round again.
/// </summary>
/// <remarks>
/// <para>
/// Keyed on the integrand with its variable renamed to <see cref="CycleVariable"/>, which
/// is the whole point. <see cref="IndefiniteIntegralSolver.SolveBySubstitution"/> names
/// each new variable with <c>Variable.CreateUnique</c>, so a level and the level it came
/// from are alpha-equivalent rather than equal, and a set keyed on the integrand as
/// written would never see the same entry twice. Neither does <see cref="answered"/>, and
/// for the same reason — which is why the memo could not stop this.
/// </para>
/// <para>
/// This is what makes <see cref="DeepestDescent"/> a backstop instead of the mechanism.
/// The bound alone stops the process dying, and it is not enough on its own: the descent
/// branches, so 32 levels of a cycle is still an enormous search, and one Rubi integrand
/// took over ten minutes under the bound where <c>v2.4.0</c> passed the whole section it
/// is in within a minute. Declining at the first repeat costs nothing and answers at once.
/// </para>
/// </remarks>
[System.ThreadStatic] private static HashSet<(Entity, bool)>? inProgress;
/// <summary>The name every in-progress integrand's variable is rewritten to.</summary>
[ConstantField] private static readonly Entity.Variable CycleVariable = Entity.Variable.CreateVariableOrConstant("__integration_cycle_var");
/// <summary>Does not add the constant of integration because this is called recursively.</summary>
internal static Entity? ComputeIndefiniteIntegral(Entity expr, Entity.Variable x, bool integrateByParts = true)
{
if (descentDepth >= DeepestDescent)
{
descentTruncated = true;
return null;
}
if (descentDepth != 0)
return ComputeIndefiniteIntegralGuarded(expr, x, integrateByParts);
descentTruncated = false;
inProgress?.Clear();
ASumOverRootsWouldAnswer = false;
var answer = ComputeIndefiniteIntegralGuarded(expr, x, integrateByParts);
if (answer is not null || !ASumOverRootsWouldAnswer || SumsOverRootsAllowed)
return answer;
// Once more with sums over roots, and with a memo of its own, since the first pass
// remembered every one of those rational functions as declined. Last, and only where
// nothing else answered: asked with the rest, a sum over roots answered each of
// Jeffrey's three terms, where the whole is one arctangent the rational integrator
// writes when it is asked the whole.
// https://github.com/asc-community/AngouriMath/issues/1285
var (memo, stamp) = (answered, answeredUnder);
SumsOverRootsAllowed = true;
answered = null;
descentTruncated = false;
inProgress?.Clear();
try
{
answer = ComputeIndefiniteIntegralGuarded(expr, x, integrateByParts);
}
finally
{
SumsOverRootsAllowed = false;
(answered, answeredUnder) = (memo, stamp);
}
// In place of the decline the first pass remembered, which a question asked again
// would otherwise be answered with, without the rule ever being reached to say that a
// second pass could answer it.
if (answer is not null && memo is not null && stamp is not null && SettingsState.StillHolds(stamp))
memo[(Normalized(expr, x), x, integrateByParts, 1)] = answer;
return answer;
}
/// <summary>
/// Whether the rational integrator may answer with a sum over the roots of a factor of the
/// denominator: only in the second pass of a question nothing else answered.
/// </summary>
[System.ThreadStatic] internal static bool SumsOverRootsAllowed;
/// <summary>
/// Set where the rational integrator declined only because the residues lie in a field of
/// degree above two, which a sum over roots would have written.
/// </summary>
[System.ThreadStatic] internal static bool ASumOverRootsWouldAnswer;
/// <summary>
/// <see cref="ComputeIndefiniteIntegral"/> past its entry check: the cycle guard, the
/// depth, and the memo.
/// </summary>
private static Entity? ComputeIndefiniteIntegralGuarded(Entity expr, Entity.Variable x, bool integrateByParts)
{
// Renamed so that the same integral under two different variable names is one entry.
var cycleKey = (expr.Substitute(x, CycleVariable), integrateByParts);
var open = inProgress ??= new HashSet<(Entity, bool)>();
if (!open.Add(cycleKey))
{
// Already on the stack: following it again cannot reach an answer this call has
// not already tried, so declining is both correct and immediate.
descentTruncated = true;
return null;
}
descentDepth++;
try
{
return ComputeIndefiniteIntegralBounded(expr, x, integrateByParts);
}
finally
{
open.Remove(cycleKey);
descentDepth--;
}
}
private static Entity? ComputeIndefiniteIntegralBounded(Entity expr, Entity.Variable x, bool integrateByParts)
{
expr = Normalized(expr, x);
if (answered is null || answeredUnder is null || !SettingsState.StillHolds(answeredUnder))
{
// A fresh dictionary rather than Clear(), because this flow may have inherited
// the reference from the one that started it, and emptying it in place would
// empty that one's too.
answered = new();
answeredUnder = SettingsState.Snapshot();
}
// Held as locals over the recursive call, which reaches this method again and may
// replace both fields on the way. Writing the answer into whatever the fields hold
// afterwards would put it in a dictionary stamped with different settings.
var into = answered;
var stamp = answeredUnder;
var depth = ScopeDepth;
var key = (expr, x, integrateByParts, depth);
if (into.TryGetValue(key, out var already))
return already;
for (var other = 1; other <= DeepestScope; other++)
if (other != depth && into.TryGetValue((expr, x, integrateByParts, other), out var elsewhere) && elsewhere is not null)
return elsewhere;
// Whether the descent ran out *inside this computation*, which is the only truncation
// that says anything about this key. The flag is one per thread, so it is cleared
// for the duration and what it held before is put back, joined with what happened
// here: a decline reached after some unrelated branch above was cut short is still a
// decline, and it used to go uncached along with everything else asked after that
// branch -- a term of a top-level sum re-did its declined sub-integrals for that.
var truncatedBefore = descentTruncated;
descentTruncated = false;
Entity? computed;
bool truncatedHere;
try
{
computed = ComputeIndefiniteIntegralUncached(expr, x, integrateByParts);
}
finally
{
truncatedHere = descentTruncated;
descentTruncated = truncatedBefore || truncatedHere;
}
// Only kept if those settings still hold. Working the answer out runs simplification,
// which opens scopes of its own; one still open means this answer was computed under
// settings the stamp does not describe.
// A null that came of running out of descent, rather than of the mathematics, is not
// an answer about this integrand and must not be remembered as one — the same key
// reached from less deep may well be answerable. A non-null answer is kept whatever
// happened elsewhere, since an antiderivative that was found is correct regardless of
// how deep the search that found it went.
if (SettingsState.StillHolds(stamp) && (computed is not null || !truncatedHere))
{
// Emptied rather than grown without end. Nothing here expires on its own — the
// settings holding still is the whole condition for keeping an answer — so a
// long-lived process integrating a stream of different expressions would hold
// every one of them for ever. What this is for is being asked the same question
// again, which needs the recent answers and not all of them.
if (into.Count >= MostAnswersKept)
into.Clear();
into[key] = computed;
}
return computed;
}
private static Entity? ComputeIndefiniteIntegralUncached(Entity expr, Entity.Variable x, bool integrateByParts)
{
if (!expr.ContainsNode(x)) return expr * x; // base case, handle here
if ((IntegralPatterns.TryStandardIntegrals(expr, x)) is { } answer) return answer;
// The flag has to be handed on. Every one of these recurses, and a solver that
// dropped it re-enabled integration by parts one level below the call that
// switched it off -- which is a cycle, since by parts calls back into here.
// `x * ln(x)` went round it until the stack ran out.
// A sum free of the variable that comes to a number multiplied out, as that number,
// before anything reads a coefficient or an exponent in it: `2 + 2 a - 2 (1 + a)`.
if ((answer = IndefiniteIntegralSolver.SolveByFoldingConstantSumsToTheirNumbers(expr, x, integrateByParts)) is { }) return answer;
// An arctangent over a power of d (1 +- i c x), under x = tan(u)/c, where 1 + i tan(u) is
// e^(i u)/cos(u): the rules below ran out of time on `(a + b atan(c x))^2/(d + i c d x)^2`.
if ((answer = IndefiniteIntegralSolver.SolveAnArctangentOverAPowerOfOnePlusICXUnderTheTangent(expr, x, integrateByParts)) is { }) return answer;
// An exponential of i times an inverse trigonometric function written algebraically, at the
// question asked, before the rules that spent the budget: `e^(3 i atan(a + b x))/x^2` ran out of
// time where, rewritten, it is answered in half a second. It is asked again in its place below.
// An exact derivative among them first, closed: `e^(i n arctan(a x)) x^2 (c + a^2 c x^2)^(-1 - n^2/2)`.
if ((answer = IndefiniteIntegralSolver.SolveAnExactDerivativeOfAnExponentialOfAnArctangent(expr, x)) is { }) return answer;
if (AnsweringTheQuestionAsked && (answer = IndefiniteIntegralSolver.SolveByWritingAnExponentialOfAnInverseAlgebraically(expr, x, integrateByParts)) is { }) return answer;
// The same for a symbolic multiple of the arctangent, beside powers of its quadratic.
if (AnsweringTheQuestionAsked && (answer = IndefiniteIntegralSolver.SolveAnExponentialOfASymbolicMultipleOfAnArctangentAsPowersOfTheLinears(expr, x, integrateByParts)) is { }) return answer;
// A polynomial over a symbolic quartic that splits into two quadratics in a hidden variable,
// palindromic or even after a shift: the rules below spent seconds declining both.
if ((answer = IndefiniteIntegralSolver.SolveAPolynomialOverASymbolicQuarticThatSplitsIntoTwoQuadratics(expr, x, integrateByParts)) is { }) return answer;
// A symbolic power of a sine, cosine, secant or cosecant times `alpha + beta f^2`, where it is
// the derivative of `cos sin^(m + 1)`, `sin cos^(m + 1)`, `tan sec^m` or `cot csc^m`.
if ((answer = IndefiniteIntegralSolver.SolveASymbolicPowerOfATrigonometricFunctionAsTheDerivativeOfAProduct(expr, x)) is { }) return answer;
// A power of a sine, cosine or hyperbolic one over a binomial in a power of it, over the
// binomial's roots, before the rules that spent the budget on `sinh(y)^4/(a + b sinh(y)^3)`.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOverABinomialInAPowerOfASineOverItsRoots(expr, x)) is { }) return answer;
// An exponential of a whole multiple of an inverse hyperbolic tangent, written algebraically,
// before the rules that timed out on `e^(artanh(a x)) x^2/(c - a^2 c x^2)^2`.
if ((answer = IndefiniteIntegralSolver.SolveAnExponentialOfAnInverseHyperbolicTangentAlgebraically(expr, x)) is { }) return answer;
// A power of a cosine over a power of k (1 +- sin), cancelled: `cos^2/((e + f x)(a + a sin))`.
if ((answer = IndefiniteIntegralSolver.SolveACosinePowerOverABinomialInTheSineByCancelling(expr, x)) is { }) return answer;
// A function of an inverse function of c + d x beside powers of multiples of it, under
// u = c + d x, before the rules that timed out on `(c e + d e x)^3 (a + b arsinh(c + d x))^2`.
if ((answer = IndefiniteIntegralSolver.SolveAFunctionOfAnInverseOfALinearBesideItsPowersUnderTheLinear(expr, x)) is { }) return answer;
// A power of x times an inverse tangent and the logarithm of its quadratic, by parts with the
// two remainders that have no antiderivative apiece paired, before the rules that timed out.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfXTimesAnArctangentAndTheLogarithmOfItsQuadratic(expr, x)) is { }) return answer;
// A rational function over the quadratic of an inverse tangent beside it, split, before the
// rules that timed out on `x^4 atan(a x)/(c + a^2 c x^2)`.
if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionOverTheQuadraticOfAnArctangentBesideIt(expr, x)) is { }) return answer;
// A power of x times a power of a sum holding an inverse function of x^n, under t = x^n,
// before the rules that timed out on `x^3 (a + b atanh(c x^2))^2`.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfXTimesAFunctionOfAnotherUnderThatPower(expr, x)) is { }) return answer;
// A polynomial in a sine or a cosine over a whole power of a binomial in it, in powers of the
// binomial, before the half-angle wrote 30 million characters for `(A + B cos + C cos^2)/(a + b cos)^4`.
if ((answer = IndefiniteIntegralSolver.SolveAPolynomialInASineOverAPowerOfABinomialInItInPowersOfTheBinomial(expr, x)) is { }) return answer;
// A rational function times the logarithm of a nested product of powers of linears, by parts,
// its logarithm's derivative read off as written: `ln(e (f (a + b x)^p (c + d x)^q)^r)`.
if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionTimesALogarithmOfPowersOfLinears(expr, x, integrateByParts)) is { }) return answer;
// x^m (d + e x^2)^p (a + b F(c x)) with m + 2p + 3 = 0 and F an inverse trigonometric function, by
// parts against v = x^(m + 1) (d + e x^2)^(p + 1)/(d (m + 1)), before by parts against the
// integrator's piecewise v timed out on `x^3 (a + b asin(c x))/(d + e x^2)^3`.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfXOverAPowerOfAQuadraticTimesAnInverseTrigonometricFunctionByParts(expr, x, integrateByParts)) is { }) return answer;
// A function of an inverse of something not linear times its derivative up to a factor
// constant though written with x: `atan(c x/sqrt(a - c^2 x^2))^2/sqrt(d - c^2 d x^2/a)`. Early,
// since it fires only where that factor holds x, and the rules before it spent 28 seconds on
// `atan(e x/sqrt(-a e^2/b - e^2 x^2))/sqrt(a + b x^2)` declining.
if ((answer = IndefiniteIntegralSolver.SolveAFunctionOfAnInverseTimesItsDerivativeUpToAConstant(expr, x, integrateByParts)) is { }) return answer;
// A whole power of a linear in the inverse sine, cosine or hyperbolic cosine of
// `c + d x^2` with `c^2 = 1`, by its recurrence, which closes: by parts it went round.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfAnInverseFunctionOfAQuadraticByTheRecurrence(expr, x)) is { }) return answer;
// A product of powers of quotients of linears with one linear in two of them, written
// over each linear once with the constant that takes in front. First, since it answers
// the question asked only, and the polynomial term below writes `1/u` as `u^(-1)` and
// asks that a level down: `1/(e^atanh(a x) (c - c/(a x))^(3/2))` reached it nowhere.
// Two linears to one power that is not whole, as one quadratic to that power, before each
// linear is written once: `(2 + 3x)^4/((1 - 2x)^(5/2) (3 + 5x)^(5/2))` took forty-five
// seconds there, and over `(3 - x - 10x^2)^(5/2)` it takes half of one.
// A linear below the cube root of a quadratic, in closed form where Rubi has one.
if ((answer = IndefiniteIntegralSolver.SolveALinearBelowACubeRootOfAQuadraticInClosedForm(expr, x)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveTwoLinearsToOnePowerAsOneQuadratic(expr, x, integrateByParts)) is { }) return answer;
// A linear and a quadratic to one power as one binomial, `(1 + x) (1 - x + x^2)` as
// `1 + x^3`, which the rules for a power of a binomial answer.
if ((answer = IndefiniteIntegralSolver.SolveALinearAndAQuadraticToOnePowerAsOneBinomial(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveByWritingEachLinearOnce(expr, x, integrateByParts)) is { }) return answer;
// A rational function with numbers for coefficients over a denominator past the twelfth
// degree, by Hermite's reduction before anything searches: nothing below answered one.
if ((answer = IndefiniteIntegralSolver.SolveAHighDegreeRationalFunctionByHermite(expr, x)) is { }) return answer;
// A whole power of the square of a polynomial written out, written over its root: the rules
// for a power of a binomial read `(a + b x^2)^4` where `(a^2 + 2 a b x^2 + b^2 x^4)^2` ran
// past the budget.
if ((answer = IndefiniteIntegralSolver.SolveAWholePowerOfASquareOverItsRoot(expr, x, integrateByParts)) is { }) return answer;
// A power of a linear beside the derivative of a quadratic over a whole power of it, by
// parts once, before anything takes the symbolic quadratic apart.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfALinearBesideTheDerivativeOfAQuadraticOverItsPower(expr, x, integrateByParts)) is { }) return answer;
// A power of a linear that is not whole, times a linear, over a whole power of a quadratic,
// the power of the quadratic lowered one at a time.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfALinearOverAPowerOfAQuadratic(expr, x, integrateByParts)) is { }) return answer;
// A rational function over a whole power of a symbolic quadratic, the power lowered one
// at a time, before the partial fractions take the quadratic apart over its roots.
if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionOverAPowerOfASymbolicQuadratic(expr, x, integrateByParts)) is { }) return answer;
// A power of x over a sextic that is a sum of two cubes, split over the cube roots of
// unity into three quadratics before the partial fractions try the symbolic sextic.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfXOverASumOfTwoCubes(expr, x, integrateByParts)) is { }) return answer;
// A power of an even quadratic over a binomial in it, split over the binomial's roots.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfAnEvenQuadraticOverABinomialInIt(expr, x, integrateByParts)) is { }) return answer;
// A root or a power of a product beside a power of one of its factors, the product
// split: `(a + b x^2)^(5/2)/sqrt(a^2 - b^2 x^4)` is `K (a + b x^2)^2/sqrt(a - b x^2)`.
// Early, since what reads the product whole is slower and longer: Rubi's 1.2.1.2:2218,
// `1/((d + e x)^4 (a d e + (c d^2 + a e^2) x + c d e x^2)^(3/2))`, took 27 seconds to
// reach it and its split form takes a quarter of one.
if ((answer = IndefiniteIntegralSolver.SolveBySplittingARootOfAProductBesideARootOfAFactor(expr, x, integrateByParts)) is { }) return answer;
// A symbolic trinomial in x^2 below the bar beside a root of a quadratic, over its roots
// in x^2, before anything reads the trinomial whole: asked of it, the routes below ran
// past a minute on Rubi's 1.2.2.4 `sqrt(d + e x^2)/(x^2 (a + b x^2 + c x^4))`.
if ((answer = IndefiniteIntegralSolver.SolveATrinomialInTheSquareBesideTheRootOfAQuadratic(expr, x)) is { }) return answer;
// A linear under a power inside a sum, written as the variable: `x^3/(a + b (c + d x)^3)`.
// Before the polynomial term, which takes the leading coefficient out of a power of
// the sum and writes what is left out in powers of x, where the linear is gone; and
// before the substitution search, which reads one function at a time.
if ((answer = IndefiniteIntegralSolver.SolveByWritingAFunctionOfOneShiftedLinear(expr, x, integrateByParts)) is { }) return answer;
// A polynomial beside error functions of one shifted linear, written in it: by parts in
// x leaves a Gaussian of the shifted linear beside a polynomial, which nothing reads.
if ((answer = IndefiniteIntegralSolver.SolveAPolynomialBesideErrorFunctionsOfAShiftedLinear(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveAsPolynomialTerm(expr, x, integrateByParts)) is { }) return answer;
// A product of powers of the variable with a power of a constant multiple of it
// among them, `(c x)^m x^n`, by the power rule with the written power kept as
// it is. Beside the polynomial term, since that is what it is.
if ((answer = IndefiniteIntegralSolver.SolveAProductOfPowersOfTheVariable(expr, x)) is { }) return answer;
// A power of x times a power of its logarithm, by the closed reduction: exact,
// and by parts n times where the exponent is a symbol was not taken.
if ((answer = IndefiniteIntegralSolver.SolveAPowerTimesAPowerOfTheLogarithm(expr, x)) is { }) return answer;
// And a half-odd power of it, onto the Gaussian's moments by t = sqrt(F).
if ((answer = IndefiniteIntegralSolver.SolveAPowerTimesAHalfOddPowerOfTheLogarithm(expr, x)) is { }) return answer;
// And an exponential of a quadratic in it, onto the Gaussian by t = ln(c x^r).
if ((answer = IndefiniteIntegralSolver.SolveAPowerTimesAnExponentialOfAQuadraticInALogarithm(expr, x)) is { }) return answer;
// And a negative whole power of one, onto the exponential integral by t = A + B ln(c x^r).
if ((answer = IndefiniteIntegralSolver.SolveAPowerOverAPowerOfALogarithm(expr, x)) is { }) return answer;
// And x^m artanh(A x/sqrt(d + A^2 x^2)), by parts, the logarithm's derivative read as the root it is.
if ((answer = IndefiniteIntegralSolver.SolveByPartsAgainstAnArtanhWhoseDerivativeIsARoot(expr, x, integrateByParts)) is { }) return answer;
// A power of a quotient of linears beside whole powers of them, which is how e^(n acoth(a x))
// over a power of c - a^2 c x^2 arrives folded, under t = the quotient.
if ((answer = IndefiniteIntegralSolver.SolveByTheMoebiusSubstitutionOfAQuotientOfLinears(expr, x)) is { }) return answer;
// And a power of x times the logarithm of A + B sqrt(Q), by parts, its derivative over the conjugate.
if ((answer = IndefiniteIntegralSolver.SolveByPartsAgainstALogarithmOfARadicalSum(expr, x)) is { }) return answer;
// A function of tanh(L) or coth(L) alone that is not rational in it, under u = tanh(L).
if ((answer = IndefiniteIntegralSolver.SolveAFunctionOfTheHyperbolicTangent(expr, x, integrateByParts)) is { }) return answer;
// A power of a quotient of linears over a half-odd power of their product, by Liouville's ansatz.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfAQuotientOfLinearsOverAHalfOddPowerOfTheirProduct(expr, x)) is { }) return answer;
// A quotient by a radical sum whose conjugate product is a constant, over the conjugate.
if ((answer = IndefiniteIntegralSolver.SolveByTheConjugateOfARadicalSumBelowTheBar(expr, x, integrateByParts)) is { }) return answer;
// By parts against an inverse hyperbolic function beside a non-polynomial, its logarithm's derivative written algebraically.
if ((answer = IndefiniteIntegralSolver.SolveByPartsAgainstAnInverseHyperbolicFunctionAsItsLogarithm(expr, x, integrateByParts)) is { }) return answer;
// A power of an exponential with a positive base is the exponential of the product,
// exactly, and only that spelling is one the exponential rules read.
if ((answer = IndefiniteIntegralSolver.SolveByFlatteningAPowerOfAnExponential(expr, x, integrateByParts)) is { }) return answer;
// And any other power of an exponential beside a polynomial below the bar, as a constant
// multiple of an exponential wherever it is differentiable.
if ((answer = IndefiniteIntegralSolver.SolveByWritingAPowerOfAnExponentialAsAMultipleOfOne(expr, x, integrateByParts)) is { }) return answer;
// An exponential of a quadratic in 1/L beside a power of L, onto the Gaussian under u = 1/L.
if ((answer = IndefiniteIntegralSolver.SolveAGaussianInAReciprocal(expr, x)) is { }) return answer;
// And a trigonometric function of the reciprocal of a linear beside a power of it, under u = 1/L.
if ((answer = IndefiniteIntegralSolver.SolveAFunctionOfTheReciprocalOfALinear(expr, x, integrateByParts)) is { }) return answer;
// And one of `a + b/x` beside any rational function, under `x = 1/u`:
// `sin(c + d/x)/(e + f x)` is `-sin(c + d u)/(u (e u + f)) du`.
if ((answer = IndefiniteIntegralSolver.SolveByTheReciprocalOfAFunctionOfTheReciprocal(expr, x, integrateByParts)) is { }) return answer;
// An exponential of a multiple of a logarithm is a power of the argument, which is
// how every inverse hyperbolic function under an exponential arrives.
if ((answer = IndefiniteIntegralSolver.SolveByFoldingAnExponentialOfALogarithm(expr, x, integrateByParts)) is { }) return answer;
// An exponential of a polynomial beside the polynomial's derivative, under u = P,
// which the substitution search does not reach: it writes the exponential apart.
if ((answer = IndefiniteIntegralSolver.SolveByTheExponentAsTheVariable(expr, x, integrateByParts)) is { }) return answer;
// And an exponential of a hyperbolic sine or cosine of a linear beside a function of it
// over its derivative, under u = twice that sine or cosine, which arrive as exponentials.
if ((answer = IndefiniteIntegralSolver.SolveAnExponentialOfAHyperbolicFunctionBesideItsDerivative(expr, x, integrateByParts)) is { }) return answer;
// A rational function of the tangent beside two roots of linears in it, or one beside a
// whole power of a + i a tan(z) below the bar, over the linears of 1 + tan^2: in the root
// of the linears' quotient 1 + tan^2 is a symbolic quartic, and a search past the budget.
if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionOfTheTangentBesideRootsOfLinearsInIt(expr, x, integrateByParts)) is { }) return answer;
// `A + i A tan(z)` is `A e^(i z)/cos(z)`, which beside a polynomial is a shape the
// closed rules answer, where the imaginary unit in the coefficient is read by none.
// The same identity beside a power of the secant, where the powers are not whole and add
// up to a whole number: an exponential over a power of the cosine, in e^(i z). Before the
// rule below, which writes a power of the identity that is not whole as the power of a
// product and takes it apart, and so answers (c sec)^(5/2)/(a + i a tan)^(5/2) with the
// wrong constant wherever the cosine is negative.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfAnImaginaryTangentBesideAPowerOfTheSecant(expr, x, integrateByParts)) is { }) return answer;
// And whatever the powers, in the sum, where they do not add up to a whole number.
if ((answer = IndefiniteIntegralSolver.SolveAPowerOfTheSecantBesideAPowerOfAnImaginaryTangentSumInTheSum(expr, x, integrateByParts)) is { }) return answer;
// And powers of the two conjugate sums, both not whole, as the exponential they make.
if ((answer = IndefiniteIntegralSolver.SolveAConjugatePairOfImaginaryTangentSums(expr, x, integrateByParts)) is { }) return answer;
// A rational function of the tangent beside a power of a + i a tan(z), in that sum.
if ((answer = IndefiniteIntegralSolver.SolveInTheImaginarySumOfAConstantAndATangent(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveByWritingAnImaginaryTangentAsAnExponential(expr, x, integrateByParts)) is { }) return answer;
// And `A cos(z) + i A sin(z)`, which is `A e^(i z)`, where no rotation is real.
if ((answer = IndefiniteIntegralSolver.SolveByWritingAnImaginarySumOfACosineAndASineAsAnExponential(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveByWritingAnImaginarySumOfACosineAndASineOverTheCosine(expr, x, integrateByParts)) is { }) return answer;
// Exponentials of quadratics, with sines, cosines and sums of them multiplied out:
// each term is the Gaussian's, where the search would take the product by parts.
if ((answer = IndefiniteIntegralSolver.SolveAProductOfExponentialsOfQuadratics(expr, x)) is { }) return answer;
// An exponential of a linear over a power of a linear, onto the exponential integral.
if ((answer = IndefiniteIntegralSolver.SolveAnExponentialOfALinearOverAPowerOfALinear(expr, x)) is { }) return answer;
// And of a quadratic over a power of the linear through its vertex, onto the moments about 0.
if ((answer = IndefiniteIntegralSolver.SolveAnExponentialOfAQuadraticOverTheLinearThroughItsVertex(expr, x, integrateByParts)) is { }) return answer;
// And a sum of exponentials, which is how sinh and cosh arrive, onto Shi and Chi.
if ((answer = IndefiniteIntegralSolver.SolveAHyperbolicOfALinearOverAPowerOfALinear(expr, x)) is { }) return answer;
// And either over several linears, split into partial fractions over them first, as the
// trigonometric rule below splits.
if ((answer = IndefiniteIntegralSolver.SolveAnExponentialOverSeveralLinears(expr, x)) is { }) return answer;
// And exponentials below the bar, where they are whole powers of one exponential and
// its powers alone are left below: `1/((c + d x)(a + a tanh(e + f x)))`.
if ((answer = IndefiniteIntegralSolver.SolveAnExponentialBelowTheBarBesideAPowerOfALinear(expr, x)) is { }) return answer;
// And with sines and cosines beside the exponential, written as exponentials: each term
// is then the exponential's, with a complex rate.
if ((answer = IndefiniteIntegralSolver.SolveAnExponentialTimesATrigonometricOverLinears(expr, x)) is { }) return answer;
// Sines and cosines of a linear over a power of a linear, onto Si and Ci under u = the
// linear, where the search would take the quotient by parts without end.
if ((answer = IndefiniteIntegralSolver.SolveATrigonometricOfALinearOverAPowerOfALinear(expr, x)) is { }) return answer;
// And an inverse trigonometric function below the bar, which under the substitution that
// undoes it is the same question; before the search, which does not find it.
if ((answer = IndefiniteIntegralSolver.SolveAReciprocalOfAnInverseTrigonometricFunction(expr, x, integrateByParts)) is { }) return answer;
// A fractional power of a perfect square is the power of the modulus, sgn(P) P^(2r).
if ((answer = IndefiniteIntegralSolver.SolveByTakingARootOfAPerfectSquare(expr, x, integrateByParts)) is { }) return answer;
// And a square in one trigonometric function, at the top: the modulus of the linear in it.
if ((answer = IndefiniteIntegralSolver.SolveByTakingARootOfAPerfectSquareInATrigonometricFunction(expr, x, integrateByParts)) is { }) return answer;
// Four nested roots with a closed form: `sqrt(1 + sqrt(1 - x^2))`,
// `sqrt(x^2 + sqrt(1 + x^4))/sqrt(1 + x^4)` and the two beside them in Rubi's 1.3.3.
if ((answer = IndefiniteIntegralSolver.SolveANestedRootByItsClosedForm(expr, x, integrateByParts)) is { }) return answer;
// And any power of a square in any power of x, as the power of its root times a factor
// constant where the root is not zero: `x^2 (a^2 + 2 a b x^3 + b^2 x^6)^p`.
if ((answer = IndefiniteIntegralSolver.SolveByWritingAPowerOfASquareAsAPowerOfItsRoot(expr, x, integrateByParts)) is { }) return answer;
// And a fractional power of a sum whose every term has x in it, the power of x taken out
// with the factor that is constant where it is not zero: `1/sqrt(a x^2 + b x^5)`.
if ((answer = IndefiniteIntegralSolver.SolveByTakingAPowerOfXOutOfAFractionalPower(expr, x, integrateByParts)) is { }) return answer;
// A power of a multiple of a quadratic's derivative beside a power of the quadratic is
// a binomial in the derivative: `(b d + 2 c d x)^m (a + b x + c x^2)^p`.
if ((answer = IndefiniteIntegralSolver.SolveByTheDerivativeOfAQuadraticAsTheVariable(expr, x, integrateByParts)) is { }) return answer;
// x^(n - 1) g(x^n) with a symbolic n is g(u)/n under u = x^n.
if ((answer = IndefiniteIntegralSolver.SolveByAPowerOfTheVariableTimesAFunctionOfItsPower(expr, x, integrateByParts)) is { }) return answer;
// A whole power of a product of a constant and the variable, as the product of
// the powers, which is how the inverse hyperbolic secant and cosecant arrive.
if ((answer = IndefiniteIntegralSolver.SolveByDistributingWholePowersOfProducts(expr, x, integrateByParts)) is { }) return answer;
// And of quotients, which a substitution leaves where it has simplified a sum over
// the bar: `(c/(a + c u^2))^2` is read by nothing that reads `c^2/(a + c u^2)^2`.
if ((answer = IndefiniteIntegralSolver.SolveByDistributingWholePowersOfQuotients(expr, x, integrateByParts)) is { }) return answer;
// The derivative of a power of x over a binomial, over a multiple of the binomial's
// square plus a power of x: an arctangent of the quotient, which nothing else reads
// past a quartic -- `(e - 4 f x^3)/(e^2 + 4 d f x^2 + 4 e f x^3 + 4 f^2 x^6)`.
if ((answer = IndefiniteIntegralSolver.SolveAsTheArctangentOfAPowerOfXOverABinomial(expr, x, integrateByParts)) is { }) return answer;
// And a fractional or symbolic power of a monomial: `(c x^n)^b` is `c^b x^(n b)`
// for a positive `c`, on the `x > 0` where a symbolic `n` leaves the integrand real.
if ((answer = IndefiniteIntegralSolver.SolveByDistributingAPowerOfAMonomial(expr, x, integrateByParts)) is { }) return answer;
// A power of a constant multiple of the radicand a logarithm holds under a root,
// written over that radicand: `acosh(c x)` beside `(d - c^2 d x^2)^(5/2)`.
if ((answer = IndefiniteIntegralSolver.SolveByWritingAConstantMultipleOfARadicandOverIt(expr, x, integrateByParts)) is { }) return answer;
// A power of the variable times a sine or cosine of a logarithm, where two rounds of
// parts close on the integrand: `int x^m sin(a + b ln(c x^n))` in closed form.
if ((answer = IndefiniteIntegralSolver.SolveAPowerTimesATrigonometricOfALogarithm(expr, x, integrateByParts)) is { }) return answer;
// The same with an imaginary coefficient on the logarithm, where the closed form divides
// by zero: in exponentials, powers of the variable.
if ((answer = IndefiniteIntegralSolver.SolveByWritingATrigonometricFunctionOfAnImaginaryLogarithmInExponentials(expr, x, integrateByParts)) is { }) return answer;
// A logarithm of a quotient that cancels with the functions in it as
// indeterminates, which is how an inverse hyperbolic function of a hyperbolic
// one arrives: atanh(tanh(u)) is 1/2 ln(e^(2u)) once its quotient is cancelled.
if ((answer = IndefiniteIntegralSolver.SolveByCancellingInsideALogarithm(expr, x, integrateByParts)) is { }) return answer;
// A logarithm whose derivative is a constant k is k x + c, and is named so.
if ((answer = IndefiniteIntegralSolver.SolveByNamingALogarithmLinearInTheVariable(expr, x, integrateByParts)) is { }) return answer;
// A polynomial times a rational function of exponentials, by parts against the
// whole rational function, before anything splits the sum: the general parts rule
// takes each term on its own, and each term's antiderivative keeps a logarithm the
// sum's does not -- `x tanh(x)^2` was twenty-five seconds of dilogarithms that way.
// Closed, and declining in a moment where the antiderivative keeps a logarithm.
if (integrateByParts && (answer = IndefiniteIntegralSolver.SolveAPolynomialTimesARationalFunctionOfAnExponential(expr, x, integrateByParts)) is { }) return answer;
// An integrand *already written* as a sum is split here, before any search. Every
// rule below does speculative work on the whole sum first, and on a polynomial that
// costs everything: the terms of `1 + 2x + ... + 25x^24` integrate in under a
// millisecond between them, and the sum they add up to took 20 seconds.
//
// degree 4 8 12 16 20 24
// was 64ms 384ms 705ms 708ms 2.6s 20.1s
// is 69ms 69ms 141ms 213ms 138ms 0.16s
//
// **Only a sum, and deliberately not a product with a sum in it.** Expanding one is a
// rewrite, and a rewrite this early takes answers away from the rules that give
// better ones: `x * (x^2 + 1)^3` is `(x^2 + 1)^4/8` by substitution and a written-out
// degree-8 polynomial by expanding, both right and only one worth reading. Three
// tests pinned exactly that and caught this when the split was put in front of the
// substitution wholesale. The expanding call stays where it was, below.
// A whole power below -1 of a linear beside a power that is not whole of another, by the
// recurrence that raises the whole power to -1: closed, where the substitution for the
// root makes a rational function over a power of a symbolic quadratic.
if ((answer = IndefiniteIntegralSolver.SolveANegativePowerOfALinearBesideAPowerOfAnotherByTheRecurrence(expr, x, integrateByParts)) is { }) return answer;
// A product of powers times a sum that is the derivative of the product with some
// of the powers raised by one -- `e^x x^2 ln(x)^2 (3 + (3 + x) ln(x))` -- read off
// the sum, for symbolic exponents too. Before the split, which loses it.
if ((answer = IndefiniteIntegralSolver.SolveAsTheDerivativeOfAProductOfPowers(expr, x)) is { }) return answer;
// And a power of a binomial in x^n beside a power of x, with a symbol in the exponents,
// where Chebyshev's third case is a whole number at most 0: by Rubi's reduction, which
// the rule above does not read for a power on its own or past the first step.
if ((answer = IndefiniteIntegralSolver.SolveABinomialInTheThirdCaseWithASymbolicExponent(expr, x)) is { }) return answer;
// Powers of two linears whose exponents sum to a whole number below -2, beside a polynomial of
// low degree, by t = L1/L2: a power of t beside a polynomial. The rule above reads the sum -2.
if ((answer = IndefiniteIntegralSolver.SolveTwoLinearPowersWhoseExponentsSumToAWholeNumber(expr, x)) is { }) return answer;
// A rational function of x and of a tower of exponentials and logarithms over it,
// by the Risch-Norman ansatz: a rational function of the same plus logarithms of
// the denominator's factors, the coefficients unknown and solved for. Before the
// split, since Hearn's `e^(x^2)/x + 2x e^(x^2) ln(x) + ...` is a sum whose terms are
// not elementary apart; at the top only, and only for a tower no closed rule reads.
if ((answer = IndefiniteIntegralSolver.SolveByRischNormanAnsatz(expr, x)) is { }) return answer;
// A quotient of polynomials in x and the functions of x in it, cancelled with those
// functions as indeterminates and its denominator factored the same way:
// `(x^2 + 2x sin(x) + sin(x)^2)/(x + sin(x))^2` is `1`, and nothing else reads it.
// Before the reduction over a linear factor and the split, which expand and take
// apart what this cancels whole: the remainder by parts leaves from
// `x asech(a + b x)` is `x^2 (L^3 q^(-1/2) + L^4)/(L^6 (1/L + sqrt(q)))`, which
// is `x^2/(L^2 sqrt(q))` cancelled, and was five terms over `L^5 (1 + L sqrt(q))`
// each searched for seconds when the reduction reached it first.
if ((answer = IndefiniteIntegralSolver.SolveByCancellingWithFunctionsAsIndeterminates(expr, x, integrateByParts)) is { }) return answer;
// A quotient by the square of a base with a function in it, by parts against the base's
// reciprocal where the base's derivative makes the rest exact.
if ((answer = IndefiniteIntegralSolver.SolveByPartsAgainstTheReciprocalOfASquaredBase(expr, x)) is { }) return answer;
// A polynomial over a power of a linear and something that is not a polynomial,
// the polynomial written in powers of the linear at its root: the part the power
// divides goes over the rest alone. Before the split, so that the whole polynomial
// is reduced once rather than each of its terms.
// A linear over a linear beside the root of a cubic binomial, or a quadratic over a
// quadratic, at the coefficients where the integral is elementary: Rubi's
// `(1 + x + sqrt(3))/((1 + x - sqrt(3)) sqrt(1 + x^3))`. Before the reduction over the
// linear, which writes it as `1` and `2 sqrt(3)` over the linear, neither elementary.
if ((answer = IndefiniteIntegralSolver.SolveAPseudoEllipticQuotientOverTheRootOfACubicBinomial(expr, x)) is { }) return answer;
// x over a cubic binomial beside the root of another, at the two ratios where that is
// elementary: Welz's `x/((4 - x^3) sqrt(1 - x^3))`.
if ((answer = IndefiniteIntegralSolver.SolveXOverACubicBinomialBesideTheRootOfAnother(expr, x, integrateByParts)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveByReducingThePolynomialOverALinearFactor(expr, x, integrateByParts)) is { }) return answer;
if (expr is Entity.Sumf or Entity.Minusf
&& (answer = IndefiniteIntegralSolver.SolveBySplittingSum(expr, x, integrateByParts)) is { })
return answer;
// A constant multiple of D'/D for a sum D below the bar holding a root, decided at
// sampled points: Hearn's quotient of two roots' derivatives over their sum was a
// search past the budget for want of the radical algebra the substitution would
// have needed.
if ((answer = IndefiniteIntegralSolver.SolveALogarithmicDerivativeOfARadicalSum(expr, x)) is { }) return answer;
if ((answer = IndefiniteIntegralSolver.SolveLogarithmic(expr, x, integrateByParts)) is { }) return answer;
// Two or more square roots of polynomials written as one, before the substitution
// looks for the one subtree it replaces: `x/(sqrt(1 + x^2) sqrt(1 - x^2))` is
// `x/sqrt(1 - x^4)`, which the substitution answers, and it is what by parts leaves
// from `arcsin(x)/(1 + x^2)^(3/2)`.
if ((answer = IndefiniteIntegralSolver.SolveByCombiningRadicals(expr, x, integrateByParts)) is { }) return answer;
// A polynomial beside the roots of two linears with symbols in them, which the
// combining above cannot sign, and over a power of one more linear: