Maneira ideal de avaliar uma função em muitos pontos

Dec 12 2020

Isso remete à minha pergunta anterior: A rotina de pesquisa do espaço de parâmetros é muito rápida?

Estou procurando uma maneira rápida de avaliar uma lista simbólica sobre muitos pontos. Digamos que tenho uma lista de expressões simbólicas como

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

e uma lista de tuplas do formulário

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

e quero avaliar a Lista A em cada tupla da Lista B, como

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

Agora, minha lista A pode ter mais de dezenas de milhares de pontos e cada expressão pode ter mais de cem linhas. Minha Lista B também pode ser enorme, como mais de dezenas de milhões de pontos, mas cada tupla tem apenas cerca de 5 elementos e eu a dividi em tamanhos de aproximadamente 100-1000 tuplas. A minha dúvida é então qual seria a melhor forma de realizar rapidamente este tipo de substituições / associação?

Minha primeira tentativa foi usada, ParallelMapmas ainda demorou muito. Em seguida, Associationspesquisei e isso reduziu o tempo, mas cada substituição de um elemento da Lista B ainda leva cerca de 1,5 a 2 segundos, que preciso reduzir consideravelmente. Aqui está um MWE para referência:

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

Agora, avaliando ListAmais de ListBprocederia como

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

Meu problema é que mesmo uma única associação como

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

leva cerca de 2 segundos. Portanto, repetir isso em uma lista de ~ 2 milhões de pontos levaria um século.

Uma função compilada seria útil? Não tenho muita experiência com a funcionalidade de compilação, então estou me perguntando se seria vantajoso explorar isso. Agradeço qualquer insight!

Atualizar

Graças à sugestão do @flinty, o uso Withparece acelerar consideravelmente a atribuição. Aqui está um experimento de curta duração:

Aqui, QuantifieroverSpacecorresponde ao ListAMWE acima.

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

Portanto, usar em Withvez de ReplaceAllé muitas ordens de magnitude mais rápido, o que é interessante. Vou implementar isso na minha rotina de pesquisa e ver o quanto isso melhora.

Atualização 2

Então, meu próximo problema é que eu preciso que o primeiro argumento de Withseja modular para o número de argumentos, ou seja, ele precisa ser capaz de aceitar um conjunto de 3 variáveis ​​como {a = 1, b = 1, c = 1} ou um número diferente como {a = 1}. Meu primeiro pensamento seria fazer algo como

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

mas o mathematica está atribuindo os valores in ArrayofValuesaos símbolos in de SymbolListmodo que a variável, apor exemplo, tenha o valor 1. Em seguida, tentei

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

mas isso faz a mesma coisa, atribuindo os valores aos símbolos. Curiosamente, o mathematica ainda executa a withexpressão usando os valores no primeiro argumento, mas ainda atribui o valor ao símbolo, o que tornaria a execução da minha rotina de pesquisa mais lenta se eu quisesse desfazer a atribuição. Eu preciso interromper de alguma forma Seta atribuição de ing, mas ainda manter a forma a=1de uma forma dinâmica para o número de variáveis.

Atualização 3

Bem, após uma inspeção mais aprofundada, descobri por que Withparece ser tão mais rápido. É porque não está realmente substituindo os valores do primeiro argumento na expressão. Por exemplo,

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

(*{l,s}*)

Então, acho que estou de volta à estaca zero, tentando encontrar uma maneira mais rápida de atribuir valores a parâmetros dentro de uma grande matriz simbólica.

Respostas

1 SimonWoods Dec 13 2020 at 02:19

Esta é apenas uma resposta parcial, mas ...

Sua função é muito complicada e acelerá-la é provavelmente mais importante do que como você a alimenta com valores. Compileé seu amigo aqui.

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

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

Com 5 ms para uma única linha de ListB, ainda vai demorar muito, embora esteja ficando mais realista.