Forma óptima de evaluar una función en muchos puntos.
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, ParallelMap
pero esto tomó años todavía. Luego miré Associations
y 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 ListA
sobre ListB
procederí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 With
parece acelerar considerablemente la tarea. Aquí hay un breve experimento de cronometraje:
Aquí, QuantifieroverSpace
corresponde al ListA
MWE 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 With
lugar de ReplaceAll
es 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 With
sea 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 ArrayofValues
a los símbolos SymbolList
para que la variable, a
por 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 with
expresió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 Set
asignación de ing pero aún mantener el formulario a=1
de 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é With
parece 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
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.