Méthode optimale pour évaluer une fonction sur de nombreux points

Dec 12 2020

Ce piggy-backs sur ma question précédente: la routine de recherche d'espace de paramètres est trop rapide?

Je recherche un moyen rapide d'évaluer une liste symbolique sur de nombreux points. Alors disons que j'ai une liste d'expressions symboliques comme

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

et une liste de tuples de la forme

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

et je veux évaluer ListA sur chaque tuple de ListB comme

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

Maintenant, ma listeA peut avoir plus de dizaines de milliers de points et chaque expression peut dépasser une centaine de lignes. Ma ListB peut aussi être gigantesque, comme plus de dizaines de millions de points, mais chaque tuple n'a que ~ 5 éléments et je l'ai découpé en tailles d'environ 100-1000 tuples. Ma question est alors quelle serait la meilleure façon d'effectuer rapidement ce type de remplacements / d'association?

Ma première tentative a été utilisée ParallelMapmais cela a encore pris des années. Ensuite, j'ai regardé Associationset cela a réduit le temps, mais chaque remplacement d'un élément de ListB prend encore 1,5 à 2 secondes, ce que je dois réduire considérablement. Voici un MWE pour référence:

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*)

Maintenant, évaluer ListAplus ListBse déroulerait comme

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

Mon problème est que même une seule association comme

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

prend environ 2 secondes. Donc, répéter cela sur une liste d'environ 2 millions de points prendrait un siècle.

Une fonction compilée serait-elle utile? Je n'ai pas beaucoup d'expérience dans l'utilisation de la fonctionnalité de compilation, donc je me demande s'il serait avantageux d'explorer cela. J'apprécie toute perspicacité!

Mettre à jour

Grâce à la suggestion @flinty, l'utilisation Withsemble accélérer considérablement la tâche. Voici une courte expérience de synchronisation:

Ici, QuantifieroverSpacecorrespond à ListAdans le MWE ci-dessus.

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}*)

Donc, utiliser Withau lieu de ReplaceAllest beaucoup plus rapide de plusieurs ordres de grandeur, ce qui est intéressant. Je vais l'implémenter dans ma routine de recherche et voir à quel point cela l'améliore.

Mise à jour 2

Donc, mon prochain problème est que j'ai besoin que le premier argument de Withsoit modulaire au nombre d'arguments, c'est-à-dire qu'il doit être capable de prendre soit un ensemble de 3 variables comme {a = 1, b = 1, c = 1} ou un nombre différent comme {a = 1}. Mon premier serait de faire quelque chose comme

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

mais Mathematica attribue les valeurs ArrayofValuesaux symboles SymbolListafin que la variable apar exemple ait la valeur 1. J'ai ensuite essayé

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

mais cela fait la même chose, en attribuant les valeurs aux symboles. Fait intéressant, Mathematica exécute toujours l' withexpression en utilisant les valeurs du premier argument, mais il affecte toujours la valeur au symbole, ce qui ralentirait l'exécution de ma routine de recherche si je voulais annuler l'affectation. Je dois en quelque sorte arrêter l' Setaffectation d'ingénierie tout en conservant le formulaire a=1d'une manière dynamique par rapport au nombre de variables.

Mise à jour 3

Eh bien, après une inspection plus approfondie, j'ai compris pourquoi Withsemble être beaucoup plus rapide. C'est parce qu'il ne remplace pas réellement les valeurs du premier argument dans l'expression. Par example,

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

(*{l,s}*)

Je suppose donc que je suis de retour à la case départ en essayant de trouver un moyen plus rapide d'attribuer des valeurs aux paramètres à l'intérieur d'un grand tableau symbolique.

Réponses

1 SimonWoods Dec 13 2020 at 02:19

Ce n'est qu'une réponse partielle, mais ...

Votre fonction est assez compliquée, et l'accélérer est probablement plus important que la façon dont vous lui donnez des valeurs. Compileest votre ami ici.

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

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

À 5 ms pour une seule rangée de ListB, cela prendra encore beaucoup de temps, même si cela devient plus réaliste.