-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMultipleScales.mpl
More file actions
executable file
·2693 lines (2123 loc) · 85.7 KB
/
MultipleScales.mpl
File metadata and controls
executable file
·2693 lines (2123 loc) · 85.7 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
# MODULE MultipleScales
#
# DESCRIPTION
# - A package used for solving systems of nonlinear partial differential
# equations using the Method of Multiple Scales
#
# Written for Maple 2018
# Thomas Zdyrski (tzdyrski@ucsd.edu)
# 12 March 2018
MultipleScales := module()
description "Solves a system of nonlinear pdes using the Method of Multiple Scales":
option package:
local
pdsolve,
AppendSols,
BothSides,
ComplementarySols,
DedupNamesFuncs,
DedupRanking,
DependentVars,
DerivFuncs,
Diff2Diffop,
Diffop2Diff,
ExtractCoeffs,
EvalSolutions,
HermitianInnerProduct,
IndependentVars,
MapRhs,
RealEquations,
RetainDerivs,
SolveEquation,
SolveEquations,
SolveICs,
SolveOrder,
SolvingVars,
ToAnalytic,
TrigCoeff,
TrigExpand,
UniqueName,
counter,
UnapplyFuncs:
global
`inttrans/expandc`:
export
BasisCoeff,
CombineSolutions,
CompleteSystem,
GenEqns,
IndICs,
IndFunc,
IndFuncToName,
IndName,
IndGen,
IndNameToFunc,
SolveSystem,
TruncateExpr,
UnitTests,
VerifyAndCombine,
VerifySolution:
# Bug Fix submitted by vv at
# www.mapleprimes.com/questions/224385-Bug-Corrected-In-Inttranshilbert
`inttrans/expandc` := proc(expr, t)
local xpr, j, econst, op1, op2;
xpr := expr;
for j in indets(xpr,specfunc(`+`,exp)) do
econst := select(type,op(j),('freeof')(t));
if 0 < nops(econst) and econst <> 0 then
xpr := subs(j = ('exp')(econst)*combine(j/('exp')(econst),exp),xpr)
end if
end do;
for j in indets(xpr,{('cos')(linear(t)), ('sin')(linear(t))}) do
if type(op(j),`+`) then
op1:=select(has, op(j),t);
op2:=op(j)-op1;
if op(0,j) = sin then
xpr := subs(j = cos(op2)*sin(op1)+sin(op2)*cos(op1),xpr)
else
xpr := subs(j = cos(op1)*cos(op2)-sin(op1)*sin(op2),xpr)
end if
end if
end do;
return xpr
end proc:
`pdsolve` := proc(eqn_sys)
local
dep_vars,
diff_ind_vars,
doesnt_depend_on_diff_ind_vars,
sols:
description "Overloads pdsolve to freeze all unknown functions that don't depend on the differentiated variables. This helps pdsolve solve equations it otherwise couldn't.":
# Determine dependent variables to solve for
if numelems({_rest}) >= 1 and type(_rest[1][1], {set,list}) then
dep_vars := _rest[1][1]:
else
dep_vars := {}:
end if:
# Determine independent variables being differentiated
convert(eqn_sys, Diff):
indets(%, specop(`Diff`)):
diff_ind_vars := map2(op,2,%):
if diff_ind_vars = {} then
# This isn't a differential equation; don't freeze variables
sols := {solve(eqn_sys, dep_vars)}:
# Check to make sure we found a solution
if sols = {} then
if _SolutionsMayBeLost=true then
# Solve was just unable to solve system
print("Warning: Unable to solve system; some solutions may be lost"):
return NULL:
else
# System was inconsistent
return NULL:
end if:
end if:
return op(sols):
end if:
# Define type for unknown functions that don't depend on any of the
# variables in diff_ind_vars
doesnt_depend_on_diff_ind_vars :=
And(
unknown,
function,
satisfies(f -> {op(f)} intersect diff_ind_vars = {})
):
# Check that system is consistent
if numelems(eqn_sys) > 1 then
frontend(PDEtools:-ConsistencyTest,[eqn_sys],
[{`+`,`*`,`=`,`<>`,`<`,`<=`, Not(doesnt_depend_on_diff_ind_vars)}],
dep_vars):
if not % then:
return NULL:
end if:
end if:
# Only freeze those variables that don't depend on diff_ind_vars
sols := {frontend(':-pdsolve', [eqn_sys],
[{`+`,`*`,`=`,`<>`,`<`,`<=`, Not(doesnt_depend_on_diff_ind_vars)}],
_rest)}:
# Check to make sure we found a solution
if sols = {} then
# Solve was just unable to solve system
print("Unable to solve system: solutions may be lost"):
return NULL:
end if:
return op(sols):
end proc:
AppendSols := proc(
existing_sols::Or(set,list)({name,function}=algebraic),
new_sols::Or(set,list)({name,function}=algebraic),
$)
description "Simply append new_sols after existing_sols":
# Append `new_sols` to `existing_sols`
[op(existing_sols), op(new_sols)]:
return %:
end proc:
BasisCoeff := proc(
expr::algebraic,
basis_func::{function,constant},
int_var::name:=NULL,
{limits::algebraic:=infinity,
regulate::truefalse:=true
},
$)
local
new_basis_func,
new_int_var,
new_expr,
integrand,
regulator,
normaliz:
description "Compute the coefficients of a basis function (or constant) `basis_func` in and expression `expr` using the inner product of the two functions. If `basis_func` depends on multiple variables, `int_var` must be the name of the variable over which to integrate. Integration limits are specified by `limits` (default: infinity) such that integration takes place from -`limits` to +`limits`. If regulate is true (default true), the integration will be regulated by a Gaussian regulator; may be slower.":
# Define new variables that we can modify
new_basis_func := basis_func:
new_int_var := int_var:
new_expr := expr:
# Check if basis_func is a constant
if type(basis_func, constant) and new_int_var = NULL then
error "Must specify variable of integration `var_integration` if basis function is a constant.":
end if:
if new_int_var = NULL then
# Determine integration variable as argument of basis_func
new_int_var := op(basis_func);
# It's possible the argument of basis_func is of the form c*f(x)
# with c a constant; we only want f(x)
indets(new_int_var, function):
if % = {} then
# It's possible the argument of basis_func is of the form c*x with
# c a constant; we only want x
indets(new_int_var, name):
end if:
# Convert set to series
new_int_var := op(%):
if numelems({%}) <> 1 then
# Could not determine variable of integration
error "Could not determine variable of integration; if either function depends on multiple variable, `int_var` must be passed":
end if:
end if:
# Use HermitianInnerProduct to calculate dot product
HermitianInnerProduct(Vector([expr]),Vector([basis_func]),new_int_var,':-limits'=limits):
return %:
end proc:
BothSides := proc(
Procedure::procedure,
eqns::Or(set,list)({`=`,`<>`})
)
description "Map `Procedure` to both sides of set\list `eqns`; pass any additioanl arguments to `Procedure`.":
map2(map, Procedure, eqns, _rest):
return %:
end proc:
ComplementarySols := proc(
sols::{set,list}({name,function}=algebraic),
existing_indets::{set,list}({name,function}),
complementary_scales::{set,list}([{name,function},list(name)]),
prev_sols::{set,list}({name,function}=algebraic),
$)
local
comp_name,
comp_sol,
new_indet,
new_sols,
new_existing_indets:
description "Given a set/list of solutions `sols` of the form {name,function}=algebraic, find any new indets not present in the set/list `existing_indets`. Then, for each name/function in `complementary_scales`, check if any of the new indets are present in that term's solution. If so, replace the indet with a new function with restricted operators as supplied by the second element of the complementary_scales list. The set/list of previous solutions `prev_sols` is used to determine which new indets are complementary solutions and which are not.":
new_sols:= sols:
# "Complementary solutions" are a bit tricky to define for PDEs. We
# will only consider new variables appearing solely in solutions for
# new variables as possible complementary solutions. Eg if a solution
# contains a term like `_D32(t__1,t__2) = _D33(t__2)`, we *won't*
# consider `_D33` at complementary solution because `_D32` appeared in
# previous orders' solutions. Another eg: if at order `n` the solution
# contains `y_n(t) = _D1*sin(t)`, we will consider
# `_D1` a complementary solution (if it doesn't appear in the solution
# of any other terms that appeared in previous solutions) because
# `y_n(t)` has not appeared in any previous solutions. One more eg: if
# previous solutions were `_D12(t__0,t__1,t__2) = _D13(t__1,t__2)` and
# current solution is `y__3(t)=_D14*sin(t), phi_3(t)=_D15*cos(t),
# _D13(t__1,t__2)=_D16(t__2)`, then `_D14` and `_D15` would be
# complementary solutions since neither `y__3(t)` nor `phi__3(t)`
# appeared in previous solutions, but `_D16(t__2)` would not be a
# compementary solution since `_D13` appeared in a previous solution.
# This criterion attempts to capture the intuitive concept of
# "complementary solutions" formally.
# Add terms appearing as solutions to previous solutions to set of
# existing_indets
eval(prev_sols, sols):
indets(%, assignable):
new_existing_indets := % union convert(existing_indets,set):
# for each complementary name
unassign('comp_name'):
for comp_sol in complementary_scales do
# Get solution for comp_name
eval(op(1,comp_sol), new_sols):
# Get new indets
indets(%, 'assignable') minus new_existing_indets:
for new_indet in % do
# Define new operands
select(member,[op(new_indet)], op(2,comp_sol)):
# If no operands, make constant
if numelems(%) = 0 then
new_sols := eval(new_sols, op(0,new_indet) =
unapply(UniqueName(),op(new_indet))):
else
new_sols := eval(new_sols, op(0,new_indet) =
unapply(UniqueName()(op(%)),op(new_indet))):
end if:
end do:
end do:
return new_sols:
end proc:
DedupNamesFuncs := proc(
funcs::Or(set,list)({name,function}),
{ind_vars::Or(set,list)({name,function}):={}},
$)
description "Given a set/list of functions, remove duplicates; optionally, remove functions with arguments not included in the set/list ind_vars.":
# Convert to list
convert(funcs, list):
if numelems(ind_vars) > 0 then
# Remove functions depending on arbitrary independent variables
remove((x,set) -> type(x,function) and not({op(x)} subset set ), %, convert(ind_vars,set)):
end if:
# Remove duplicate functions
ListTools:-MakeUnique(%, 1,
proc(x,y) type(x,'function') and type(y, 'function') and
evalb(op(0,x) = op(0,y)) end proc):
# Remove duplicate names
ListTools:-MakeUnique(%, 1,
proc(x,y) type(x,'name') and type(y, 'name') and
evalb(x = y) end proc):
# Convert back to original type
convert(%, whattype(funcs)):
end proc:
DedupRanking := proc(
ranking::list(Or(set,list)),
$)
local
rev_ranking,
new_ranking,
j:
description "Remove duplicates from lower order ranking.":
# Reverse ranking
rev_ranking := ListTools:-Reverse(ranking):
new_ranking := []:
unassign('j'):
# Remove duplicates from lower order in the rev_ranking
for j from 1 to numelems(rev_ranking) do
# Remove indets from current element that appear in previous
# elements
remove(member, rev_ranking[j], `union`(op([1..j-1], map(convert,
rev_ranking, set)))):
if % <> {} and % <> [] then
# Append to new_rev_ranking
new_ranking := [op(new_ranking), %]:
end if:
end do:
# Undo ranking reversal
ListTools:-Reverse(new_ranking):
return %:
end proc:
DependentVars := proc(
exprs::Or(set,list),
{ind_vars::Or(set,list)({name,function}):={}},
$)
local
arguments:
description "Return a set of variables which appear as functions in set of equations or inequations `eqn_sys`. If `ind_vars` is passed, only functions depending on a subset of `ind_var` will be returned.":
# Select all functions
indets(convert(exprs,Diff),And(assignable,function,unknown)):
# Convert to list
convert(%, list):
# Remove duplicate functions
DedupNamesFuncs(%, ':-ind_vars'=ind_vars):
# Convert to set
convert(%, set):
return %:
end proc:
EvalSolutions := proc(
exprs,
solutions::Or(set,list)({name,function}=algebraic),
only_derivs::Or(set,list)({name,function}):={},
$)
local
eval_fcns,
exclude_fcns:
description "Evaluates `exprs` using `solutions`; any functions in `only_derivatives` will only have their derivatives evaluated, but their value will not be substituted otherwise":
# Remove any `only_derivs` from `solutions`
exclude_fcns, eval_fcns := selectremove((expr,arg) ->
member(lhs(expr),arg), convert(solutions,D), only_derivs):
# Unapply solutions to get them in functional form
eval_fcns := UnapplyFuncs(eval_fcns):
exclude_fcns := UnapplyFuncs(exclude_fcns):
# Insert `solutions` to be evaluated
eval['recurse'](convert(exprs,D), eval_fcns):
# Evaluate derivatives
value(%):
# Evaluate derivatives of `only_derivs` functions
evalindets(convert(%,D),
typefunc(
typefunc(
{map(identical,map2(op,0,convert(only_derivs,set)))},
Or(
identical(D),
specindex(posint,D),
And(
typefunc(
identical(`@@`)
),
anyfunc(identical(D), posint)
)
)
)
), eval['recurse'], [op(exclude_fcns), op(eval_fcns)]):
return %:
end proc:
DerivFuncs := proc(
eqns::Or(set,list)(equation),
deriv_var::name,
$)
local
deriv_wrt_var:
description "Given a set/list of equations `eqns`, return a set/list of sets of all functions that appear in `eqns` and are differentiated (at least once) with respect to the name `deriv_var`.":
# Define depends_on_deriv_var
deriv_wrt_var := And(
typefunc(identical(diff)),
anyfunc(function,identical(deriv_var))
):
# Expand exprs to ensure derivatives appear as monomials
expand(eqns):
# Convert to diff-notation
convert(%, diff):
# Get all derivatives wrt deriv_var
map(indets, %, deriv_wrt_var):
# Extract functions from derivatives
map(eq -> indets(`union`(eq),assignable(function)), %):
return %:
end proc:
Diff2Diffop := proc(
eqns::{set,list}({equation,algebraic}),
{vars::{set,list}:={},
funcs::{set,list}:={}
},
$)
local
ind_vars,
repl_funcs,
replaced_eqns,
prev_replaced_eqns,
diff_vars,
var:
description "Given a set/list of equations or expressions `eqns`, returns the equations with all derivative replaced by symbols Dname with name representing the differentiating variable. Also returns a set of equations of the form Dname=name for each differentiation variable. By default, all independent variables are used; if only some should be transformed, pass a set/list `vars` of differentiation variable names to transform. If a set/list of functions `func` is passed (eg {f(x),g(t,x)}), only derivatves of those functions will be transformed.":
if vars <> {} then
ind_vars := vars:
else
# Get list of variables
ind_vars := IndependentVars(eqns):
end if:
# Specify which functions to replace
if numelems(funcs) > 0 then
repl_funcs := Or(op(map(identical,funcs))):
else
repl_funcs := function:
end if:
# Create empty variables to store results
replaced_eqns := expand(convert(eqns,diff)):
prev_replaced_eqns := replaced_eqns:
diff_vars := {}:
# For each independent variable, replace
for var in ind_vars do
# Loop until applying the transform no longer changes anything (this
# is necessary to handle multiple derivatives
do
# Replace derivative
replaced_eqns := evalindets(replaced_eqns,
And(
anyop(repl_funcs,identical(var)),
typefunc(identical('diff'))
),
f -> cat(`D`,var)*op(1,f)):
# Check if the result has stopped changing
if prev_replaced_eqns = replaced_eqns then
break:
else
prev_replaced_eqns := replaced_eqns:
end if:
end do:
# Add differential variable to diff_vars
diff_vars := diff_vars union {cat(`D`,var) = var}:
end do:
return replaced_eqns, diff_vars:
end proc:
Diffop2Diff := proc(
eqns::{set,list}({equation,algebraic}),
diff_vars::{set,list}(name=name),
$)
local
replaced_eqns,
prev_replaced_eqns,
diff_repl:
description "Given a set/list of equations or expressions `eqns` and a set/list of differentiation variables `diff_vars` of the form Dname=name, replaces each instance of Dname with diff( ,name).":
# Note: it is important here that, when converting back to
# derivatives, we take the derivative of *all* terms.
# For instance, exp(t)*diff(f(t),t) converts to Dt*exp(t)*f(t) and converts
# back to diff(exp(t)*f(t),t); this may seem wrong at first, but
# Diffop2Diff is used when taking transposes. the transpose of
# exp(t)*diff(<something>,x) is in fact -diff(exp(t)*<something>,x),
# as can be seen by integrating by parts.
# Create empty variables to store results
replaced_eqns := expand(eqns):
prev_replaced_eqns := replaced_eqns:
# For each independent variable, replace
for diff_repl in diff_vars do
# Loop until applying the transform no longer changes anything (this
# is necessary since powers (eg Dname^3) will only trigger this
# condition once
do
# Replace derivative operators
replaced_eqns := evalindets(replaced_eqns,
And(
satisfies(f -> has(f,lhs(diff_repl))),
specop(`*`)
),
f -> diff(f/lhs(diff_repl),rhs(diff_repl)));
# Check if the result has stopped changing
if prev_replaced_eqns = replaced_eqns then
break:
else
prev_replaced_eqns := replaced_eqns:
end if:
end do:
end do:
return replaced_eqns:
end proc:
ExtractCoeffs := proc (
exprs::Or(set,list)(equation),
orth_basis::Or(set,list)(function),
{exclude_vars::Or(set,list):={},
extract_type::{"trig","innerProduct"}:="trig",
limits::algebraic:=Pi},
$)
description "Given a set or list of functions `orth_basis`, extract the components of each basis function from each element of set/list `exprs`. If `extract_type` is 'trig' (default), then `orth_basis` should be trigonometric functions and `exclude_vars` should consist of the arguments to `orth_basis`. If `extract_type` is `innerProduct`, a hermitian inner product is used to calculate the coefficients of `orth_basis` and `limits` (default Pi) are the +/- limits of integration; note: `innerProduct` is much slower than `trig`.":
# Isolate coefficients of basis functions
if extract_type = "trig" then
map[3](op@BothSides, TrigCoeff, exprs, orth_basis, exclude_vars):
else
map[3](op@BothSides, BasisCoeff, exprs, orth_basis, ':-limits'=limits):
end if:
return %:
end proc:
HermitianInnerProduct := proc(
bra::Vector,
ket::Vector,
int_var::name,
{limits::algebraic:=infinity,
op_list::Or(set,list)(name=name):={}},
$)
local
additional_op_list,
identical_diffop,
contains_diffop,
assumptions,
new_bra:
description "Given a two Vectors `bra` (possibly containing differential operators) and `ket` (not containing differential operators), calculate the hermitian inner product by integrating over variable `int_var`. If algebraic `limits` is provided, then the integral has limits of -limits..limits. If a set/list `op_list` of differentiation variables `diff_vars` of the form Dname=name, replaces each instance of Dname with diff( ,name).":
# Get list of all dependent and independent variables (so we can
# assume real) except derivatives
`union`(indets(convert(bra,list),assignable),
indets(convert(ket,list),assignable)):
% minus convert(map(lhs,op_list),set):
# Assume real
map(x -> x::real, %):
# Assume derivatives are anti-Hermitian
assumptions := % union map(x -> x::imaginary, map(lhs,op_list)):
## We want to ensure all derivatives are conjugated correctly.
# Convert derivatives to additional_op_list
new_bra, additional_op_list := Diff2Diffop(convert(bra,list)):
# Add new additional_op_list to assumptions
assumptions union map(x -> x::imaginary, map(lhs,additional_op_list)):
# Take conjugate of bra to convert vector to 1-form
conjugate(new_bra) assuming op(%):
# Convert back to derivatives
new_bra := Vector(Diffop2Diff(%, additional_op_list)):
# Element-wise multiply each element of conjugate(ket) and bra
# without taking the conjugate (we already did that)
LinearAlgebra:-DotProduct(new_bra,ket, conjugate=false):
# Check if there are diff_ops
if numelems(op_list) <> 0 then
# Generate type containing diffops
identical_diffop := Or(op((map(identical, map(lhs,op_list))))):
contains_diffop := satisfies(x -> hastype(x, identical_diffop)):
# Freeze all non-diffop terms
expand(new_bra):
new_bra := map2(map, evalindets, %, And(Not(contains_diffop),
function), x->freeze(x)):
# Element-wise multiply each element of conjugate(ket) and bra
# without taking the conjugate (we already did that)
LinearAlgebra:-DotProduct(new_bra,ket, conjugate=false):
# Convert back to diff-form
Diffop2Diff({%}, op_list)[1]:
# Expand
expand(%):
# Thaw
thaw(%):
do
# Loop because it's possible frozen terms contain other frozen terms
thaw(%):
if % = %% then
break:
end if:
end do:
end if:
# Convert trig to exp to help integration
evalindets(%, And(function,satisfies(f -> has(f,int_var))), convert,
exp) assuming op(assumptions);
expand(%):
# Replace and Dirac with Dirac*lim so that they don't vanish when
# taking int / lim
eval(%, Dirac= (proc() lim*Dirac(args) end proc));
# Calculate inner product
int(%,int_var=-lim..lim):
# Take integration limits to `limits` and divide by `limits`
limit(%/lim,lim=limits) assuming real:
if has(%, int) then
print("Warning: Integral in HermitianInnerProduct couldn't fully evaluate; some solutions may be lost."):
return false:
end if:
return %:
end proc:
IndependentVars := proc(
exprs::Or(set,list),
$)
local
arguments:
description "Return a set of variables which appear as parameters of functions in set of equations or inequations `eqn_sys`":
# Select all functions
indets(exprs,function):
# It's possible we have functions of functions of ... of arguments;
# recursively select arguments the list of arguments doesn't change
arguments := %:
while 1 = 1 do
map(op, arguments):
if % = arguments then
arguments := %:
break:
end if:
arguments := %:
end do:
# It's possible we have some constants now; remove them
indets(%, assignable):
return %:
end proc:
MapRhs := proc (
fcn::Or(procedure,name),
equn::Or(equation,{set,list}(equation))
)
local
rest_args:
description "Maps function `fcn` to the right-hand-side of `equn`":
# It seems the special variable `_rest` can't be accessed inside a
# `map` command; instead, store the result to a temporary variable
# `rest_args` that can be accessed with `map`
rest_args := _rest:
map(expr -> lhs(expr) = fcn(rhs(expr), rest_args), equn):
end proc:
SolveOrder := proc(
PDEs::{set,list}(Or(`=`, `<>`)),
solving_order::integer,
mult_scales::list(name),
{const::Or(set,list)(name):={},
prev_sols::Or(set,list)({name,function}=algebraic):={},
exclude_vars::Or(set,list)({name,function}):={},
ind_vars::Or(set,list)({name,function}):={},
constraints::Or(set,list)({`=`({name,function},constant),`<>`({name,function},constant),
`<`({name,function},constant),`>`({name,function},constant),
`<=`({name,function},constant),`>=`({name,function},constant)}):={},
extract_type::{"trig","innerProduct"}:="trig",
secular_allowed::Or(set,list)(function):={},
complementary_scales::{set,list}([{name,function},list(name)]):={}},
$)
local
eval_pdes,
diff_funcs,
op_pdes,
op_list,
lin_pdes,
nonlin_pdes,
hom_sols,
new_unknowns,
compatibility,
term,
zero_eigenvector,
compat_sols,
solving_system,
existing_indets,
hom_terms,
inhom_terms,
unknowns,
hom_eqns,
null_space_vectors,
new_ind_vars,
HomInhomTerms,
ConjTransHomEqns,
NullSpaceVecs,
GenAndSolveSys:
description "Solve a set\list of partial differential equations `PDEs` using a set\list of previous solutions `prev_sols` of the form `function(args) = expression`. `solving_order` denotes which order in the perturbative parameter `PDEs` satisfies. `mult_scales` is a list of names of independent variables; secular terms in these variables will be avoided (in order) unless the term becoming secular appears in `secular_allowed`. `const` is a set of constants that should not be solved for (arbitrary constants of the problem). `exclude_vars` are functions/variables whose values should not be substituted into the answer (though derivative of these quantities will still be evaluated). Additionally, `constraints` are any additional equation/inequalities of the form 'function/symbol =/</>/<=/>=/<> constant'. `extract_type` is passed to ExtractCoeffs; see ExtractCoeffs for a description. If `ind_vars` is supplied, only functions of these variables will be considered. If a set/list of lists `complementary_scales` of the form [{name,function},[names]] is supplied, then any function appearing in the first element (either a name or function) will only have homogenous terms depending on the variables `names`. This is useful for restricting higher-order terms from having too many free parameters.":
HomInhomTerms := proc(
eqns::Or(set,list)(equation),
funcs::Or(set,list)(function),
$)
local
unique_const,
contains_funcs,
hom_terms,
inhom_terms,
op_eqns,
common_factor:
description "Given a set/list of differential equations `eqns` and a set/list of functions `funcs`, return a set/list of the terms depending on `funcs` followed by a set/list of the terms that don't.":
# Hack-y: generate a random constant to add to each equation so we can
# guarantee every equation is a sum and not a monomial; add two
# constants together in case the equation was 0=0, we need to to still
# get a sum
UniqueName():
unique_const := % + UniqueName():
# Define proc that returns true for terms linear in funcs after
# converting all diffs to diff operators; also return true for
# independent variables
contains_funcs :=f -> hastype(
f, Or(op(map(identical, funcs)))):
# Move all terms to the same side
map(x->lhs(x)-rhs(x), eqns):
# Make sure we have a sum of terms
expand(%):
map(`+`, %, unique_const):
# Convert to diff
convert(%, diff):
# Separate terms linear in funcs
map(f -> [selectremove(contains_funcs, f)], %):
hom_terms, inhom_terms := map2(op,1,%), map2(op,2,%):
inhom_terms := map(`-`, inhom_terms, unique_const):
return hom_terms, inhom_terms:
end proc:
ConjTransHomEqns := proc(
hom_eqns::list(algebraic = 0),
funcs::Or(set,list)(function),
$)
local
op_eqns,
op_list,
diffop_names,
linear_op,
nonlinear_op,
null_space_ind_vars,
null_space_vector,
hom_eq,
identical_diffop,
contains_diffop,
contains_null_vector:
description "Must pass a list of homogenous equations (algebraic = 0) that are linear in all the functions in the set/list funcs. Returns a list of functions where the linear operator has been replaced by its conjugate transpose and applied to a new set of functions; the second returned parameter is the list of these new functions.":
# Convert equations to diff form
op_eqns, op_list := Diff2Diffop(hom_eqns):
# Get list of diffops
diffop_names := map(lhs,op_list):
# Generate type containing diffops
identical_diffop := Or(op((map(identical, diffop_names)))):
contains_diffop := satisfies(x -> hastype(x, identical_diffop)):
# Convert to a matrix equation
linear_op, nonlinear_op := LinearAlgebra:-GenerateMatrix(op_eqns, funcs):
if not LinearAlgebra:-Equal(nonlinear_op,
Vector(LinearAlgebra:-Dimension(nonlinear_op),fill=0)) then
error(cat("Nonlinearity in order-",solving_order,", ", mult_scales[1],
"-derivative equation: ", op_eqns, "\nThis is not yet implemented.")):
end if:
# Generate test functions to determine null space of Dagger(lin_pdes)
null_space_ind_vars := IndependentVars(hom_eqns) intersect ind_vars:
unassign('j'):
null_space_vector := [seq(UniqueName()(op(null_space_ind_vars)),
j=1..LinearAlgebra:-RowDimension(linear_op))]:
# Create type
contains_null_vector := Or(op(map(identical,null_space_vector))):
# Assume all quantites (except derivatives) are real
indets(linear_op,assignable) union indets(null_space_vector,assignable):
% minus diffop_names:
map(x -> x::real, %):
# Assume derivatives are anti-Hermitian
% union map(x -> x::imaginary, diffop_names):
# Regenerate linear, homogenous part assuming real
Physics:-Dagger(linear_op).Vector(null_space_vector) assuming op(%):
convert(%, list):
# Convert back to diff-form
Diffop2Diff(%, op_list):
# Set equal to zero
map(x -> x=0, %):
return %, null_space_vector:
end proc:
NullSpaceVecs := proc(
hom_eqns::Or(set,list)(algebraic = 0),
funcs::Or(set,list)(function),
diff_var::name,
{const::Or(set,list)(name):={},
ind_vars::Or(set,list)({name,function}):={}},
$)
local
trans_eqns,
trans_funcs,
trans_sols,
trans_sol,
null_vectors,
all_null_vectors,
op_eqns,
op_list,
op_sols,
dname,
diffop,
max_degree,
ranking,
j,
replacements,
lim:
description "Given a set/list of homogenous differential equations and a set/list of functions, returns a set of lists representing a basis for the null space of the conjugate of the differntial equation's operator. A name `diffvar` should be passed to indicate the variable wrt to which derivatives are being considered. Sets/lists of names (`const`) and names/functions (`ind_vars`) are passed to SolveEquations when calculating the null space. If any null vectors depend on diff_var, they will be replaced by a Dirac delta function Dirac(diff_var - diff_var__dummy).":
# Check that our homogenous equation system isn't just an array of
# 0=0.
if convert(hom_eqns,set) = {0=0} then
return {}:
end if:
# Take conjugate transpose of linear operator
trans_eqns, trans_funcs := ConjTransHomEqns(hom_eqns, funcs):
# Rank solving_vars so that terms with more derivatives are solve
# first; we want solutions like _D1(t) = diff(_D2(t),t), not _D2(t) =
# int(_D1(t),t)
op_eqns, op_list := Diff2Diffop(trans_eqns, ':-funcs'=trans_funcs):
# We will count all derivatives equally, so replace them all with a
# temporary name
dname := UniqueName():
for diffop in map(lhs, op_list) do
op_eqns := eval(op_eqns, diffop = dname):
end do:
# Move all terms to one side
op_eqns := map(x -> lhs(x) - rhs(x), op_eqns):
# Determine highest power of dname
map(degree, op_eqns, dname):
max_degree := max(%):
unassign('j'):
ranking := []:
# Put higher degrees earlier in the ranking (ie later in the list)
for j from max_degree by -1 to 0 do
# Collect all terms of order-j in dname
map(coeff, op_eqns, dname, j):
# Convert to set
convert(%, set):
# We only care about terms, not constant coefficients; remove them
indets(%, assignable) minus convert(const,set) minus