Forma óptima de evaluar una función en muchos puntos.

Dec 12 2020

Esto se basa en mi pregunta anterior: ¿La rutina de búsqueda de espacio de parámetros es demasiado rápida?

Estoy buscando una forma rápida de evaluar una lista simbólica en muchos puntos. Digamos que tengo una lista de expresiones simbólicas como

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

y una lista de tuplas de la forma

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

y quiero evaluar ListA sobre cada tupla de ListB como

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

Ahora, mi listA puede tener más de decenas de miles de puntos y cada expresión puede tener más de cien líneas. Mi ListaB también puede ser descomunal, como más de decenas de millones de puntos, pero cada tupla solo tiene ~ 5 elementos y la he dividido en tamaños de aproximadamente 100-1000 tuplas. Mi pregunta es entonces ¿cuál sería la mejor manera de realizar rápidamente este tipo de reemplazos / asociaciones?

Utilicé mi primer intento, ParallelMappero esto tomó años todavía. Luego miré Associationsy esto redujo el tiempo, pero cada reemplazo de un elemento de ListB todavía toma como 1.5 - 2 segundos, que necesito reducir considerablemente. Aquí hay un MWE como referencia:

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

Ahora, la evaluación ListAsobre ListBprocedería como

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

Mi problema es que incluso una sola asociación como

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

tarda unos 2 segundos. Entonces, repetir esto en una lista de ~ 2 millones de puntos llevaría un siglo.

¿Sería útil una función compilada? No tengo mucha experiencia en el uso de la funcionalidad de compilación, así que me pregunto si sería ventajoso explorar eso. ¡Aprecio cualquier idea!

Actualizar

Gracias a la sugerencia de @flinty, el uso Withparece acelerar considerablemente la tarea. Aquí hay un breve experimento de cronometraje:

Aquí, QuantifieroverSpacecorresponde al ListAMWE anterior.

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

Entonces, usar en Withlugar de ReplaceAlles muchos órdenes de magnitud más rápido, lo cual es interesante. Implementaré esto en mi rutina de búsqueda y veré cuánto lo mejora.

Actualización 2

Entonces, mi siguiente problema es que necesito que el primer argumento de Withsea ​​modular al número de argumentos, es decir, debe poder tomar un conjunto de 3 variables como {a = 1, b = 1, c = 1} o un número diferente como {a = 1}. Mi primer pensamiento sería hacer algo como

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

pero mathica está asignando los valores ArrayofValuesa los símbolos SymbolListpara que la variable, apor ejemplo, tenga el valor 1. Luego intenté

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

pero esto hace lo mismo, asignando los valores a los símbolos. Curiosamente, mathica todavía ejecuta la withexpresión usando los valores en el primer argumento, pero sigue asignando el valor al símbolo, lo que ralentizaría la ejecución de mi rutina de búsqueda si quisiera deshacer la asignación. Necesito detener de alguna manera la Setasignación de ing pero aún mantener el formulario a=1de una manera dinámica al número de variables.

Actualización 3

Bueno, después de una inspección más profunda, descubrí por qué Withparece ser mucho más rápido. Es porque en realidad no sustituye los valores del primer argumento en la expresión. Por ejemplo,

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

(*{l,s}*)

Así que supongo que estoy de vuelta en el punto de partida tratando de encontrar una forma más rápida de asignar valores a los parámetros dentro de una gran matriz simbólica.

Respuestas

1 SimonWoods Dec 13 2020 at 02:19

Esta es solo una respuesta parcial, pero ...

Su función es bastante complicada, y acelerar eso probablemente sea más importante que cómo le da valores. Compilees tu amigo aquí.

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

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

A 5 ms para una sola fila de ListB, todavía llevará mucho tiempo, aunque se está volviendo más realista.