Saturday 27 May 2023

gnuplot palette for interference pictures

My shitty codes, part 1.

Suppose you want to visualize some complicated superpositions of sine (or other) waves, spot their constructive and destructive interference, keep track of the sign of the result and not care too much about the actual value. Linear color map or "palette" is rather useless here—in order to get a readable picture, you'd have to send a very weak wave, its amplitude would be very close to the zero (white) regions of your map.

I found no examples of built-in gnuplot palettes that would give something nice according to my taste and needs, so here's what I did: (1) fire Inkscape and create a gradient rectangle (the svg file cannot be added here, but png would work just as good):

and (2) add a bunch of numbers every step. Then comes (3) which is the stupidest part of the job: rescaling the rectangle so that these numbers are adjusted to the horizontal scale (nevermind the units) in Inkscape's window (I guess it's standard for programs for vector graphics to have such a ruler somewhere),  making it comfortable (and boring/frustrating!) to "loop" over the rectangle and collect the 33 color codes, using some other rectangle and setting its fill color using the pick-color selection. If you're into numerology, 33 is purely accidental.

Then I wrote a shitty script in an even more shitty language, and got a linear palette, like

set palette defined(-10.0 '#0301fe', -9.393939393939394 '#021cdf', -8.787878787878787 '#0237bf', -8.181818181818182 '#01539e', -7.575757575757576 '#016e7f', -6.969696969696971 '#008a5e', -6.363636363636363 '#04a545', -5.757575757575758 '#1db857', -5.151515151515151 '#36ca6a', -4.545454545454545 '#37cb6a', -3.9393939393939394 '#6af190', -3.333333333333333 '#83ffa3', -2.727272727272727 '#9effb6', -2.121212121212121 '#b5ffc8', -1.5151515151515158 '#ceffda', -0.9090909090909083 '#e6ffec', 0.0 '#ffffff', 0.9090909090909083 '#ffffde', 1.5151515151515158 '#fffebe', 2.121212121212121 '#fffe9f', 2.727272727272727 '#fffd7d', 3.333333333333333 '#fffd7e', 3.9393939393939394 '#ffe74a', 4.545454545454545 '#ffcb37', 5.151515151515151 '#ffaf24', 5.757575757575758 '#ff9311', 6.363636363636363 '#ff7800', 6.969696969696971 '#ff6400', 7.575757575757576 '#ff5000', 8.181818181818182 '#ff3c00', 8.787878787878787 '#ff2800', 9.393939393939394 '#ff1400', 10.0 '#ff0000')

supposing that my z-axis-to-be range would be [–10,10]. The script expects a symmetric interval of float numbers, divides it evenly and selects white (#ffffff) to represent z = 0. Such naive (linear) mapping suffers from the above described problem—the near-to-white region of the z-axis is too wide for my purposes, like here—waves with amplitudes smaller than, say 4 or 5, would be barely visible:


To save myself the pain of changing the gradient using mouse (and perhaps clicking through it again and copying and pasting color codes. as I said, it's boring but probably faster even for 33 colors than finding some appropriate program that would do that quickly), I thought about changing my shitty script a little—to make the map non-linearand got something better, 

set palette defined(-10.0 '#0301fe', -8.426272039497363 '#021cdf', -7.203207828070985 '#0237bf', -6.2169434823154734 '#01539e', -5.3977002712995725 '#016e7f', -4.7002706840325805 '#008a5e', -4.094018979439592 '#04a545', -3.5573892333332457 '#1db857', -3.074714348928315 '#36ca6a', -2.6342728999643885 '#37cb6a', -2.2270573040626522 '#6af190', -1.845964683787546 '#83ffa3', -1.4852476934266692 '#9effb6', -1.1401297208707324 '#b5ffc8', -0.8065261428492804 '#ceffda', -0.48083469544786817 '#e6ffec', 0.0 '#ffffff', 0.48083469544786817 '#ffffde', 0.8065261428492804 '#fffebe', 1.1401297208707324 '#fffe9f', 1.4852476934266692 '#fffd7d', 1.845964683787546 '#fffd7e', 2.2270573040626522 '#ffe74a', 2.6342728999643885 '#ffcb37', 3.074714348928315 '#ffaf24', 3.5573892333332457 '#ff9311', 4.094018979439592 '#ff7800', 4.7002706840325805 '#ff6400', 5.3977002712995725 '#ff5000', 6.2169434823154734 '#ff3c00', 7.203207828070985 '#ff2800', 8.426272039497363 '#ff1400', 10.0 '#ff0000')

and the following picture:


Now even the originally emitted waves are well-visible, before they interfere with some reflected waves. The trick is to use e.g. the tan function for z-range. (Which means we apply arctan to colors—a well-known and easy to use smooth approximation for the sign function that goes steeply through zero and later becomes flat, using the engineers' language.)

This is the result in action—the original wavefronts have just passed the figure of a bee or fly or whatever fucking insect is there at the center, and there's a bunch of reflected waves, interfering with themselves and with the incoming waves:


 

Here goes the shitty code in an even more shitty language. Works for me, but it's very basic and probably buggy. Enjoy, modify etc.:

# only tan is used at the moment.

from math import atan, pi, tan, sin, fabs

# the 33 colors from the palette gradient. 
# perhaps could have been extracted by some program 
# which I don't know of - ...
 
colors = [
    '0301fe',
    '021cdf',
    '0237bf',
    '01539e',
    '016e7f',
    
    '008a5e',
    '04a545',
    '1db857',
    '36ca6a',
    '37cb6a',

    '6af190',
    '83ffa3',
    '9effb6',
    'b5ffc8',
    'ceffda',

    'e6ffec',
    'ffffff', # this is white!
    'ffffde',
    'fffebe',
    'fffe9f',

    'fffd7d',
    'fffd7e',
    'ffe74a',
    'ffcb37',
    'ffaf24',

    'ff9311',
    'ff7800',
    'ff6400',
    'ff5000',
    'ff3c00',

    'ff2800',
    'ff1400',
    'ff0000'    
]

# makes no sense if f is non-monotonic.
def map(f, x, A):
    return float(f(k*x)/f(k*A))

a = 10.
 
# prevents tan(x) from blowing up:
k = (2./pi+0.5)/a
 
# z-range step
da = (2.*a) /float(len(colors))
 
# find the position of white in the gradient:
i0 = colors.index('ffffff')
 
# value-color pairs:
palette = {)
 
# choose any monotonic function you like.
f = lambda x: tan(x)
 
# fill the "left" (i.e. z < 0) half of the palette
for i in range(0, i0):
    value = a*map(f, -a + float(i)*da, a)
    palette[value] = colors[i]
 
# assign white to z = 0;
palette[0.] = colors[i0]
 
# fill the "right" half of the palette (z > 0)
right = len(colors) - i0

for i in range(1, right):
    value = a* map(f, a - float(right - i - 1)*da, a)
    palette[value] = colors[i0 + i] 
 
# create gnuplot set palette command:
print('set palette defined(', end='')
for x in palette:
    print(str(x) + " '#" + palette[x] + "', ", end='')
 
# delete the last space and comma, add the closing parenthesis:
print('\b\b)')

No comments:

Post a Comment