Cara optimal untuk mengevaluasi suatu fungsi pada banyak titik

Dec 12 2020

Ini piggy-back pada pertanyaan saya sebelumnya: Rutin pencarian ruang parameter terlalu cepat?

Saya mencari cara cepat untuk mengevaluasi daftar simbolis dari banyak poin. Jadi katakanlah saya memiliki daftar ekspresi simbolik seperti

ListA={a*b*c>0, a*b*(c+1)>0, a*b*(c-1)>0, etc.}

dan daftar tupel formulir

ListB={{1,1,1}, {1,1,2}, {1,2,1}, {1,2,2}< etc.}

dan saya ingin mengevaluasi ListA atas setiap tupel ListB seperti

ListA/.Thread[{a,b,c} -> ListB[[1]]]
ListA /.Thread[{a,b,c} -> ListB[[2]]]

Sekarang, listA saya dapat memiliki lebih dari puluhan ribu poin dan setiap ekspresi bisa lebih dari seratus baris. ListB saya juga bisa sangat besar, seperti hingga puluhan juta poin, tetapi setiap tupel hanya memiliki ~ 5 elemen dan saya telah memecahnya menjadi ukuran sekitar 100-1000 tupel. Pertanyaan saya adalah, apa cara terbaik untuk segera melakukan penggantian / pengaitan jenis ini?

Upaya pertama saya digunakan ParallelMaptetapi ini masih membutuhkan waktu lama. Kemudian saya melihat ke dalam Associationsdan ini memotong waktu tetapi setiap penggantian elemen ListB masih membutuhkan waktu 1,5 - 2 detik, yang perlu saya kurangi. Berikut adalah MWE untuk referensi:

func = (-2^(1 - px) (-1 + px) px Coth[
       rx sx]^2 (-2 sx y Sech[sx (-rx + x^2 + y^2)]^2 + 
        2 sx y Sech[sx (rx + x^2 + y^2)]^2)^2 (Coth[
         rx sx] (-Tanh[sx (-rx + x^2 + y^2)] + 
          Tanh[sx (rx + x^2 + y^2)]))^(-2 + px) - 
    2^(1 - px) px Coth[
      rx sx] (Coth[
         rx sx] (-Tanh[sx (-rx + x^2 + y^2)] + 
          Tanh[sx (rx + x^2 + y^2)]))^(-1 + 
        px) (-2 sx Sech[sx (-rx + x^2 + y^2)]^2 + 
       2 sx Sech[sx (rx + x^2 + y^2)]^2 + 
       8 sx^2 y^2 Sech[sx (-rx + x^2 + y^2)]^2 Tanh[
         sx (-rx + x^2 + y^2)] - 
       8 sx^2 y^2 Sech[sx (rx + x^2 + y^2)]^2 Tanh[
         sx (rx + x^2 + y^2)]) + 
    2^-px (-1 + px) px Coth[
       rx sx]^2 (-2 sx y Sech[sx (-R - rx + x^2 + y^2)]^2 + 
        2 sx y Sech[sx (-R + rx + x^2 + y^2)]^2)^2 (Coth[
         rx sx] (-Tanh[sx (-R - rx + x^2 + y^2)] + 
          Tanh[sx (-R + rx + x^2 + y^2)]))^(-2 + px) + 
    2^-px px Coth[
      rx sx] (Coth[
         rx sx] (-Tanh[sx (-R - rx + x^2 + y^2)] + 
          Tanh[sx (-R + rx + x^2 + y^2)]))^(-1 + 
        px) (-2 sx Sech[sx (-R - rx + x^2 + y^2)]^2 + 
       2 sx Sech[sx (-R + rx + x^2 + y^2)]^2 + 
       8 sx^2 y^2 Sech[sx (-R - rx + x^2 + y^2)]^2 Tanh[
         sx (-R - rx + x^2 + y^2)] - 
       8 sx^2 y^2 Sech[sx (-R + rx + x^2 + y^2)]^2 Tanh[
         sx (-R + rx + x^2 + y^2)]) + 
    2^-px (-1 + px) px Coth[
       rx sx]^2 (-2 sx y Sech[sx (R - rx + x^2 + y^2)]^2 + 
        2 sx y Sech[sx (R + rx + x^2 + y^2)]^2)^2 (Coth[
         rx sx] (-Tanh[sx (R - rx + x^2 + y^2)] + 
          Tanh[sx (R + rx + x^2 + y^2)]))^(-2 + px) + 
    2^-px px Coth[
      rx sx] (Coth[
         rx sx] (-Tanh[sx (R - rx + x^2 + y^2)] + 
          Tanh[sx (R + rx + x^2 + y^2)]))^(-1 + 
        px) (-2 sx Sech[sx (R - rx + x^2 + y^2)]^2 + 
       2 sx Sech[sx (R + rx + x^2 + y^2)]^2 + 
       8 sx^2 y^2 Sech[sx (R - rx + x^2 + y^2)]^2 Tanh[
         sx (R - rx + x^2 + y^2)] - 
       8 sx^2 y^2 Sech[sx (R + rx + x^2 + y^2)]^2 Tanh[
         sx (R + rx + x^2 + y^2)]));

