0

I am using gnuplot v6 rc1 as I am relying on a function block to implement a non-trivial algorithm.

I have a function block definition function $f << _EOFD that returns a 7 element array based on each data point in a dataset. The function call, $f($1), consumes each data point in the dataset parsed by a plot command and returns a 7-element array value. So I wish to achieve something equivalent to:

local array x[7] = $f($1)

where the scope of that local array variable, x, extends over the entire plot command that makes the $f($1) function call (just the once!) for each line of the dataset.

  • The datasets are never complex and are "well behaved" - being just one positive integer value per line in an ASCII file and always monotonic increasing in magnitude. The length of a dataset can be very large. Currently ~20,000,000 data points is the longest dataset but I run short tests using datasets of fewer than 600 data points.

Ideally, within the current plot command I'd assign the return value of the function to an array variable and then use references to that array variable within the "using : : : : " section of the plot command however, I cannot see how to achieve such a variable assignment in a way that will trigger for each reading of a data point by the plot command. I cannot find an example of plot command syntax that accommodates this. The closest I have read is the notion of using one <plot element>, to define a function which can then be used across the remainder of the plot command. I'd like to do something similar but with an array variable rather than a function.

As you will see from the script snippet below, I use 5 elements of the returned array in a plot command and since I currently do so by calling $f($1) a total of (5+6)=11 times I suspect it is terribly wasteful of CPU cycles and time.

I'd appreciate learning how to achieve the same result more efficiently/elegantly.

script snippet to illustrate:-

array lastFNvalue[7] = [0,0,0,0,0,0,0]

function $f << _EOFD
if ( (lastFNvalue[2] == ARGC) && (lastFNvalue[1] == ARGV[1]) ) { return lastFNvalue }
:
: # algorithm yielding 5 values from one input value
:
local array s[7] = [ ARGV[1], ARGC, azimuth, r, delTheta, delR, density ]
lastFNvalue = s
return s
_EOFD

:
: # intervening code of no direct relevance
:

plot @inFile \
using ($f($1)[3]) : ($f($1)[4]) : ($f($1)[5]) : ($f($1)[6]) : ($f($1)[7]) \
with sectors linecolor palette \
, @inFile \
using ($f($1)[3]+$f($1)[5]/2) : ($f($1)[4]-0.4) : 1 : (-fixT*( ($f($1)[3]+$f($1)[5]/3) + pi/2 ) ) : ($f($1)[7]) \
with labels nopoint center rotate variable textcolor palette font "Times,8" \
, '-' using 1:2:3:4:5 with sectors linecolor variable
0 0 2 @N 0
EOD

So you can see that elements 3,4,5,6 of the array are used to plot (annular) sectors and element 7 (the density) indexes a color palette to yield a meaningful color for each radius of this plot .. which clearly uses polar coordinates.

I'd imagined I could use one comma-delimited: <plot-element>, to assign the returned value of $f($1) to an array variable and thereby avoid calling $f() multiple times within the one plot command. When I could not discover a suitable syntax for doing so I capitulated and settled for calling $f($1) multiple times and added a conditional test (as shown above) to bypass the time-consuming algorithm whenever the $1 data value being passed to $f() has not changed since the last time $f() was called. This was done out of expedience but now I'd like to understand the gnuplot interpreter better and see if a more elegant solution can be found.

  • Do you know of a legitimate syntax that enables (what hopefully the reader will agree is) intuitive/expected behavior from the pseudo-code I describe below?

specifically that plot-element: local array x[7] = $f($1) ,

plot @inFile \
local array x[7] = $f($1) \
, using (x[3]) : (x[4]) : (x[5]) : (x[6]) : (x[7]) \
with sectors linecolor palette \
, using (x[3]+x[5]/2) : (x[4]-0.4) : 1 : (-fixT*( (x[3]+x[5]/3) + pi/2 ) ) : (x[7]) \
with labels nopoint center rotate variable textcolor palette font "Times,8" \
, '-' using 1:2:3:4:5 with sectors linecolor variable
0 0 2 @N 0
EOD

The intent being that array x[7] has scope local to just that one plot command and therefore $f($1) need be called only the once per data point in any data set.

