Gnuplot Function with Multiple Statements - gnuplot

Question
Is it possible to define functions which have multiple statements defined within?
Context
I want to automate some of the calculations involved in creating stacked plots by defining functions. In particular, I was hoping to have something like
mp_setup(bottom_margin, top_margin) = \
set tmargin 0; \
set bmargin 0; \
mp_available_height = 1.0 - top_margin - bottom_margin; \
mp_current_height = bottom_margin;
mp_plot(plot_height) = \
mp_plot_size = plot_height * mp_available_height; \
set origin 0,mp_current_height; \
set size 1,mp_plot_size; \
mp_current_height = mp_current_height + mp_plot_size;
with the intended usage being:
...
set multiplot
mp_setup(0.05, 0.05)
mp_plot(1.0/3.0)
plot ...
mp_plot(2.0/3.0)
plot ...
and this should automatically result in the plots being nicely stacked without me having to calculate the origin and size for each graph.
Problem
The way of defining the functions above doesn't work because it seems like the parsing of the function definition ends at the first occurrence of ;; but these semicolons are necessary in order to separate each statement (otherwise, we have set tmargin 0 set bmargin 0... which is invalid).
It also seems like Gnuplot doesn't support any way of grouping statement (like {...} in C/C++); or at least, I have never come across it.
Possible Solution
The only method I know to store multiple functions and evaluate them is using macros:
mp_setup = "<as above>"
mp_plot = "<as above>"
But the issue here is that macros do not allow for arguments to be passed in and instead each variable would have to be declared beforehand as follows:
...
set multiplot
top_margin = 0.05
bottom_margin = 0.05
#mp_setup
plot_height = 1.0/3.0
#mp_plot
plot ...
plot_height = 2.0/3.0
#mp_plot
plot ...
This solution, although it should work, is not quite as elegant.
Is there no other way of doing this?

No, it isn't possible to define such functions. In gnuplot user-defined functions cannot contain set, unset or other commands. Only those kind of expressions are allowed, which return numerical or string variables. Here, you can have several expressions, separated by comma:
a = 0
f(x) = (a = a + 1, a + x)
print f(1)
print f(1)
Besides your solution to use macros (#var), I prefer constructing strings inside the function and calling eval:
set_margin(s, v) = sprintf('set %smargin at screen %f;', s, v)
set_margins(l, r, b, t) = set_margin('l', l).set_margin('r', r).set_margin('b', b).set_margin('t', t)
eval(set_margins(0.1, 0.95, 0.15, 0.98))
For your specific case of a multiplot layout you can also see Removing blank gap in gnuplot multiplot.

You can do like that
mp_setup(bottom_margin, top_margin)=(tmargin=0,bmargin=0,mp_available_height=1.0 -top_margin-bottom_margin,mp_current_height=bottom_margin)
test:
print mp_setup(0.05,0.05)
==> 0.05
As you mentioned, the grouping statement in the function is not supporting yet.

Related

plotting simplex method in Maxima

im having trouble plotting the results of simplex method used for linear programing in Maxima
i used
minimize_lp(-4*x-5*y,[5*x+4*y<=40, x+4*y<=32, 5*x<=30]);
to solve the problem but now i need to plot this
tried this:
wxplot2d(minimize_lp(-4*x-5*y,[5*x+4*y<=40, x+4*y<=32, 5*x<=30]),[x,-10,10],[y,-10,10]);
all i get is "expression evaluates to non-numeric value everywhere in plotting range" and "nothing to plot" messages
i dont really use Maxima, im not very familiar with it,
its an assigment for a course, any help would be great
Graphical solution for a linear optimization problem subject to constraints
To plot the inequalities and the feasible set I did the following:
Step 1 – solving algebraically using minimize_lp()
load("simplex")$
minimize_lp(
-4*x-5*y,[
5*x+4*y<=40,
x+4*y<=32,
5*x<=30,
x>=0,y>=0
]
);
which yields
%o2) [-91/2,[y=15/2,x=2]]
Step 2 – extracting the coordinates of the optimizing point from %o2
soln: %o2$
c4sol: soln[2]$
x_c: rhs(c4sol[2])$
y_c: rhs(c4sol[1])$
pOpt: [x_c,y_c];
This outputs
(%o7) [2,15/2]
the coordinates of the optimizing point pOpt in a list that we later need for plotting.
Step 3 – defining the feasible set
We know from minimize_lp() that f(x,y) is minimized at (x=2,y=15/2) with
f=-91/2. So we rewrite the target function as -4x-5y=-91/2
tfm: solve(-4*x-5*y=-91/2,y)$
Next we use the restriction r1,r2,r3 to define the the bounderies of the feasible set by turning the inequalities into equalities and solving for the dependent variables:
r1: solve(5*x+4*y=40,y)$
r2: solve(x+4*y=32,y)$
r3: solve(5*x=30,x)$
We then create a list L containing:
L: [ev(y,tfm), ev(y,r1), ev(y,r2), ev(x,r3)];
which yields:
(%o12) [-(8*x-91)/10,-(5*x-40)/4,-(x-32)/4,6]
Step 4 – The plots
We use plot2d() for quick'n'dirty sketches:
plot2d(L,[x,1.5,5.25],[y,5,8.25])$
For the final plot we use draw2d
draw2d(
proportional_axes=xy ,
xrange = [1.5,5.25],
yrange = [5.00,8.25],
point_type = filled_circle, point_size=1.25,
color = black,
points(pOpt),
label(["pOpt = (2,15/2)", 2.50, 7.65]),
key = "tfMin=-91/2",
line_width = 2,
color = steelblue4,
explicit(ev(y,tfm ), x,0,12.25),
key = "r1",
line_width = 1.5,
color = DeepPink3,
explicit(ev(y,r1), x,0,12.25),
key = "r2",
line_width = 1.5,
color = cyan4,
explicit(ev(y,r2), x,0,12.25),
key = "r3",
line_width = 1.5,
color = orange,
explicit(ev(x,r3), x,0,12.25),
key = "feasible set",
border = false,
fill_color = LightPink2,
polygon([[3.20,6],[2,7.5],[3.9,6]])
)$
Hope this helps, after all these years.
Cheers
Tilda

