I apologize for the very long question with a relatively complicated code. I am dealing with a real problem, and the question below appeared from my attempt to simplify this problem to ask about it here, while simultaneously not simplifying too much such that its scope will be lost. In addition, the reason for the problem is relatively simple: how Break[]
compares (in particular, inside a compiled code).
What I need to do
So, consider a 2D table tab2
that has 4*n
columns, where $n\geq 2$ is an arbitrary integer. The columns may be grouped by 4-columns,
1,2,3,4,...,4*(n-1)+1,4*(n-1)+2,4*(n-1)+3,4*n = (1,2,3,4),...(4*(n-1)+1,4*(n-1)+2,4*(n-1)+3,4*n)
The data from each of the 4-columns are used to calculate some boolean condition condition
. The arguments of condition
depend also on some external parameters contained in a table tab1
. For the given row of tab2
, let us define the acceptance: it is =1 if the product condition(4-column1)*condition(4-column2) = 1
for at least one pair of the 4-columns, or zero otherwise.
(The tables tab1
, tab2
, and the condition condition
are defined below.)
I need to calculate the acceptance averaged over the rows of tab2
for the given row of tab1
. So the output should be the list of acceptances with the dimensionality of tab1
.
My attempts
My attempt to solve this task resulted in two codes: acceptanceNotOptimized
and acceptanceOptimized
.
(See these codes below)
The first one, acceptanceNotOptimized
, loops over all pair combinations and the rows, then sums the boolean conditions.
It has two problems:
- Consider, some 4-column, say, (1,2,3,4). For
n
4-columns, it entersm<n-1
4-column pair combinations: e.g.,
(1,2,3,4)+(5,6,7,8), (1,2,3,4)+(9,10,11,12),...
In the code, the functions projection1,projection2,condition
are evaluated m
times for the given 4-column. In the truly optimized code, this has to be done only once.
- The code loops over all pair combinations and does not care whether the acceptance is 1 for one of them. Because of this, even if for the given pair combination for the given row
j
the pair acceptance is 1, the code does not stop. It evaluates the acceptances for all possible pair combinations.
(Because of this, in the end, the code may return acceptances > 1, which I will then replace with 1 using acceptanceMaxRepl
defined below)
acceptanceOptimized
should partially deal with these problems. It differs from acceptanceNotOptimized
literally by adding two Break[]
inside looping over the first 4-column and the second 4-column. The goal of Break[]
is to stop once at least for one pair of 4-columns the acceptance = 1. For large n
, it works considerably faster than acceptanceNotOptimized
.
However, it is still not the optimal one. For instance, in the marginal case, when there is no pair for which the acceptance = 1, it will compute all the boolean conditions for each 4-column m
times, where m
is the number of its occurrences in the pair combinations.
I have an idea for further optimization, but I got stuck on reproducing the results of the first code with the second code, which is crucial.
The question
The problem is that acceptanceOptimized
and acceptanceNotOptimized
give different acceptances (see the illustrations below). It may be related to the second Break[]
, but I do not see any problem with it. What may be the reason? Also, is it possible to further optimize the acceptanceOptimized
?
Pre-definitions required to launch the code
(*Dimensionality of tab1*)
nvals = 10^6;
(*tab1*)
tab1Temp :=
Join[RandomReal[{0, 1}, {nvals, 1}], RandomReal[{0, 1}, {nvals, 1}],
RandomReal[{1, 5}, {nvals, 1}], 2];
(*Dimensionality of tab2*)
nvals2 = 10^3;
(*tab2. Each 4-th column is a "charge". The 4-columns may form a pair \
only if they have the opposite charges*)
tab2temp[n_] := Block[{}, charge := Exp[-I*Pi*RandomInteger[{0, 1}]];
chargelist = Table[0, n];
While[Length[DeleteDuplicates[chargelist]] == 1,
chargelist = Table[charge, n]];
Join[##, 2] & @@
Table[Join[RandomReal[{0., 0.3}, {nvals2, 1}],
RandomReal[{0., 0.2}, {nvals2, 1}],
RandomReal[{0.8, 0.99}, {nvals2, 1}],
Table[{chargelist[[i]]}, nvals2], 2], {i, 1, n, 1}]]
(*This code returns tab1,tab2*)
datapreptemp[ncols_] := Block[{}, tt = tab2temp[ncols];
{tab1Temp, tt, chargelist, ncols}]
(*Functions of elements of tab1 (x0,y0,z0) and the first 3 columns of \
a 4-column of tab2 (vx,vy,vz)*)
projection1[x0_, z0_, vx_, vz_] = x0 + (10 - z0)*vx/vz;
projection2[y0_, z0_, vy_, vz_] = y0 + (10 - z0)*vy/vz;
(*The boolean condition where the functions above should be inserted*)
condition[projx_, projy_] =
Boole[-2 < projx < 2 && -1.5 < projy < 1.5];
The code - not optimized
acceptanceNotOptimized =
Hold@Compile[{{tab1, _Real, 1}, {tab2, _Real,
2}, {ncols, _Integer}},
Module[{count, x0vals, y0vals, z0vals, indexprod2, vxval1,
vyval1, vzval1, vxval2, vyval2, vzval2, xprod1, yprod1,
xprod2, yprod2, acc, productindex, charge1, charge2, acc1,
acc2},
count = 0.;
(*Extracting elements of tab1*)
x0vals = Compile`GetElement[tab1, 1];
y0vals = Compile`GetElement[tab1, 2];
z0vals = Compile`GetElement[tab1, 3];
(*Looping over rows of tab2*)
Do[
acc = 0.;
(*Looping over 4-columns of tab2*)
Do[
(*Extracting elements of tab2*)
vxval1 = Compile`GetElement[tab2, j, 4*(f1 - 1) + 1];
vyval1 = Compile`GetElement[tab2, j, 4*(f1 - 1) + 2];
vzval1 = Compile`GetElement[tab2, j, 4*(f1 - 1) + 3];
(*Calculating the boolean arguments*)
xprod1 = projection1[x0vals, z0vals, vxval1, vzval1];
yprod1 = projection2[y0vals, z0vals, vyval1, vzval1];
(*Condition for the first 4-column*)
acc = condition[xprod1, yprod1];
acc2 = 0.;
(*If the condition is !=0, looping over 4-
columns that may create a pair with the first 4-column*)
If[acc != 0.,
charge1 = Compile`GetElement[tab2, j, 4*(f1 - 1) + 4];
Do[
charge2 = Compile`GetElement[tab2, j, 4*(f2 - 1) + 4];
(*Only if charge1 = -charge2, the given 4-columns form a pair*)
If[charge1 == -charge2,
vxval2 = Compile`GetElement[tab2, j, 4*(f2 - 1) + 1];
vyval2 = Compile`GetElement[tab2, j, 4*(f2 - 1) + 2];
vzval2 = Compile`GetElement[tab2, j, 4*(f2 - 1) + 3];
xprod2 = projection1[x0vals, z0vals, vxval2, vzval2];
yprod2 = projection2[y0vals, z0vals, vyval2, vzval2];
(*Condition for the second 4-column - this is the 4-
column pair acceptance*)
acc2 = condition[xprod2, yprod2];
count += acc2];
, {f2, f1 + 1, ncols, 1}]];
, {f1, 1, ncols - 1, 1}];
, {j, 1, nvals2, 1}];
{count/nvals2}], CompilationTarget -> "C",
RuntimeOptions -> "Speed",
RuntimeAttributes -> {Listable}] /. OwnValues@nvals2 /.
DownValues@projection1 /. DownValues@projection2 /.
DownValues@condition // ReleaseHold;
acceptanceMaxRepl =
Compile[{{tab, _Real, 2}},
Table[{Min[tab[[i]][[1]], 1]}, {i, 1, Length[tab], 1}],
CompilationTarget -> "C", RuntimeOptions -> "Speed"];
The code - optimized
acceptanceOptimized =
Hold@Compile[{{tab1, _Real, 1}, {tab2, _Real,
2}, {ncols, _Integer}},
Module[{count, x0vals, y0vals, z0vals, indexprod2, vxval1,
vyval1, vzval1, vxval2, vyval2, vzval2, xprod1, yprod1,
xprod2, yprod2, acc, productindex, charge1, charge2, acc1,
acc2},
count = 0.;
(*Extracting elements of tab1*)
x0vals = Compile`GetElement[tab1, 1];
y0vals = Compile`GetElement[tab1, 2];
z0vals = Compile`GetElement[tab1, 3];
(*Looping over rows of tab2*)
Do[
acc = 0.;
(*Looping over 4-columns of tab2*)
Do[
(*Extracting elements of tab2*)
vxval1 = Compile`GetElement[tab2, j, 4*(f1 - 1) + 1];
vyval1 = Compile`GetElement[tab2, j, 4*(f1 - 1) + 2];
vzval1 = Compile`GetElement[tab2, j, 4*(f1 - 1) + 3];
(*Calculating the boolean arguments*)
xprod1 = projection1[x0vals, z0vals, vxval1, vzval1];
yprod1 = projection2[y0vals, z0vals, vyval1, vzval1];
(*Condition for the first 4-column*)
acc = condition[xprod1, yprod1];
acc2 = 0.;
(*If the condition is !=0, looping over 4-
columns that may create a pair with the first 4-column*)
If[acc != 0.,
charge1 = Compile`GetElement[tab2, j, 4*(f1 - 1) + 4];
Do[
charge2 = Compile`GetElement[tab2, j, 4*(f2 - 1) + 4];
(*Only if charge1 = -charge2, the given 4-columns form a pair*)
If[charge1 == -charge2,
vxval2 = Compile`GetElement[tab2, j, 4*(f2 - 1) + 1];
vyval2 = Compile`GetElement[tab2, j, 4*(f2 - 1) + 2];
vzval2 = Compile`GetElement[tab2, j, 4*(f2 - 1) + 3];
xprod2 = projection1[x0vals, z0vals, vxval2, vzval2];
yprod2 = projection2[y0vals, z0vals, vyval2, vzval2];
(*Condition for the second 4-column - this is the 4-
column pair acceptance*)
acc2 = condition[xprod2, yprod2];
count += acc2];
(*If for the given second 4-
column the pair acceptance is 1, then break*)
If[acc2 > 0., Break[]], {f2, f1 + 1, ncols, 1}]];
(*If for the given first 4-
column the pair acceptance is 1, then break*)
If[acc2 > 0., Break[]], {f1, 1, ncols - 1, 1}];
, {j, 1, nvals2, 1}];
{count/nvals2}], CompilationTarget -> "C",
RuntimeOptions -> "Speed",
RuntimeAttributes -> {Listable}] /. OwnValues@nvals2 /.
DownValues@projection1 /. DownValues@projection2 /.
DownValues@condition // ReleaseHold;
How to launch the code
First, one should generate tab1
and tab2
for the given n
; let us call the corresponding list dataprep
. This is done using (here n = 4
)
nval = 4;
dataprep = datapreptemp[nval];
Once dataprep
is generated, the codes are launched using this command (where Optimized
is "True"
if one wants to use acceptanceOptimized
, and something else otherwise):
acctab[dataprep_, Optimized_] := Module[{tab1, tab2, ncols},
tab1 = dataprep[[1]];
tab2 = dataprep[[2]];
ncols = dataprep[[4]];
tabtemp =
If[Optimized == "True", acceptanceOptimized[tab1, tab2, ncols],
acceptanceNotOptimized[tab1, tab2, ncols]]
]
The differences: illustration
Consider n
= 4:
nval = 4;
dataprep = datapreptemp[nval];
data1 = acctab[dataprep, "True"]; // AbsoluteTiming
data2 = acctab[dataprep, "False"]; // AbsoluteTiming
data1
, data2
are different:
Style[Row[{Histogram[data1 // Flatten, 100, ScalingFunctions -> {"Linear", "Log"}], Histogram[acceptanceMaxRepl[data2] // Flatten, 100, ScalingFunctions -> {"Linear", "Log"}]}], ImageSizeMultipliers -> {1, 1}]
The jump in unit value in data2
is caused by the fact that there are many rows for which more than one pair combinations have the unit boolean condition. In data1
, it is also observed, although much less explicitly. The shape of the distribution for the lower values is similar.
It looks like that the problem sits in the second Break[]
: if removing it from acceptanceOptimized
, it shows the same distribution. I do not understand what the issue is with this Break[]
. It should just break the cycle over the first 4-column once the cycle over the second 4-column was broken by the first Break[]
. Otherwise, it should do nothing as acc2 = 0
in the beginning...
It is interesting that the predictions of the two codes agree if nvals2 = 1
, but the differences appear for any other nvals
. I tried to put the break condition in different places of the code, but it did not work...
Maybe it has something to do with the option RuntimeAttributes->{Listable}
...
acceptanceNotOptimized
is correct.Break[]
may in principle work incorrectly inside the code. $\endgroup$Break
with alternative conditional code and see what the results are. $\endgroup$acceptanceOptimized
. $\endgroup$