Thank you for considering this gnuplot conundrum!

  1. NOTE: I currently use 2 elements of the returned array to skip over time-consuming parts of the function block code whenever ARGV[1] and ARGC have not changed since the last time the $f() was called. This is a hack I am prepared to live with if my other goal is not (yet!) possible.
  2. FWIW: I did toy with having $f() return a string resembling the "using" syntax: "using (azimuth) : (r) : (delTheta) : (delR) : (density) " with a view to substitution of that string variable as a macro via: @$f($1) style of de-referencing but that was frustratingly unsuccessful. That type of approach is alluded to in this Q&A.
  3. For similar reasons expressed by the O.P. at this Q&A, I'd imagined I could use one comma-delimited: <plot-element>, to assign the returned value of $f($1) to an array variable and thereby avoid calling $f($1) multiple times within the one plot command. This is the solution path I wish to exhaust until I'm told it is not (yet!) possible.
1
  • It seems that one satisfactory answer lies in the use of the so-called comma operator. using (x = $f($1), x[3]) : (x[4]) : (x[5]) : (x[6]) : (x[7]) evaluates $f($1) and assigns the array result to x; but x[3] is returned to using as the value to use first and subsequent references to x[n] draw upon the value of $f($1) evaluated once and assigned to x[]. Commented Sep 27, 2023 at 6:20

2 Answers 2

0

Your solution to the problem looks correct to me with regard to the specific issue of loading array x[] the minimum number of times.

However I am uneasy about the final clause of your plot statement:

plot ... \
     , '-' using 1:2:3:4:5 with sectors linecolor variable
0 0 2 @N 0
e

You don't show the initialization or scope of the string variable N, but I would worry that the macro substitution may not occur at the time you are expecting. Macro expansion is not applied to input data, so this command would not work from the command line. If it is working in your case I guess that means it is being substituted during creation of a script or something like that. Since it appears that N is, or produces, a single value, I suggest the following as an alternative. This avoids both the macro expansion and the use of in-line data.

local array A[1]
evaluate( "A[1] = " . N )
plot ... \
     , A using (0):(0):(2):(A[1]):(0) with sectors lc variable
Sign up to request clarification or add additional context in comments.

4 Comments

<egg on face> Oh, I know for certain that the attempted in-line data was giving me grief! I suspect that line in my code snuck into this post late at night. I very much prefer your suggestion: a using clause with constants and variables for such a one-line data record.
Even in a month of sundays, I would never have thought to use an array, let alone a single element array, as the data source. That is clever!
For whatever reasons I could not get the solution using local array A[1] as a data source to work for me. Even so, inspired by the use of (constants) and (variables) within each using entry, I used a more familiar data block as the data source and achieved the desired result thereby avoiding both in-line data and macro expansion.
A typo in the answer - my fault. Fixed by editing the example. Since you want A[1] as a constant value, when given in the using specifier it must be in parentheses so that it will not be misinterpreted as a column number.
0

The following syntax is interpreted without error and seems to do exactly what I needed it to do.

local array x[7] = [0,0,0,0,0,0,0] # an explicit declaration seems optional
plot @inFile \
    using (x = $f($1), x[3]) : (x[4]) : (x[5]) : (x[6]) : (x[7]) \
    with sectors linecolor palette \
    , '' \
    using (x = $f($1), x[3]+x[5]/2) : (x[4]-0.4) : 1 : (-fixT*( (x[3]+x[5]/3) + pi/2 ) ) : (x[7]) \
    with labels nopoint center rotate variable textcolor palette font "Times,8" \
    , $Outline using (0):(0):(2):(N):(0) with sectors linecolor variable
  1. $f($1) is called from within the (parenthesized) expression that is the first "entry" of each "using clause".
  2. plot evaluates this entry each time a data record/data point is read .. because it is (parenthesized) (^)
  3. the comma separated statements (*) within the 1st entry of each using clause ensures that array x[] is assigned the value returned by the function block just the once per data record. Simultaneously, only the value from the last expression, x[3], within that "serially evaluated" 1st entry is used by plot (as the azimuth) in a polar plot to draw either sectors and/or labels.
  4. this survives a replot - so that is a good sign
  5. This reduces the number of times $f($1) is called from 11 to just 2 times per plot command - i.e. just once per using clause.

(^) (gnuplot manpage "Using examples" section)

The crucial point is that the expression is evaluated once if it doesn’t start with a left parenthesis, or once for each data point read if it does.

(*) Refer to the "serial evaluation" operator in gnuplot manual. It is a hidden gem!

I doubt I'll discover a better solution than this but I'll be glad to hear your feedback.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.