How to plot lines parallel to the x-axis with a certain offset given by data in an input file with gnuplot

I calculated the eigenvalues of the Hamiltonian for the 1D-hydrogen atom in atomic units with the Fourier-Grid-Hamiltonian method in a nice little Fortran program.
All the eigenvalues found between -1 and 0 (the bound states) are saved into a file line by line like this:
-0.50016671392950229
-0.18026105614262633
-0.11485673263086937
-4.7309305955423042E-002
-4.7077108902158216E-002
As the number of found eigenvalues differs depends on the stepsize my program uses, the number of entries in the file can vary (in theory, there are infinite ones).
I now want to plot the values from the file as a line parallel to the x-axis with the offset given by the values read from file.
I also want to be able to plot the data only up to a certain line number, as the values get really close to each other the further you come to zero and they cannot be distinguished by eye anymore.
(Here e.g. it would make sence to plot the first four entries, the fifth is already too close to the previous one)
I know that one can plot lines parallel to the x axis with the command plot *offset* but I don't know how to tell gnuplot to use the data from the file. So far I had to manually plot the values.
As a second step I would like to plot the data only in a certain x range, more concrete between the points of intersection with the harmonic potential used for the numeric solution V(x) = -1/(1+abs(x))
The result should look like this:
scheme of the desired plot (lookalike)
The closest I got to, was with
plot -1/(1+abs(x)),-0.5 title 'E0',-0.18 title 'E1', -0.11 title 'E2'
which got me the following result:
my plot
Hope you guys can help me, and I'm really curios whether gnuplot actually can do the second step I described!
As for the first part of your question, you can for example use the xerrorbars plotting style as:
set terminal pngcairo
set output 'fig.png'
unset key
set xr [-1:1]
set yr [-1:0]
unset bars
plot '-' u (0):($1<-0.1?$1:1/0):(1) w xerrorbars pt 0 lc rgb 'red'
-0.50016671392950229
-0.18026105614262633
-0.11485673263086937
-4.7309305955423042E-002
-4.7077108902158216E-002
e
The idea here is to:
interpret the energies E as points with coordinates (0,E) and assign to each of them an x-errorbar of width 1 (via the third part of the specification (0):($1<-0.1?$1:1/0):(1))
"simulate" the horizontal lines with x-errorbars. To this end, unset bars and pt 0 ensure that Gnuplot displays just plain lines.
consider only energies E<-0.1, the expressions $1<-0.1?$1:1/0 evaluates otherwise to an undefined value 1/0 which has the consequence that nothing is plotted for such E.
plot '-' with explicit values can be of course replaced with, e.g., plot 'your_file.dat'
This produces:
For the second part, it mostly depends how complicated is your function V(x). In the particular case of V(x)=-1/(1+|x|), one could infer directly that it's symmetric around x=0 and calculate the turning points explicitly, e.g.,
set terminal pngcairo
set output 'fig.png'
fName = 'test.dat'
unset key
set xr [-10:10]
set yr [-1:0]
unset bars
f(x) = -1 / (1+abs(x))
g(y) = (-1/y - 1)
plot \
f(x) w l lc rgb 'black', \
fName u (0):($1<-0.1?$1:1/0):(g($1)) w xerrorbars pt 0 lc rgb 'red', \
fName u (0):($1<-0.1?$1:1/0):(sprintf("E%d", $0)) w labels offset 0, char 0.75
which yields
The idea is basically the same as before, just the width of the errorbar now depends on the y-coordinate (the energy). Also, the labels style is used in order to produce explicit labels.
Another approach may be to get data from "energy.dat" (as given in the question) with system and cat commands (so assuming a Un*x-like system...) and select V(x) and E at each x via max:
set key bottom right
set yr [-1:0.2]
set samples 1000
Edat = system( "cat energy.dat" )
max(a,b) = ( a > b ) ? a : b
V(x) = -1/(1+abs(x))
plot for [ E in Edat ] \
max(V(x),real(E)) title sprintf("E = %8.6f", real(E)) lw 2, \
V(x) title "V(x) = -1/(1+|x|)" lc rgb "red" lw 2
If we change the potential to V(x) = -abs(cos(x)), the plot looks pretty funny (and the energy levels are of course not correct!)
More details about the script:
max is not a built-in function in Gnuplot, but a user-defined function having two formal arguments. So for example, we may define it as
mymax( p, q ) = ( p > q ) ? p : q
with any other names (and use mymax in the plot command). Next, the ? symbol is a ternary operator that gives a short-hand notation for an if...else construct. In a pseudo-code, it works as
function max( a, b ) {
if ( a > b ) then
return a
else
return b
end
}
This way, max(V(x),real(E)) selects the greater value between V(x) and real(E) for any given x and E.
Next, Edat = system( "cat energy.dat" ) tells Gnuplot to run the shell command "cat energy.dat" and assign the output to a new variable Edat. In the above case, Edat becomes a string that contains a sequence of energy values read in from "energy.dat". You can check the contents of Edat by print( Edat ). For example, it may be something like
Edat = "-0.11 -0.22 ... -0.5002"
plot for [ E in Edat ] ... loops over words contained in a string Edat. In the above case, E takes a string "-0.11", "-0.22", ..., "-0.5002" one-by-one. real(E) converts this string to a floating-point value. It is used to pass E (a character string) to any mathematical function.
The basic idea is to draw a truncated potential above E, max(V(x),E), for each value of E. (You can check the shape of such potential by plot max(V(x),-0.5), for example). After plotting such curves, we redraw the potential V(x) to make it appear as a single potential curve with a different color.
set samples 1000 increases the resolution of the plot with 1000 points per curve. 1000 is arbitrary, but this seems to be sufficient to make the figure pretty smooth.

