Оптимальный способ оценить функцию по многим точкам

Dec 12 2020

Это ответ на мой предыдущий вопрос: процедура поиска в пространстве параметров работает слишком быстро?

Я ищу быстрый способ оценить символический список по многим пунктам. Итак, допустим, у меня есть список символических выражений, таких как

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

и список кортежей вида

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

и я хочу оценить ListA для каждого кортежа ListB, например

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

Теперь мой listA может иметь более десятков тысяч точек, а каждое выражение может содержать более сотни строк. Мой ListB также может быть огромным, например, до десятков миллионов точек, но каждый кортеж имеет только ~ 5 элементов, и я разбил его на размеры примерно по 100-1000 кортежей. Тогда у меня вопрос, как лучше всего быстро выполнить этот тип замен / ассоциаций?

Моя первая попытка использовалась, ParallelMapно это все еще заняло много времени. Затем я посмотрел, Associationsи это сократило время, но каждая замена элемента ListB по-прежнему занимает 1,5–2 секунды, что мне нужно значительно сократить. Вот MWE для справки:

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

Теперь, оценивая ListAболее ListBбудет продолжаться как

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

Моя проблема в том, что даже одна ассоциация вроде

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

занимает около 2 секунд. Таким образом, повторение этого над списком из ~ 2 миллионов точек заняло бы столетие.

Будет ли полезна скомпилированная функция? У меня нет большого опыта использования функций компиляции, поэтому мне интересно, было бы полезно изучить это. Я ценю любую проницательность!

Обновлять

Благодаря предложению @flinty использование, Withпохоже, значительно ускоряет назначение. Вот небольшой эксперимент по времени:

Здесь QuantifieroverSpaceсоответствует ListAв приведенном выше MWE.

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

Так что использование Withвместо на ReplaceAllмного порядков быстрее, что интересно. Я добавлю это в свою процедуру поиска и посмотрю, насколько это улучшит.

Обновление 2

Итак, моя следующая проблема заключается в том, что мне нужно, чтобы первый аргумент Withбыл модульным по отношению к количеству аргументов, то есть он должен иметь возможность принимать либо набор из трех переменных, например {a = 1, b = 1, c = 1}, либо другое число, например {a = 1}. Моя первая мысль была бы сделать что-нибудь вроде

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

но mathematica присваивает значения в ArrayofValuesсимволах в SymbolListтак, чтобы переменная, aнапример, имела значение 1. Затем я попробовал

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

но это то же самое, присваивая значения символам. Интересно, что mathematica по-прежнему выполняет withвыражение, используя значения в первом аргументе, но по-прежнему присваивает значение символу, что замедлит выполнение моей процедуры поиска, если я захочу отменить присвоение. Мне нужно как-то остановить Setприсвоение ing, но при этом сохранить форму a=1динамически в зависимости от количества переменных.

Обновление 3

При дальнейшем осмотре я понял, почему Withон работает намного быстрее. Это потому, что на самом деле в выражение не подставляются значения первого аргумента. Например,

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

(*{l,s}*)

Итак, я думаю, что я вернулся на круги своя, пытаясь найти более быстрый способ присвоить значения параметрам внутри большого символьного массива.

Ответы

1 SimonWoods Dec 13 2020 at 02:19

Это лишь частичный ответ, но ...

Ваша функция довольно сложна, и ее ускорение, вероятно, важнее, чем то, как вы вводите в нее значения. Compileтвой друг здесь.

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 мс для одной строки ListBэто все равно займет много времени, хотя это становится более реалистичным.