-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMain.kt
1122 lines (1030 loc) · 42.5 KB
/
Main.kt
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 2023 by Arkadiusz Bulski <[email protected]> under MIT License
// This library does one thing and one thing only: it differentiates arbitrary mathematical functions.
// Well, that and it plots them and optimizes them and visualizes them and... yeah, just that.
// What it cannot do (yet):
// - integrate symbolically
// - function optimization is 90% baked
// Installing dependencies manually:
// $ sudo apt install kotlin graphviz python3 python3-matplotlib firefox
// Compiling and running the code manually:
// $ kotlinc Main.kt -include-runtime -d Main.jar
// $ java -jar Main.jar
// ----------------------------------------------------------------------------
// Important imports (pun)
import java.io.File
import java.lang.IllegalStateException
import java.lang.StrictMath.*
// ----------------------------------------------------------------------------
// Core functionality of functions (also a pun)
typealias EvaluateFunc = (Double) -> Double
typealias DeriveFunc = () -> Function
typealias DescribeFunc = (String) -> String
typealias ChildrenFunc = () -> List<Function>
typealias LabelFunc = () -> String
typealias ReEvaluateFunc = (List<Function>) -> ((Double) -> Double)
typealias ReDeriveFunc = (List<Function>) -> (() -> Function)
typealias ToConstFunc = () -> Double?
typealias ToInfoFunc = () -> OptimizationInfo?
typealias PossiblyOptimizedFunc = () -> Function?
/**
* Used internally to give functions and function graphs unique IDs. It helps to sort out what is what when debugging.
*/
private var nextID: Int = 1
/**
* Used internally to pass information from one function to another what is its structure ie. its operation and
* operands. This is used by phase 2 of the optimization algorithm. The problem it solves is that Function class
* contains only lambdas and has no derived classes, and therefore its impossible to know what operation it represents
* or to get its captured operands.
*/
data class OptimizationInfo (
val op: String,
val leftFunc: Function,
val leftConst: Double,
val rightFunc: Function,
val rightConst: Double,
)
/**
* Used internally.
*/
fun withEnglishSuffix (n: Int): String =
n.toString() + when (n) {
0 -> "th"
1 -> "st"
2 -> "nd"
3 -> "rd"
else -> "th"
}
/**
* The Function class serves the core functionality (pardon the pun) of the library. Its instance represents an
* abstract mathematical function, and by means of the 10 lambdas it allows you to evaluate the function for some
* value x, differentiate it many many times, integrate it numerically, optimize it, visualize it with a graph, etc.
*
* Note to users:
* You can evaluate it for a given x, using invoke operator:
* | val f = Sin.Of(X.PowerOf(2))
* | f(3.14159) returns -0.43028616647684903
* (Please do not use the Eval lambda, nor EvalCached method. Those are used internally.)
* You can differentiate it, using derive* methods:
* | f.derive() return a Function, f's derivative
* | f.deriveNth(5) returns a Function, f's fifth derivative
* (...which you can manipulate in all the same ways as the original function f.)
* You can integrate it numerically, using integrate method:
* | f.integrate(0.0, 10.0, 1000) returns a Double
* You can optimize it:
* | f.optimized() returns a Function
* (Look at FunctionGraph method optimized for details.)
* You can display its textual representation:
* | f.Description returns a String
* (Please do not use the Describe lambda, which is used internally.)
* You can display its graphical representation:
* (Look at FunctionGraph method exportedSvgs for details.)
*
* Note to implementers:
* To make your own Function, you need to provide its primary constructor any or all 10 lambdas. Those are:
* Eval (x:Double -> Double) -- It evaluates the function for the given x. You do not need to be concerned
* with caching, as that is taken care of. This is needed if you want to plot the function, integrate
* it, or just to evaluate it for whatever reason.
* Derive (() -> Function) -- It differentiates the current function, and returns it as a Function instance.
* This is needed if you want to obtain any of its derivatives, which is the whole point of using this
* library btw.
* Describe (d:String -> String) -- It computes a textual representation of the function. You can embed
* Description of the operands into it. The d argument is almost always "X", unless you start fiddling
* with nesting functions in which case d will be a description of the nested function. This is needed
* if you want (1) to see its textual mathematical form (2) to export it to SVG (3) to optimize it.
* Children (() -> List<Function>) -- It returns a list of Function instances that are its operands. This is
* needed if you want (1) to export it to SVG (2) to optimize it.
* Label (() -> String) -- It returns a very short description of the function ie. "Sin" "X" "+" "2 *" etc.
* This is needed when exporting the function into SVG format. The label is the text displayed in the
* node, and that it all it does.
* ReEvaluate ((mc:List<Function>) -> ((x:Double) -> Double)) -- It allows you to rebuild a new class instance
* that does the same thing as the original instance, but with different operands. The problem it
* solves is that Function class is immutable. This lambda *should* return the exact same thing as
* Eval lambda, except that you replace operands eg. this with mc[0], mc[1], etc. in same order as they
* appear in Children lambda. This is needed if you want to optimize the function. All 3 phases of the
* optimizing algorithm use this lambda.
* ReDerive ((mc:List<Function>) -> (() -> Function)) -- It allows you to rebuild a new class instance that
* does the same thing as the original instance, but with different operands. The problem it solves is
* that Function class is immutable. This lambda *should* return the exact same thing as Derive lambda,
* except that you replace operands eg. this with mc[0], mc[1], etc. in same order as they appear in
* Children lambda. This is needed if you want to optimize the function. All 3 phases of the optimizing
* algorithm use this lambda.
* ToConst (() -> Double?) -- If some or all the operands are constants and therefore the current function is
* itself a constant, this lambda returns that constant's value. This is optional, but useful if you
* want to optimize the function. The phase 1 and 2 of the optimizing algorithm uses this lambda. If
* unsure, just make it return a null.
* ToInfo (() -> OptimizationInfo?) -- It provides other functions with information what this instance does
* and what are its operands. This information is necessary for some of the mathematical optimizations.
* This is optional, but useful if you want to optimize the function. The phase 2 of the optimizing
* algorithm uses this lambda. If unsure, just make it return a null.
* PossiblyOptimized (() -> Function?) -- It provides the optimization algorithm with a new, better instance
* than the original instance. Usually this lambda refers to its operands ToConst and ToInfo lambdas.
* This is optional, but useful if you want to optimize the function. The phase 2 of the optimizing
* algorithm uses this lambda. If unsure, just make it return a null.
*/
class Function (
val Eval: EvaluateFunc = { throw NotImplementedError("Eval lambda was not defined.") },
val Derive: DeriveFunc = { throw NotImplementedError("Derive lambda was not defined.") },
val Describe: DescribeFunc = { throw NotImplementedError("Describe lambda was not defined.") },
val Children: ChildrenFunc = { throw NotImplementedError("Children lambda was not defined.") },
val Label: LabelFunc = { throw NotImplementedError("Label lambda was not defined.") },
val ReEvaluate: ReEvaluateFunc = { throw NotImplementedError("ReEvaluate lambda was not defined.") },
val ReDerive: ReDeriveFunc = { throw NotImplementedError("ReDerive lambda was not defined.") },
val ToConst: ToConstFunc = {null},
val ToInfo: ToInfoFunc = {null},
val PossiblyOptimized: PossiblyOptimizedFunc = {null},
) {
/**
* Secondary constructor, a minimalistic version of it. You can only use this function to evaluate and derive, no
* optimization or exporting or anything fancy.
*/
constructor (
Eval: EvaluateFunc,
Derive: DeriveFunc,
) : this (
Eval,
Derive,
{ throw NotImplementedError("Describe lambda was not defined.") },
{ throw NotImplementedError("Children lambda was not defined.") },
{ throw NotImplementedError("Label lambda was not defined.") },
{ throw NotImplementedError("ReEvaluate lambda was not defined.") },
{ throw NotImplementedError("ReDerive lambda was not defined.") },
{null},
{null},
{null},
)
/**
* Secondary constructor, a minimalistic version of it. You can only use this function to evaluate and derive, no
* optimization or exporting or anything fancy.
*/
constructor (
Children: ChildrenFunc,
ReEvaluate: ReEvaluateFunc,
ReDerive: ReDeriveFunc,
) : this (
ReEvaluate(Children()),
ReDerive(Children()),
{ throw NotImplementedError("Describe lambda was not defined.") },
Children,
{ throw NotImplementedError("Label lambda was not defined.") },
ReEvaluate,
ReDerive,
{null},
{null},
{null},
)
/**
* Secondary constructor, used internally to rebuild functions which are immutable.
*/
constructor (originalFunc: Function, mutableChildren: List<Function>) : this(
originalFunc.ReEvaluate(mutableChildren),
originalFunc.ReDerive(mutableChildren),
originalFunc.Describe,
{mutableChildren.toList()},
originalFunc.Label,
originalFunc.ReEvaluate,
originalFunc.ReDerive,
originalFunc.ToConst,
originalFunc.ToInfo,
originalFunc.PossiblyOptimized,
)
/**
* Secondary constructor, used internally to rebuild functions which are immutable.
*/
fun possiblyRebuild (mutableChildren: List<Function>): Function{
val thisChildren = this.Children()
return if (thisChildren.indices.all({ i -> thisChildren[i] === mutableChildren[i] }))
this
else
Function(this, mutableChildren)
}
/**
* Used internally when exporting and useful during debugging.
*/
val ID: Int = nextID++
/**
* Textual mathematical representation of the function. This is useful for public display, when debugging, but it
* is also used internally when exporting graphs, optimizing, etc.
*/
val Description: String = this.Describe("X")
/**
* Used internally by EvalCached.
*/
private var CachedEvalX: Double = Double.NaN
/**
* Used internally by EvalCached.
*/
private var CachedEvalResult: Double = Double.NaN
/**
* Used internally by invoke operator. Use the invoke operator instead.
*/
fun EvalCached (x: Double): Double {
if (x == CachedEvalX) {
return CachedEvalResult
} else {
CachedEvalX = x
CachedEvalResult = Eval(x)
return CachedEvalResult
}
}
/**
* Returns the next derivative. Please use this instead of Derive lambda.
*/
fun derive (): Function =
this.Derive()
/**
* Returns a derivative of a derivative of a derivative... of the function.
*/
fun deriveNth (n: Int): Function {
require(n >= 0) { "You can only demand 0-th or higher derivative." }
return (1..n).fold(this, { f,i -> f.Derive() })
}
/**
* Returns several successive derivatives in a list.
*/
fun deriveMany (n: Int): List<Function> {
require(n >= 0) { "You can only demand 0 or more derivatives." }
return (1..n).runningFold(this, { f,i -> f.Derive() })
}
/**
* Returns an optimized version of the function. Its a wrapper around FunctionGraph.optimized method.
*/
fun optimized (): Function =
FunctionGraph(this,0).optimized().DerivativeFunctions[0]
/**
* Computes a definite integral over the function numerically.
*/
fun integrate (a: Double, b: Double, count: Int): Double {
require(count >= 1, {"Amount of points (count) must be at least 1."})
var sum = 0.0
val width = (b-a)/count
for (i in 1..count) {
sum += this(a + width*i)
}
return sum * width
}
override fun toString(): String =
"Function ID=$ID is $Description"
override fun equals(other: Any?): Boolean =
(other is Function) && (Description == other.Description)
override fun hashCode(): Int =
Description.hashCode()
}
/**
* Function evaluation operator. Use it instead of Eval lambda and EvalCached method.
*/
operator fun Function.invoke (other: Double): Double =
this.EvalCached(other)
/**
* General purpose operator for +f. Yes, it is completely useless. Its only purpose is to make optimization harder.
*/
operator fun Function.unaryPlus (): Function =
Function(
{x -> this(x)},
{+this.Derive()},
{d -> this.Describe(d) },
{listOf(this)},
{"unary +"},
{mc -> {x -> mc[0](x)}},
{mc -> {mc[0].Derive()}},
// Optimizes +(a) into (a) constant.
{
return@Function this.ToConst()
},
{OptimizationInfo("+f", this, Double.NaN, this, Double.NaN)},
// Optimizes +(f) into (f) function.
{
return@Function this
},
)
/**
* General purpose operator for -f.
*/
operator fun Function.unaryMinus (): Function =
Function(
{x -> -this(x)},
{-this.Derive()},
{d -> "-(${this.Describe(d)})"},
{listOf(this)},
{"unary -"},
{mc -> {x -> -mc[0](x)}},
{mc -> {-mc[0].Derive()}},
// Optimizes -(a) into (-a) constant.
{
val right = this.ToConst()
if (right != null)
return@Function -right
null
},
{OptimizationInfo("-f", this, Double.NaN,this, Double.NaN)},
// Optimizes -(-(f)) into (f) function.
{
val right = this.ToInfo()
if (right != null && right.op == "-f")
return@Function right.rightFunc
null
},
)
/**
* Syntactic sugar for (a+f) without using Value.
* Preserves (a+f) optimization, as opposed to (f+a).
*/
operator fun Double.plus (other: Function) =
Value(this)+other
/**
* Syntactic sugar for (f+a) without using Value.
* Optimizes (f+a) into (a+f), which is useful down the line.
*/
operator fun Function.plus (other: Double) =
Value(other)+this
/**
* General purpose operator for f+g.
*/
operator fun Function.plus (other: Function): Function =
Function(
{x -> this(x)+other(x)},
{this.Derive()+other.Derive()},
{d -> "(${this.Describe(d)}) + (${other.Describe(d)})"},
{listOf(this, other)},
{"+"},
{mc -> {x -> mc[0](x)+mc[1](x)}},
{mc -> {mc[0].Derive()+mc[1].Derive()}},
// Optimizes (a)+(b) into (a+b) constant.
{
val left = this.ToConst()
val right = other.ToConst()
if (left != null && right != null)
return@Function left+right
null
},
{OptimizationInfo("f+g", this, Double.NaN, other, Double.NaN)},
// Optimizes (0+f) and (f+0) into (f) function.
// Optimizes a+(b+f) into (a+b)+f function.
// Optimizes a+(f+b) into (a+b)+f function.
{
val left = this.ToInfo()
val right = other.ToInfo()
if (left != null && left.op == "a" && left.leftConst == 0.0)
return@Function other
if (right != null && right.op == "a" && right.leftConst == 0.0)
return@Function this
if (left != null && left.op == "a" && right != null && right.op == "f+g" && right.leftFunc.ToConst() != null)
return@Function (left.leftConst + right.leftFunc.ToConst()!!) + right.rightFunc
if (left != null && left.op == "a" && right != null && right.op == "f+g" && right.rightFunc.ToConst() != null)
return@Function (left.leftConst + right.rightFunc.ToConst()!!) + right.leftFunc
null
},
)
/**
* Syntactic sugar for (a-f) without using Value.
* Preserves (a-f) optimization, as opposed to (f-a).
*/
operator fun Double.minus (other: Function) =
Value(this)-other
/**
* Syntactic sugar for (f-a) without using Value.
*/
operator fun Function.minus (other: Double) =
this-Value(other)
/**
* General purpose operator for f-g.
*/
operator fun Function.minus (other: Function): Function =
Function(
{x -> this(x)-other(x)},
{this.Derive()-other.Derive()},
{d -> "(${this.Describe(d)}) - (${other.Describe(d)})"},
{listOf(this, other)},
{"-"},
{mc -> {x -> mc[0](x)-mc[1](x)}},
{mc -> {mc[0].Derive()-mc[1].Derive()}},
// Optimizes (a)-(b) into (a-b) constant.
{
val left = this.ToConst()
val right = other.ToConst()
if (left != null && right != null)
return@Function left-right
null
},
{OptimizationInfo("f-g", this, Double.NaN, other, Double.NaN)},
// Optimizes (0-f) into (-f) function.
// Optimizes (f-0) into (f) function.
// Optimizes a-(b-(f)) into ((a-b)+f) function.
{
val left = this.ToInfo()
val right = other.ToInfo()
if (left != null && left.op == "a" && left.leftConst == 0.0)
return@Function -other
if (right != null && right.op == "a" && right.leftConst == 0.0)
return@Function this
if (left != null && left.op == "a" && right != null && right.op == "f-g" && right.leftFunc.ToConst() != null)
return@Function (left.leftConst - right.leftFunc.ToConst()!!) + right.rightFunc
null
},
)
/**
* General purpose operator for a*f.
*/
operator fun Double.times (other: Function): Function =
Function(
{x -> this*other(x)},
{this*(other.Derive())},
{d -> "($this) * (${other.Describe(d)})"},
{listOf(other)},
{"$this *"},
{mc -> {x -> this*mc[0](x)}},
{mc -> {this*(mc[0].Derive())}},
// Optimizes (0*f) and (f*0) into 0 constant.
// Optimizes (a)*(b) into (a*b) constant.
{
val right = other.ToConst()
if (this == 0.0 || right == 0.0)
return@Function 0.0
if (right != null)
return@Function this*right
null
},
{OptimizationInfo("a*f", Value(this), this, other, Double.NaN)},
// Optimizes (1*f) into (f) function.
// Optimizes a*(b*f) into (a*b)*f function.
{
val right = other.ToInfo()
if (this == 1.0)
return@Function other
if (right != null && right.op == "a*f")
return@Function (this * right.leftConst) * right.rightFunc
null
},
)
/**
* Syntactic sugar for (f*a) without using Value.
* Preserves (a*f) optimization, as opposed to (f*a).
*/
operator fun Function.times (other: Double) =
other*this
/**
* General purpose operator for f*g.
*/
operator fun Function.times (other: Function): Function =
Function(
{x -> this(x)*other(x)},
{this.Derive()*other+this*other.Derive()},
{d -> "(${this.Describe(d)}) * (${other.Describe(d)})"},
{listOf(this,other)},
{"*"},
{mc -> {x -> mc[0](x)*mc[1](x)}},
{mc -> {mc[0].Derive()*mc[1]+mc[0]*mc[1].Derive()}},
// Optimizes (0*f) and (f*0) into 0 constant.
// Optimizes (a)*(b) into (a*b) constant.
{
val left = this.ToConst()
val right = other.ToConst()
if (left == 0.0 || right == 0.0)
return@Function 0.0
if (left != null && right != null)
return@Function left*right
null
},
{
val left = this.ToInfo()
val right = other.ToInfo()
if (left?.op == "a")
return@Function OptimizationInfo("a*f", Value(left.leftConst), left.leftConst, other, Double.NaN)
if (right?.op == "a")
return@Function OptimizationInfo("a*f", Value(right.leftConst), right.leftConst, this, Double.NaN)
OptimizationInfo("f*g", this, Double.NaN, other, Double.NaN)
},
// Optimizes (a*f) and (f*a) into a*(f) function using Double.times(Function) operator.
{
val left = this.ToInfo()
val right = other.ToInfo()
if (left != null && left.op == "a")
return@Function left.leftConst * other
if (right != null && right.op == "a")
return@Function right.leftConst * this
null
},
)
/**
* General purpose operator for a/f.
*/
operator fun Double.div (other: Function): Function =
Function(
{x -> this/other(x)},
{this/(other.Derive().Of(1.0/other))},
{d -> "($this) / (${other.Describe(d)})"},
{listOf(other)},
{"$this / "},
{mc -> {x -> this/mc[0](x)}},
{mc -> {this/(mc[0].Derive().Of(1.0/mc[0]))}},
// Optimizes (0/f) into 0 constant.
// Optimizes (a/b) into (a/b) constant.
{
val right = other.ToConst()
if (this == 0.0)
return@Function 0.0
if (right != null)
return@Function this/right
null
},
// No optimization is achieved with this info.
{OptimizationInfo("a/f", Value(this), this, other, Double.NaN)},
// No optimization is achieved here.
{null},
)
/**
* General purpose operator for f/g.
*/
operator fun Function.div (other: Function): Function =
Function(
{x -> this(x)/other(x)},
{(this.Derive()*other-this*other.Derive())/(other*other)},
{d -> "(${this.Describe(d)}) / (${other.Describe(d)})"},
{listOf(this,other)},
{"*"},
{mc -> {x -> mc[0](x)/mc[1](x)}},
{mc -> {(mc[0].Derive()*mc[1]-mc[0]*mc[1].Derive())/(mc[1]*mc[1])}},
// Optimizes (0/f) into 0 constant.
// Optimizes (a/b) into (a/b) constant.
{
val left = this.ToConst()
val right = other.ToConst()
if (left == 0.0)
return@Function 0.0
if (left != null && right != null)
return@Function left/right
null
},
{OptimizationInfo("f/g", this, Double.NaN, other, Double.NaN)},
// Optimizes (f/a) into (1/a)*(f) function using Double.times(Function) operator.
{
val right = other.ToInfo()
if (right != null && right.op == "a")
return@Function (1.0/right.leftConst)*this
null
},
)
/**
* General purpose singleton for the X variable. Need I say more?
*/
val X: Function =
Function(
{x -> x},
{Value(1.0)},
{d -> d},
{listOf()},
{"X"},
{mc -> {x -> x}},
{mc -> {Value(1.0)}},
// No optimization is achieved here.
{null},
// No OptimizationInfo is provided because it would have to be self-referential.
{null},
// No optimization is achieved here.
{null},
)
/**
* General purpose function for creating constant values. Note that there are many sugary operators that do not require
* you to use a Value eg. 5.0*Sin will work the same as Value(5.0)*Sin.
*/
fun Value (value: Double): Function =
Function(
{x -> value},
{Value(0.0)},
{"$value"},
{listOf()},
{"$value"},
{mc -> {x -> value}},
{mc -> {Value(0.0)}},
{value},
// This OptimizationInfo is used throughout entire codebase to detect special conditions eg. (a*f) instead of more general (f*g).
{OptimizationInfo("a", Value(value), value, Value(value), value)},
{null},
)
/**
* Function for creating the (f ** n) power function, given both the base function and integer exponent.
*/
fun Function.PowerOf (exponent: Int): Function =
Function(
{x -> pow(this(x),exponent.toDouble())},
{(exponent.toDouble())*(this.PowerOf(exponent-1))*(this.Derive())},
{d -> "(${this.Describe(d)}) ** (${exponent.toDouble()})"},
{listOf(this)},
{" ** ${exponent}"},
{mc -> {x -> pow(mc[0](x),exponent.toDouble())}},
{mc -> {(exponent.toDouble())*(mc[0].PowerOf(exponent-1))*(mc[0].Derive())}},
// Optimizes (a**b) into (a**b) constant.
// Optimizes (a**0) into 1 constant.
{
val left = this.ToConst()
if (left != null)
return@Function pow(left,exponent.toDouble())
if (exponent == 0)
return@Function 1.0
null
},
{null},
// Optimizes (f**1) into (f).
// Optimizes (f**2) into (f*f).
{
if (exponent == 1)
return@Function this
if (exponent == 2)
return@Function this*this
null
},
)
/**
* Function for creating the (a ** f) exponential function, given both the floating-point base and exponent function.
*/
fun Double.ExpOf (exponent: Function): Function =
Function(
{x -> pow(this,exponent(x))},
{(this.ExpOf(exponent))*(Log.Of(Value(this)))*(exponent.Derive())},
{d -> "($this) ** (${exponent.Describe(d)})"},
{listOf(exponent)},
{"$this ** "},
{mc -> {x -> pow(this,mc[1](x))}},
{mc -> {(this.ExpOf(mc[0]))*(Log.Of(Value(this)))*(mc[0].Derive())}},
// Optimizes (0**f) into 0 constant.
// Optimizes (1**f) into 1 constant.
// Optimizes (a**b) into (a**b) constant.
{
val right = exponent.ToConst()
if (this == 0.0)
return@Function 0.0
if (this == 1.0)
return@Function 1.0
if (right != null)
return@Function pow(this,right)
null
},
{null},
// No optimization is achieved here.
{null},
)
/**
* Singleton for natural logarithm function.
*/
val Log: Function =
Function(
{x -> log(x)},
{1.0/X},
{d -> "Log($d)"},
{listOf(X)},
{"Log"},
{mc -> {x -> log(x)}},
{mc -> {1.0/mc[0]}},
// No optimization is achieved here.
{null},
{null},
// No optimization is achieved here.
{null},
)
/**
* Singleton for the sine trigonometric function.
*/
val Sin: Function =
Function(
{x -> sin(x)},
{Cos},
{d -> "Sin($d)"},
{listOf(X)},
{"Sin"},
{mc -> {x -> sin(x)}},
{mc -> {Cos}},
// No optimization is achieved here.
{null},
{null},
// No optimization is achieved here.
{null},
)
/**
* Singleton for the cosine trigonometric function.
*/
val Cos: Function =
Function(
{x -> cos(x)},
{-Sin},
{d -> "Cos($d)"},
{listOf(X)},
{"Cos"},
{mc -> {x -> cos(x)}},
{mc -> {-Sin}},
// No optimization is achieved here.
{null},
{null},
// No optimization is achieved here.
{null},
)
/**
* The most impressive operator of them all, the nesting method.
*/
fun Function.Of (other: Function): Function =
Function(
{x -> this(other(x))},
{this.Derive().Of(other)*(other.Derive())},
{d -> this.Describe(other.Describe(d)) },
{listOf(this,other)},
{"of"},
{mc -> {x -> mc[0](mc[1](x))}},
{mc -> {mc[0].Derive().Of(mc[1])*(mc[1].Derive())}},
// Optimizes (a.Of(f)) into (a) constant. Rarely needed.
// Optimizes (f.Of(b)) into (f.Of(b)) constant, regardless of f.
{
val left = this.ToConst()
val right = other.ToConst()
if (left != null)
return@Function left
if (right != null)
return@Function this(right)
null
},
{null},
// No optimization is achieved here.
{null},
)
// ----------------------------------------------------------------------------
// Core functionality of function graphs
/**
* The FunctionGraph class represents both an original function and several of its derivatives. The difference between
* a FunctionGraph and a list of Functions is that a graph can show you overlapping subfunctions.
*
* Note to users:
* You can access the derivatives:
* | val fg = FunctionGraph(Sin, 4)
* | fg.DerivativeFunctions[0] returns a Function, the original function.
* | fg.DerivativeFunctions[1] returns a Function, the first derivative.
* You can optimize the functions:
* | fg.optimized() returns a new FunctionGraph
* You can export the functions graph of subfunctions into SVG:
* | fg.exportedGraphsSvgs("filename") returns itself
* You can export the functions combined plot into SVG:
* | fg.exportedPlotSvg("filename") returns itself
*/
class FunctionGraph (originalFunc: Function, derivatives: Int) {
/**
* Used to store the original function and several of its derivatives.
*/
var DerivativeFunctions: MutableList<Function> = originalFunc.deriveMany(derivatives).toMutableList()
/**
* Used internally when exporting and useful during debugging.
*/
val ID: Int = nextID++
/**
* Secondary constructor, used internally by the optimized method.
*/
constructor (DerivativeFunctions: MutableList<Function>) : this(Value(Double.NaN), 0) {
this.DerivativeFunctions = DerivativeFunctions
}
/**
* Returns a new FunctionGraph that contains equivalent derivative functions, but with less nodes. The process is
* both deterministic (same functions will always be optimized into same equivalent functions) and adaptive (the
* optimizer will repeat its phases until they stop giving better results).
*/
fun optimized(): FunctionGraph {
var DerivativeFunctions = this.DerivativeFunctions.toMutableList()
// Phase 1: full constant expressions turn into constants.
for (derivative in DerivativeFunctions.indices) {
fun traverse (func: Function): Function {
var mutableChildren = func.Children().toMutableList()
for (childIndex in mutableChildren.indices) {
mutableChildren[childIndex] = traverse(mutableChildren[childIndex])
}
val r = func.possiblyRebuild(mutableChildren)
val c = r.ToConst()
return if (c != null) Value(c) else r
}
DerivativeFunctions[derivative] = traverse(DerivativeFunctions[derivative])
}
while (true) {
var OldDerivativeFunctions = DerivativeFunctions.toMutableList()
// Phase 2: partial constant expressions turn into (better) functions.
for (derivative in DerivativeFunctions.indices) {
fun traverse (func: Function): Function {
var mutableChildren = func.Children().toMutableList()
for (childIndex in mutableChildren.indices) {
mutableChildren[childIndex] = traverse(mutableChildren[childIndex])
}
var r = func.possiblyRebuild(mutableChildren)
return r.PossiblyOptimized() ?: r
}
DerivativeFunctions[derivative] = traverse(DerivativeFunctions[derivative])
}
if (DerivativeFunctions.indices.all({ i -> DerivativeFunctions[i] === OldDerivativeFunctions[i] }))
break
}
// Phase 3: deduplication of paths in the dag of subfunctions.
var deduplicatedPaths = mutableMapOf<String,Function>()
for (derivative in DerivativeFunctions.indices) {
fun traverse (func: Function): Function {
if (deduplicatedPaths.containsKey(func.Description)) {
return deduplicatedPaths[func.Description]!!
} else {
deduplicatedPaths[func.Description] = func
}
var mutableChildren = func.Children().toMutableList()
for (childIndex in mutableChildren.indices) {
mutableChildren[childIndex] = traverse(mutableChildren[childIndex])
val childDescription = mutableChildren[childIndex].Description
if (deduplicatedPaths.containsKey(childDescription)) {
mutableChildren[childIndex] = deduplicatedPaths[childDescription]!!
} else {
deduplicatedPaths[childDescription] = mutableChildren[childIndex]
}
}
return func.possiblyRebuild(mutableChildren)
}
DerivativeFunctions[derivative] = traverse(DerivativeFunctions[derivative])
}
return FunctionGraph(DerivativeFunctions)
}
/**
* Exports the graph containing the functions into an SVG file, and optionally opens it in the firefox browser.
* Returns itself so you can do chaining.
*/
fun exportedGraphsSvgs (outputFilename: String, openInBrowser: Boolean = false): FunctionGraph {
require(outputFilename.isNotEmpty()) { "Please provide a filename, anything." }
require(!outputFilename.contains('.')) { "Please provide a filename (without extension) for .dot and .svg files." }
val originalFuncDescription = DerivativeFunctions[0].Description
var dotTasks = mutableListOf<Process>()
for (derivative in 0..DerivativeFunctions.lastIndex) {
// Phase 1
var deduplicatedNodes = hashMapOf<Int,Function>()
var deduplicatedEdges = hashSetOf<Pair<Int,Int>>()
var nodeDuplicateSubnodesCounts = hashMapOf<Int,Int>()
fun traverseNodesAndEdges (func: Function): Int {
deduplicatedNodes[func.ID] = func
var duplicateSubnodes = func.Children().size
for (child in func.Children()) {
deduplicatedEdges.add(Pair(func.ID, child.ID))
duplicateSubnodes += traverseNodesAndEdges(child)
}
nodeDuplicateSubnodesCounts[func.ID] = duplicateSubnodes
return duplicateSubnodes
}
for (subderivative in 0..derivative) {
traverseNodesAndEdges(DerivativeFunctions[subderivative])
}
// Phase 2
var nodeUniqueSubnodeSets = hashMapOf<Int,HashSet<Int>>()
fun traverseUnique (uniqueNodes: HashSet<Int>, func: Function) {
if (uniqueNodes.add(func.ID)) {
for (child in func.Children()) {
traverseUnique(uniqueNodes, child)
}
}
}
for (subderivative in 0..derivative) {
val root = DerivativeFunctions[subderivative]
var uniqueNodes = hashSetOf<Int>()
nodeUniqueSubnodeSets[root.ID] = uniqueNodes
traverseUnique(uniqueNodes, root)
}
// Phase 3
var sb = StringBuilder()
sb.append("""
digraph {
graph [
layout = dot
tooltip = "Hey! Notice that red nodes are clickable and \n tooltips contain additional information!"
]
node [
style = filled
]
""")
for (subderivative in 0..derivative) {
val subderivativefid = DerivativeFunctions[subderivative].ID
sb.append("""
function${subderivative} [
label = "${if (subderivative == 0) "$originalFuncDescription \n original function" else "$originalFuncDescription \n ${withEnglishSuffix(subderivative)} derivative"}"
tooltip = "${if (subderivative == 0) "original function" else "${withEnglishSuffix(subderivative)} derivative function"} \n contains ${nodeDuplicateSubnodesCounts[subderivativefid]!!+1} duplicate subfunctions \n contains ${nodeUniqueSubnodeSets[subderivativefid]!!.size} unique subfunctions \n (and is clickable)"
URL = "${outputFilename}-${subderivative}.svg"
color = red
style = "rounded,filled"
shape = box
]
function${subderivative} -> f${subderivativefid}
""")
}
for (node in deduplicatedNodes.values) {
sb.append("""
f${node.ID} [
label="${node.Label()}"
tooltip="Function ID=${node.ID} \n contains ${nodeDuplicateSubnodesCounts[node.ID]} duplicate subfunctions"
color=${if (node.Children().isNotEmpty()) "lightblue" else "lightgreen"}
]
""")
}
for (edge in deduplicatedEdges) {
sb.append("""
f${edge.first} -> f${edge.second} [
tooltip="Function ID=${edge.first} depends on function ID=${edge.second}"
]
""")