Making x,y tics dynamically in gnuplot

I have a problem with setting the x and ytics in gnuplot.
I want to create heat maps, and to do it I need specific tics for each "block" in them. I want to create them with loops, so it is much easier to work with them, because for instance I need to make like 60 xtics so I have to write down this:
set xtics ("-0.3" 0, "-0.29" 1, ... "0.3" 60)
It is exhausting and frustrating, so I wanted to make a script like this:
xnumtics = 60
ynumtics = 90
set macros
xticstring = '('
do for [i=0:xnumtics] {
xticstring = xticstring.'"'.(-0.3 + i*0.01).'" '.i.', '
}
xticstring = xticstring.'"'.0.3.'" '.xnumtics.')'
set xtics #xticstring
yticstring = '('
do for [j=0:ynumtics] {
yticstring = yticstring.'"'.(0 - i*0.01).'" '.j.', '
}
yticstring = yticstring.'"'.0.9.'"'.ynumtics.' '
set ytics #yticstring
Unfortunately I get an error: line 21: internal error : STRING operator applied to non-STRING type
line 21 refers for: xticstring = xticstring.'"'.(-0.3 + i*0.01).'" '.i.','
I got this idea from: Can GNUPLOT dynamically prepare index labels?
I am using gnuplot 4.6 ptchlvl 4 on ubuntu 14.04 LTS
Sorry if it is too trivial, I cannot figure it out what is the problem...
Thank you!
The string concatenation operator . can automatically convert only integer numbers, not doubles. You would need to use sprintf or gprintf to convert the (-0.3 + i*0.01) to a string.
However, a shorter way to set the tics would be to use set for ... iterations:
xnumtics = 60
ynumtics = 90
set xtics () # clear all tics
set for [i=0:xnumtics] xtics add (gprintf("%g", -0.3 + i * 0.01) i)
set ytics ()
set for [i=0:ynumtics] ytics add (gprintf("%g", -i * 0.01) i)
Do you really need to manually add the tics? Usually one maps the values from a data file to use gnuplot's usual numerical axes:
plot 'file.dat' using (-0.3 + $1 * 0.01):($2 * -0.01)