parameters = {px, pz, R, rx, rz, sx, sz}
variables = {x, y, z}

Quantifier[coords_, params_] := 
 Function[Evaluate@Join[variables, parameters], Evaluate@(func > 0)][
  Sequence @@ Join[coords, params]]

SpaceA = Tuples[Range[-2, 2, 0.2], 3];

ListA = Quantifier[#1, parameters] & /@ SpaceA;
ListB = Tuples[Range[1, 4, 0.4], 7];
(*ListB contains~2 million elements*)

Sekarang, mengevaluasi ListAlebih ListBakan melanjutkan seperti

(AllTrue[ListA /. Thread[parameters -> #], TrueQ]) & /@ ListB
(*Careful running this, it will probably take a few months :( *)

Masalah saya adalah bahwa bahkan satu asosiasi seperti

ListA/.Thread[parameters->{1,1,1,1,1,1,1}]

membutuhkan waktu sekitar 2 detik. Jadi, mengulangi ini pada daftar ~ 2 juta poin akan membutuhkan waktu satu abad.

Akankah fungsi yang dikompilasi berguna? Saya tidak punya banyak pengalaman menggunakan fungsionalitas kompilasi jadi saya bertanya-tanya apakah akan menguntungkan untuk menjelajahinya. Saya menghargai semua wawasan!

Memperbarui

Berkat saran @flinty, penggunaan Withtampaknya mempercepat penugasan secara signifikan. Inilah percobaan waktu singkat:

Di sini, QuantifieroverSpacesesuai dengan ListAMWE di atas.

ClearAll[\[Epsilon], px, pz, R, rx, rz, sx, sz]
ByteCount[QuantifieroverSpace]

With[{\[Epsilon] = 2, px = 1, pz = 5, R = 1, rx = 2, rz = 2, sx = 2, 
   sz = 2},
  Evaluate@AllTrue[QuantifieroverSpace, TrueQ]] // Timing

AllTrue[QuantifieroverSpace /. 
   Thread[{\[Epsilon], px, pz, R, rx, rz, sx, sz} -> {2, 1, 5, 1, 2, 
      2, 2, 2}], TrueQ] // Timing

(*126992696*)
(*{0.000026, False}*)

(*{2.08846, False}*)

Jadi menggunakan Withalih-alih ReplaceAllbanyak lipat lebih cepat, yang menarik. Saya akan menerapkan ini dalam rutinitas pencarian saya dan melihat seberapa besar peningkatannya.

Perbarui 2

Jadi masalah saya berikutnya adalah bahwa saya memerlukan argumen pertama Withuntuk menjadi modular ke jumlah argumen, yaitu harus dapat mengambil 3 set variabel seperti {a = 1, b = 1, c = 1} atau nomor yang berbeda seperti {a = 1}. Pikiran pertama saya adalah melakukan sesuatu seperti

With[
     {Thread[SymbolList = ArrayofValues]}, 
     ...
     ]

tetapi mathematica memberikan nilai ArrayofValueske dalam simbol SymbolListsehingga variabel amisalnya memiliki nilai 1. Saya kemudian mencoba

init = MapThread[HoldForm[#1=#2]&, {SymbolList, ArrayofValues}];
With[
     Evaluate@ReleaseHold[init],
     ...
     ]

tetapi ini melakukan hal yang sama, menetapkan nilai ke simbol. Menariknya, mathematica masih mengeksekusi withekspresi menggunakan nilai-nilai dalam argumen pertama, tetapi memberikan nilai ke simbol masih, yang akan memperlambat eksekusi rutinitas pencarian saya jika saya ingin membatalkan tugas. Saya perlu menghentikan Setpenugasan tetapi tetap mempertahankan formulir a=1dengan cara yang dinamis ke jumlah variabel.

Perbarui 3

Nah setelah pemeriksaan lebih lanjut, saya menemukan mengapa Withtampaknya jauh lebih cepat. Itu karena itu tidak benar-benar menggantikan nilai-nilai argumen pertama ke dalam ekspresi. Sebagai contoh,

a = {l, s};
With[{l = 3, s = 12},
  Print[Evaluate[a]]
  ];

(*{l,s}*)

Jadi saya kira saya kembali ke titik awal mencoba menemukan cara yang lebih cepat untuk menetapkan nilai ke parameter di dalam array simbolik yang besar.

Jawaban

1 SimonWoods Dec 13 2020 at 02:19

Ini hanya sebagian jawaban, tapi ...

Fungsi Anda cukup rumit, dan mempercepatnya mungkin lebih penting daripada cara Anda memasukkan nilai ke dalamnya. Compileadalah temanmu disini.

cfunc = Compile @@ {Join[variables, parameters], func, 
   CompilationTarget -> "C", "RuntimeOptions" -> "Speed", 
   RuntimeAttributes -> {Listable}}

RepeatedTiming[AllTrue[cfunc @@ Join[SpaceA // Transpose, ListB[[1]]], Positive]]
{0.0051, False}

Pada 5ms untuk satu baris ListB, itu masih akan memakan waktu lama, meski semakin realistis.