reduce datapoints when using logscale in gnuplot

I have a large set of data points from x = 1 to x = 10e13 (step size is fixed to about 3e8).
When I try to plot them using a logscale I certainly get an incredible huge point-density towards the end. Of course this affects my output plots since postscript and svg files (holding each and every data point) are getting really big.
Is there a way to tell gnuplot to decrease the data density dynamically?
Sample data here. Shows a straight line using logarithmic x-axis.
Usually, for this kind of plots, one can use a filter function which selects the desired points and discards all others (sets their value to 1/0:
Something like:
plot 'sample.dat' using (filter($1) ? $1 : 1/0):2
Now you must define an appropriate filter function to change the data density. Here is a proposal, with pseudo-data, although you might for sure find a better one, which doesn't show this typical logarithmic pattern:
set logscale x
reduce(x) = x/(10**(floor(log10(x))))
filterfunc(x) = abs(log10(sc)+(log10(x) - floor(log10(x))) - log10(floor(sc*reduce(x))))
filter(x) = filterfunc(x) < 1e-5 ? x : 1/0
set multiplot layout 1,2
sc = 1
plot 'sample.data' using (filter($1)):2 notitle
sc = 10
replot
The variable sc allows to change the density. The result is (with 4.6.5) is:
I did some work inspired by Christoph's answer and able to get equal spacing in log scale. I made a filtering, if you have numbers in the sequence you can simply use Greatest integer function and then find the nearest to it in log scale by comparing the fraction part. Precision is tuned by precision_parameter here.
precision_parameter=100
function(x)=(-floor(precision_parameter*log10(x))+(precision_parameter*log10(x)))
Now filter by using the filter function defined below
density_parameter = 3.5
filter(x)=(function(x) < 1/(log10(x))**density_parameter & function(x-1) > 1/(log10(x))**density_parameter ) ? x : 1/0
set datafile missing "NaN"
Last line helps in plotting with line point. I used x and x-1 assuming the xdata is in arithmetic progression with 1 as common difference, change it accordingly with your data. Just replace x by filter(x) in the plot command.
plot 'sample_data.dat' u (filter($1)):2 w lp

Histogram using gnuplot?

I know how to create a histogram (just use "with boxes") in gnuplot if my .dat file already has properly binned data. Is there a way to take a list of numbers and have gnuplot provide a histogram based on ranges and bin sizes the user provides?
yes, and its quick and simple though very hidden:
binwidth=5
bin(x,width)=width*floor(x/width)
plot 'datafile' using (bin($1,binwidth)):(1.0) smooth freq with boxes
check out help smooth freq to see why the above makes a histogram
to deal with ranges just set the xrange variable.
I have a couple corrections/additions to Born2Smile's very useful answer:
Empty bins caused the box for the adjacent bin to incorrectly extend into its space; avoid this using set boxwidth binwidth
In Born2Smile's version, bins are rendered as centered on their lower bound. Strictly they ought to extend from the lower bound to the upper bound. This can be corrected by modifying the bin function: bin(x,width)=width*floor(x/width) + width/2.0
Be very careful: all of the answers on this page are implicitly taking the decision of where the binning starts - the left-hand edge of the left-most bin, if you like - out of the user's hands. If the user is combining any of these functions for binning data with his/her own decision about where binning starts (as is done on the blog which is linked to above) the functions above are all incorrect. With an arbitrary starting point for binning 'Min', the correct function is:
bin(x) = width*(floor((x-Min)/width)+0.5) + Min
You can see why this is correct sequentially (it helps to draw a few bins and a point somewhere in one of them). Subtract Min from your data point to see how far into the binning range it is. Then divide by binwidth so that you're effectively working in units of 'bins'. Then 'floor' the result to go to the left-hand edge of that bin, add 0.5 to go to the middle of the bin, multiply by the width so that you're no longer working in units of bins but in an absolute scale again, then finally add back on the Min offset you subtracted at the start.
Consider this function in action:
Min = 0.25 # where binning starts
Max = 2.25 # where binning ends
n = 2 # the number of bins
width = (Max-Min)/n # binwidth; evaluates to 1.0
bin(x) = width*(floor((x-Min)/width)+0.5) + Min
e.g. the value 1.1 truly falls in the left bin:
this function correctly maps it to the centre of the left bin (0.75);
Born2Smile's answer, bin(x)=width*floor(x/width), incorrectly maps it to 1;
mas90's answer, bin(x)=width*floor(x/width) + binwidth/2.0, incorrectly maps it to 1.5.
Born2Smile's answer is only correct if the bin boundaries occur at (n+0.5)*binwidth (where n runs over integers). mas90's answer is only correct if the bin boundaries occur at n*binwidth.
Do you want to plot a graph like this one?
yes? Then you can have a look at my blog article: http://gnuplot-surprising.blogspot.com/2011/09/statistic-analysis-and-histogram.html
Key lines from the code:
n=100 #number of intervals
max=3. #max value
min=-3. #min value
width=(max-min)/n #interval width
#function used to map a value to the intervals
hist(x,width)=width*floor(x/width)+width/2.0
set boxwidth width*0.9
set style fill solid 0.5 # fill style
#count and plot
plot "data.dat" u (hist($1,width)):(1.0) smooth freq w boxes lc rgb"green" notitle
As usual, Gnuplot is a fantastic tool for plotting sweet looking graphs and it can be made to perform all sorts of calculations. However, it is intended to plot data rather than to serve as a calculator and it is often easier to use an external programme (e.g. Octave) to do the more "complicated" calculations, save this data in a file, then use Gnuplot to produce the graph. For the above problem, check out the "hist" function is Octave using [freq,bins]=hist(data), then plot this in Gnuplot using
set style histogram rowstacked gap 0
set style fill solid 0.5 border lt -1
plot "./data.dat" smooth freq with boxes
I have found this discussion extremely useful, but I have experienced some "rounding off" problems.
More precisely, using a binwidth of 0.05, I have noticed that, with the techniques presented here above, data points which read 0.1 and 0.15 fall in the same bin. This (obviously unwanted behaviour) is most likely due to the "floor" function.
Hereafter is my small contribution to try to circumvent this.
bin(x,width,n)=x<=n*width? width*(n-1) + 0.5*binwidth:bin(x,width,n+1)
binwidth = 0.05
set boxwidth binwidth
plot "data.dat" u (bin($1,binwidth,1)):(1.0) smooth freq with boxes
This recursive method is for x >=0; one could generalise this with more conditional statements to obtain something even more general.
We do not need to use recursive method, it may be slow. My solution is using a user-defined function rint instesd of instrinsic function int or floor.
rint(x)=(x-int(x)>0.9999)?int(x)+1:int(x)
This function will give rint(0.0003/0.0001)=3, while int(0.0003/0.0001)=floor(0.0003/0.0001)=2.
Why? Please look at Perl int function and padding zeros
I have a little modification to Born2Smile's solution.
I know that doesn't make much sense, but you may want it just in case. If your data is integer and you need a float bin size (maybe for comparison with another set of data, or plot density in finer grid), you will need to add a random number between 0 and 1 inside floor. Otherwise, there will be spikes due to round up error. floor(x/width+0.5) will not do because it will create pattern that's not true to original data.
binwidth=0.3
bin(x,width)=width*floor(x/width+rand(0))
With respect to binning functions, I didn't expect the result of the functions offered so far. Namely, if my binwidth is 0.001, these functions were centering the bins on 0.0005 points, whereas I feel it's more intuitive to have the bins centered on 0.001 boundaries.
In other words, I'd like to have
Bin 0.001 contain data from 0.0005 to 0.0014
Bin 0.002 contain data from 0.0015 to 0.0024
...
The binning function I came up with is
my_bin(x,width) = width*(floor(x/width+0.5))
Here's a script to compare some of the offered bin functions to this one:
rint(x) = (x-int(x)>0.9999)?int(x)+1:int(x)
bin(x,width) = width*rint(x/width) + width/2.0
binc(x,width) = width*(int(x/width)+0.5)
mitar_bin(x,width) = width*floor(x/width) + width/2.0
my_bin(x,width) = width*(floor(x/width+0.5))
binwidth = 0.001
data_list = "-0.1386 -0.1383 -0.1375 -0.0015 -0.0005 0.0005 0.0015 0.1375 0.1383 0.1386"
my_line = sprintf("%7s %7s %7s %7s %7s","data","bin()","binc()","mitar()","my_bin()")
print my_line
do for [i in data_list] {
iN = i + 0
my_line = sprintf("%+.4f %+.4f %+.4f %+.4f %+.4f",iN,bin(iN,binwidth),binc(iN,binwidth),mitar_bin(iN,binwidth),my_bin(iN,binwidth))
print my_line
}
and here's the output
data bin() binc() mitar() my_bin()
-0.1386 -0.1375 -0.1375 -0.1385 -0.1390
-0.1383 -0.1375 -0.1375 -0.1385 -0.1380
-0.1375 -0.1365 -0.1365 -0.1375 -0.1380
-0.0015 -0.0005 -0.0005 -0.0015 -0.0010
-0.0005 +0.0005 +0.0005 -0.0005 +0.0000
+0.0005 +0.0005 +0.0005 +0.0005 +0.0010
+0.0015 +0.0015 +0.0015 +0.0015 +0.0020
+0.1375 +0.1375 +0.1375 +0.1375 +0.1380
+0.1383 +0.1385 +0.1385 +0.1385 +0.1380
+0.1386 +0.1385 +0.1385 +0.1385 +0.1390
Different number of bins on the same dataset can reveal different features of the data.
Unfortunately, there is no universal best method that can determine the number of bins.
One of the powerful methods is the Freedman–Diaconis rule, which automatically determines the number of bins based on statistics of a given dataset, among many other alternatives.
Accordingly, the following can be used to utilise the Freedman–Diaconis rule in a gnuplot script:
Say you have a file containing a single column of samples, samplesFile:
# samples
0.12345
1.23232
...
The following (which is based on ChrisW's answer) may be embed into an existing gnuplot script:
...
## preceeding gnuplot commands
...
#
samples="$samplesFile"
stats samples nooutput
N = floor(STATS_records)
samplesMin = STATS_min
samplesMax = STATS_max
# Freedman–Diaconis formula for bin-width size estimation
lowQuartile = STATS_lo_quartile
upQuartile = STATS_up_quartile
IQR = upQuartile - lowQuartile
width = 2*IQR/(N**(1.0/3.0))
bin(x) = width*(floor((x-samplesMin)/width)+0.5) + samplesMin
plot \
samples u (bin(\$1)):(1.0/(N*width)) t "Output" w l lw 1 smooth freq

Resources