3D ploter in python-tk with matplotlib.

Wishing to prove my assertion wrong on python-tk that piping python directly into tk/tcl interpreter is simple I tried to contradict myself by making a full GUI in matplotlib. Because, if you are not aware : matplotlib supports multi-target (Wx, Qt, gtk, tk, html) multi-platform widgets (Button, checkbox, text entry and so much more).

The challenge seemed pretty easy, to assemble an easy demo with simple example : Thus, the added value resided in letting people fill in the min/max/step information for the relevant dimensions.

Without the colorbar I would have just been slightly annoyed by the slowness of the reaction of matplotlib as a GUI, but the colorbar posed a new challenge because it would either stack for each drawing or make plt.clf/ax.cla erase too much (see this great resource on when to use cla/clf in matplotlib).

So ... I tried python-tk with matplotlib knowing all too well that you can embed natively matplotlib in tkinter interface.

And since it was working I kept it.

Here is a screenshot of the interface :
WARNING this code should not be let in inventive hands (such as bored teenagers) because there is an evil eval; it requires to be in care of consenting adults.

Some highlights of the code :
  • bidir python-tk requires setting the Popen PIPEs to non blocking and using select.select on the output
  • matplotlib is unusable in non blocking mode : once matplotlib has the focus you need to destroy it to plot another function
  • from np import * is evil, but it let you have access to all array oriented math function (sin, cos, exp, ...)
This said, we have a pretty compact code of 128 lines of code that is pretty more reactive than using matplolib's widget for a 3D ploter.

Strong suspicions that the game of life might not be a good base for a PRNG : Takens killed my hopes

Previously, I was having high hopes of doing open science on PRNG (Pseudo Random Number Generator) based on the game of life on an hexagonal board. However, today, after a little bit of investigations I have bad and good news on the topic.

The bad news



Takens series might have killed my hopes, but it did it before I invested much time



Takens' series are a useful tool for evaluating the quality of randomness. When choosing this method I did know what to reach as a goal, I had a compass, and this compass was filling the space phase uniformely without patterns : f(x) => f(x+1) should not show signs of preference for space. Seeing my tweaked game of life with enhanced self injected entropy converge to a Sierpinsky gasket. Maybe it's not totally bad given I don't know it's lyapounov exponent (aka fractal dimension), but I refuse to let anyone use a method that displays strongly biased entropy in the space phase.

Good randomness for scientific usage or cryptographic one should be evenly distributed. Here is a pertubed game of life, with self injection of « chaos » into itself displaying a Sierpinky Gasket as a strange attractor, followed by a test sample of consecutive deterministric pseudo random numbers which source is the random python's PRNG.



Normally, I can conclude here with : « don't let friends use biased randomness and don't roll your own crypto ».

But I won't : first and foremost I want to higlight that keeping a clear view of what you do is important and knowing fast when you fail is as much postive experience as succeeding slowly.



The good news



The importance of visualisation



Since the beginning of the experiment as a python-tk proof of concept I added a visual interface. Here is the interface :


I added the possibility not only to see in real time the Takens series, but also the non overlapping evolutions of the pertubed game of life with the original one (level of gray coding the discrepancies).

I can testify I detected A LOT OF bugs through this. Manipulating code is easy, and making mistakes even easier. By keeping a view of the initial problem I kept a strong way of checking everything was making sense.

Data visualisation is the second compass with Takens series that helped me save a lot of time to not conclude falsely I made a unique discovery.

python-tk is bearing the load



Every cycle, I communicate back and forth between tk and python, python sending up to 2500 tcl commands in less than 1 second to update de canvas. Just for this it proved useful in giving me trust in the concept.

I was often asked why not tkinter or pure tcl ?

Well, python has data structures such as sets that supports operations that maps nicely with the concepts used in bit arithmetic (xor, and, or ...) that tcl does not have.
In python I can also additionnaly call matplotlib if I want to do 3D scatters or use the very performant numpy arrays. On the other hand, I have experience in Tk/Tcl and have a better grasp of how I deal with GUI in Tk rather than in tkinter. It's a question of comfort not a question of « one best way ». And it was fun to code this way without to much abstractions in the way.

Is the experiment over ?



Yes, it is time consuming.

However, I strongly suspect and would like to check in the future, that the strange attractor in the shape of a Sierpinsky Gasket is due to the symetry in the first neighbours in an hexagonal shape. I would -if I was paid for it explore other kinds of neighbouroud and rules.

For instance we could add a second ring of neighbours and maybe use them as anti-ferromagnetic rules. Maybe I would use random neighbourhood to check if the Sierpinsky gasket disappears .... and so long.

The nice part for this experimentations would be that I have the tooling that is ready.

Annexe the code



The code is available here as a gist on github

Can game of life be used as a pseudo random generator ? Maybe so.

After having fun doing bidirectionnal python <=> tk programming I thought that an hexagonal game of life experiment would be a nice way to saturate the PIPE from python to tcl to assert how much load the concept could take.

So I made a game of life with a twist inspired from this article about using strange attractors to qualify randomness.

Conway's game of life is a pretty simple example of cellular automata. The tradition version is on a square grid, here we not only change for an hexagonal grid, we also let users change the rules and we add a feature : we plot in real time a graph with u(n-1) on the x-axis and u(n) on the y-axis every turn and I'll explain why later.

But first let's look at a classical run with the normal rules : it's boring because it converges pretty fast to oscillators.
. But, by tweaking rules, you can have all kind of boring oscillators, patterns that repeat themselves with often a period of only two.
And also configurations for which we cannot tell it they are periodical :

The tool

But actually, let's have a look at the complete interface for playing with hexagonal game of life :
As you can see, you can set the rules for both dead cells that gives birth to new one, or lively cells that spreads. As a result, it becomes interesting to explore when the game of life does not converge.

It has an assymetry due to the coding techniques of python to pure tk for which python output is priorized over tk input, hence the feedback from the pure tk interfaces that may take several full long seconds.

Takens serie usage



Game of life being a deterministic game given a configuration Cn: if we already see it we can assert with certainty what Cn+1 will be. Hence if you plot Cn, Cn1 on respectively the x and y axis you can ascertain that if new points appear in the space phase you have not yet entered a period. It is a pretty darn KISS (Keep it Simple Stupid) method for evaluating not only if we hit a repetition but also biases in the space phase.
As refered at the beginning of this post in lcamtuf post on exploring randomness : a good PRNG (pseudo random number generator) should not only have a very long period it should cover the space phase equiprobably. And you know what tool is very good at seeing non uniformity? Your eyes coupled with your brain.

Spoiler alert : if I show you this : you are gonna tell me : hum ... Why does your space phase tends to looks like sierpinsky triangles?



My greek teacher used to say the pĥilosophs are not the one who gives smart answers but those who asks clever questions. And here you are touching an interesting point. Is the sierpinsky pattern inherent to randomness ? No. Is it inherent to topology ? Maybe. Is hit inherent to the hexagonal variation on the game of life ? Surely. Is it due to the Periodic Condiditions at the Borders ? No (because patterns who don't evolve from configurations touching the borders also have it).

In one simple eye analysis we have an interesting fact : suites of numbers generated by using game of life have a signature in the space phase that reflects the topology (here hexagonal).
Even though it my not be obvious at first sight :


Qualitative analysis of the hexagonal game of life for use as a PRNG



After a little bit of trial and errors we can notice that when we set for either dead or living cells to 3, 4, 5 we have often more long lasting sequences. As you can see there for alive = 4,5,6, dead = 2,3,6, it is also very sensitive to initial conditions making it tougher to analyse by differential analysis.



Even though it seems to do a pretty good job at generating a long list of non repeating numbers, however, as is it as the following problem :

evaluating entropy seeding



Given intial entropy we may enter the following problems :
  • Initial configuration may converge fast to a null of full one configuration not evolving anymore
  • Can we quantify how much pseudo entropy we generate ?
  • How much round of non repeating numbers do we have given a known size of grid and given suitable rules ?
  • Which rules generates the maximum chaos ? Are there equivalent sets of rules we can switch too to keep the same entropy level ?
  • Real life randomness accepts to repeat preceding seen values, how much the non repetition (sign of a non converging serie) is a weakness ?


Thinking of designing a PRNG based on hexagonal game of life



Real life cryptography have good practices like regenerating an initialization vector every round to avoid leaking information since ... enigma. Here, we could actually use the entropy we generate to alter ever the configuration or the rules in a deterministic way that would actually make the Takens serie cover the full space uniformely showing no sign of priviledging a siperinsky triangle kind of shape.

Actually, Takens series are an helpful tool in determining the quality of randomness so we already have one tool available.

Also, game of life is a fairly easy to code and understand algorihtm, thus, by lowering the entry barrier to coding the chore of the engine we have more eyeballs that can spot bugs and contribute to a better solution.

As they are easy to code, they are also easy to synthethize as an ASIC or FGPA, bringing an easy gain in acceleration by going full hardware with algorithm that being intrinsicly massively parallel will be tougher to brake with thread based computing, and mathematically it's a non linear system for which theory provides fewer tools of analysis.

Talking about FGPA we also can study not only the alteration of configuration but explore the alteration of topology breaking the hexagonal symmetry that may be the root cause of the sirpinksy triangle in the space phase.

I think game of life as a base for a PRNG is a funny topic of hobby research that could empower a lot of amateurs in this field building not only better algorithms but also dispelling myth and obscurantism around this field of computer science.

Writing an interactive tcl/tk interpreter proxy to wish in python

Maybe, you want to experiment small stuffs in wish (the tcl/tk) interpreter because of a post claiming that direct python tcl/tk is simpler in some simple cases than tkinter.

As a convinced tkinter/FreeSimpleGUI user, I see this as an extreme claim that requires solid evidences.

When all is said and done, wish interpreter is not interactive, and for testing simple stuff it can get annoying very fast. Thus, it would be nice to add readline to the interface.

So here is a less than 100 line of code exercice of soing exactly so while having fun with : readline and multiprocessing (I would have taken multithreading if threads were easy to terminate).

Readline I quote
The readline module defines a number of functions to facilitate completion and reading/writing of history files from the Python interpreter.
Basically, it adds arrow navigation in history, back search with Ctrl+R, Ctrl+K for cuting on the right, Ctrl+Y for yanking ... all the facilities of interaction you have in bash or ipython for instance.

We are gonna use multiprocessing because tcl/tl is event oriented, hence, asynchronuous hence, we may have string coming from the tcl stdout while we do nothing and we would like to print them.

We also introduce like in ipython some magic prefixed with # (comment in tcl) like #? for the help. A session should look like this :
# pack [ button .c -text that -command { puts "hello" } ]
# 
tcl output> hello # here we pressed the button "that"
tcl output> hello # here we pressed the button "that"


# set name 32
# puts $name

tcl output> 32

# #?

#l print current recorded session
#? print current help
#! calls python code like
  #!save(name="temp") which saves the current session in current dir in "temp" file
bye exit quit quit the current session

# #l
pack [ button .c -text that -command { puts "hello" } ]
set name 32
puts $name

# #!save("my_test.tcl")
# quit
The code in itself is fairly easy to read the only catch is that wish accepts multiline input. I can't because I don't know how to parse tcl. As a result I « eval in tcl » every line to know if there is an error and ask politely tcl to do the job of signaling the error with a « catch/error » (the equivalent of python try + raise an exception).
#!/usr/bin/env python3
# -*- coding: utf8 -*-

from subprocess import Popen, PIPE, STDOUT
from multiprocessing import Process
import sys, os
import atexit
import os
import readline
from select import select
from time import sleep

### interactive session with history with readline
histfile = os.path.join(os.path.expanduser("~"), ".wish_history")
try:
    readline.read_history_file(histfile)
    # default history len is -1 (infinite), which may grow unruly
    readline.set_history_length(-1)
except FileNotFoundError:
    pass

### saving history at the end of the session
atexit.register(readline.write_history_file, histfile)

### opening wish
wish = Popen(['wish'], 
        stdin=PIPE,
        stdout=PIPE,
        stderr=PIPE,
        bufsize=-1,
        )

os.set_blocking(wish.stdout.fileno(), False)
os.set_blocking(wish.stderr.fileno(), False)
os.set_blocking(wish.stdin.fileno(), False)

def puts(s):
    out = f"""set code [ catch {{ {s} }} p ]
if {{$code}} {{ error $p }}
"""
    select([], [wish.stdin], [])
    wish.stdin.write(out.encode())


def gets():
    while True:
        wish.stdout.flush()
        tin = wish.stdout.read()
        if tin:
            print("\ntcl output> " + tin.decode())
        sleep(.1)

def save(fn="temp"):
    with open(fn,"wt") as f:
        f.write(session)

session=s=""
def load(fn="temp"):
    global session
    with open(fn, "rt") as f:
        while l:= f.readline():
            session+=l + "\n"
            puts(l)


# async io in tcl requires a background process to read the input
t =Process(target=gets, arwish=())
t.start()

while True:
    s = input("# ")
    if s in { "bye", "quit", "exit" }:
        t.terminate()
        wish.stdin.write("destroy .".encode())
        break
    elif s == "#l":
        print(session)
    elif s == "#?":
        print("""
#l print current recorded session
#? print current help
#! calls python code like
  #!save(name="temp") which saves the current session in current dir in "temp" file
  #!load(name="temp") which load the session stored in current dir in "temp" file
bye exit quit quit the current session
""" )
        continue
    elif s.startswith("#!"):
        print(eval(s[2:]))
        continue
    else:
        puts(s)
        if err:=wish.stderr.readline():
            sys.stderr.write(err.decode())
        else:
            if s and not s.startswith("#"):
                session += s + "\n"

This code is available on pypi as iwish (interactive wish) and the git link is in the README.

Theming a python to tcl/tk (hence without tkinter) application

As a foreword, tkinter users should have a look at ttkbootstrap which is unique to tkinter and is an awesome flat theme for tk.

But, I will instead admit that tcl/tk default is ugly by default ...


Except for windows ...


I am not a designer, but I am not blind either. I noticed that flat (aka without relief) designs age fairly well compared to the others.

So, we want themes, and why ? Because they can help provide a consistent cross platform look and feel. Here is an exemple on windows linux, freeBSD with the breeze theme :
The most part of the code has been publish in the post about making python and tcl/tk talk bidirectionnaly without tkinter. I am gonna focus on the theme part of the code which is mainly. Python users may want to look this posts since the available themes for python that can be installed with
pip install ttkthemes
have a good documentation on how to use ttkthemes and how they look but does not include all the available themes and where to get them.

The same themes as pypi ttkthemes can be obtained on debian with
apt install tcl-ttkthemes
and they are stored in /usr/share/tcltk/ttkthemes/themes/. From there we can use tcl that has its good sides to build a combobox for choosing a theme :
set themes_dir [ glob -directory  /usr/share/tcltk/ttkthemes/themes/ -type d * ]
set themes [ list ]
foreach dir $themes_dir {
    lappend themes [ file tail $dir ]
}
ttk::combobox .cb -textvariable theme -values [ lsort $themes ] -width 12
.cb state readonly
bind .cb <<ComboboxSelected>> { 
    set theme [%W get]
    catch {
    source /usr/share/tcltk/ttkthemes/themes/$theme/$theme.tcl }} pass
    ttk::style theme use $theme
}
catch { code } result
is the equivalent of try:/except, except you capture the exception in result and catch return 1 on error. the bind part call a proc to change the theme. With this and a few more widgets and bindings we can appreciate that definitively the breeze theme that is not provided by default is the best one ^_^

The essential



A ttk theme is fairly easy to use in tcl:
  1. spot its tcl file. ex aquativo.tcl
  2. source it ONCE
     source /usr/share/tcltk/ttkthemes/themes/aquativo/aquativo.tcl 
  3. use it :
    ttk::style theme use aquativo
  4. use only the ttk:: widgets in your tcl code
And that's all.

Annexe: full code



#!/usr/bin/env python
from subprocess import Popen, PIPE
from time import sleep, time, localtime
import os

# let's talk to tk/tcl directly through p.stdin
p = Popen(['wish'], stdin=PIPE, stdout=PIPE)

os.set_blocking(p.stdout.fileno(), False)
os.set_blocking(p.stdin.fileno(), False)

def puts(s):
    for l in s.split("\n"):
        p.stdin.flush()
        p.stdin.write((l + "\n").encode())
        p.stdin.flush()

def gets():
    ret=p.stdout.read()
    p.stdout.flush()
    return ret

WIDTH=HEIGHT=500

puts(f"""

canvas .c -width {WIDTH} -height {HEIGHT} -bg white
pack .c
.c configure -background white
ttk::frame .h
pack .h -fill both -expand true -padx 0 -pady 0
ttk::frame .g 
pack .g -in .h -fill x -expand true -padx 0

ttk::button  .ba -command {{  puts ch-=1 }} -text <<
ttk::button .bb -command {{  puts cm-=1 }} -text <
ttk::button .bc -command {{  puts ch+=1 }} -text >>
ttk::button .bd -command {{  puts cm+=1 }} -text >
pack .ba .bb  -side left -anchor w -in .g
pack .bc .bd  -side right -anchor e -in .g

set themes_dir [ glob -directory  /usr/share/tcltk/ttkthemes/themes/ -type d * ]
set themes [ list ]
foreach dir $themes_dir {{
    lappend themes [ file tail $dir ]
}}
ttk::combobox .cb -textvariable theme -values [ lsort $themes ] -width 12
.cb state readonly
bind .cb <<ComboboxSelected>> {{ 
    set theme [%W get]
    catch {{
    source /usr/share/tcltk/ttkthemes/themes/$theme/$theme.tcl }} pass
    ttk::style theme use $theme
}}
pack .cb  -in .g
ttk::frame .f 
pack .f -in .h -expand 1 -anchor s -padx 0 
set h 0
set cm 0
set s 0

ttk::label .l -text "sample of label"
ttk::entry .i -text "sample of input" -textvariable theme
pack .l .i -anchor w -in .f -padx 5 -pady 5
ttk::scale .s  -from 0 -to 24 -variable h 
ttk::button .bt -text Quit -command "destroy ."
ttk::spinbox .sb -from -60 -to 60 -textvariable cm -width 5 -command {{ puts "cm=$cm" }}
ttk::progressbar .pb -maximum 60 -variable s
pack .s .sb .pb  -in .f -side left -anchor se -padx 5 -pady 5 
pack .bt -in .h -anchor s -pady 5 -padx 5
""")

# Constant are CAPitalized in python by convention
from cmath import  pi as PI, e as E
ORIG=complex(WIDTH/2, HEIGHT/2)

# correcting python notations j => I
I = complex("j")
rad_per_sec = 2.0 * PI /60.0
rad_per_min = rad_per_sec / 60
rad_per_hour= rad_per_min / 12

origin_vector_hand = WIDTH/2 *  I

size_of_sec_hand = .9
size_of_min_hand = .8
size_of_hour_hand= .65

rot_sec = lambda sec  : -E ** (I * sec  * rad_per_sec)
rot_min = lambda min  : -E ** (I * min  * rad_per_min)
rot_hour= lambda hour : -E ** (I * hour * rad_per_hour)

to_real = lambda c1,c2 : "%f %f %f %f" % (c1.real,c1.imag,c2.real, c2.imag)
for n in range(60):
    direction= origin_vector_hand * rot_sec(n)
    start=.9 if n%5 else .85
    puts(f".c create line {to_real(ORIG+start*direction,ORIG+.95*direction)}")
    sleep(.01)

diff_offset_in_sec = (time() % (24*3600)) - \
    localtime()[3]*3600 -localtime()[4] * 60.0 \
    - localtime()[5]
ch=cm=0
n=0
while True:
    n+=1
    # eventually parsing tcl output
    t = time()
    s= t%60
    m = m_in_sec = t%(60 * 60) + cm * 60
    h = h_in_sec = (t- diff_offset_in_sec)%(24*60*60) + ch * 3600 + cm * 60 

    if back := gets():
        back = back.decode()
        exec(back)

    puts(".c delete second")
    puts(".c delete minute")
    puts(".c delete hour")
    if n%10==0:
        puts(f"set s {int(s%60)}")
        puts(f"set cm {cm}")
        puts(f"set h {h/3600}")
    n%=100
    c0=ORIG+ -.1 * origin_vector_hand * rot_sec(s)
    c1=ORIG+ size_of_sec_hand * origin_vector_hand * rot_sec(s)
    puts( f".c create line {to_real(c0,c1)} -tag second -fill blue -smooth true")
    c1=ORIG+size_of_min_hand * origin_vector_hand * rot_min(m)
    puts(f".c create line {to_real(ORIG, c1)} -tag minute -fill green -smooth true")
    c1=ORIG+size_of_hour_hand * origin_vector_hand * rot_hour(h)
    puts(f".c create line {to_real(ORIG,c1)} -tag hour -fill red -smooth true")
    puts("flush stdout")
    sleep(.15)

Bidirectionnal python/tk by talking to tk interpreter back and forth

Last time I exposed an old way learned in physical labs to do C or python/tk like in the old days: by summoning a tcl/tk interpreter and piping commands to it.

But what fun is it?

It's funnier if the tcl/tk interperpreter talks back to python :D as an hommage to the 25 years awaited TK9 versions that solves a lot of unicode trouble.

Beforehand, to make sense to the code a little warning is required : this code targets only POSIX environment and loses portability because I chose to use a way that is not the « one best way » for enabling bidirectionnal talks. By using os.set_blocking(p.stdout.fileno(), False) we can have portable non blocking IO, which means this trick has been tested on linux, freeBSD and windows successfully. It's pretty much advised when using non blocking IO to use select.select to check if file descriptors are ready to use.

First and foremost, the Popen now use p.stdout=PIPE enabling the channel on which tcl will talk. As a joke puts/gets are named from tcl/tk functions and are used in python to push/get strings from tcl.

Instead of using multithreading having one thread listen to the output and putting the events in a local queue that the main thread will consume I chose the funniest technique of setting tcl/tk output non blocking which does not work on windows. This is the fnctl part of the code.

Then, I chose not to parse the output of tcl/tk but exec it, making tcl/tk actually push python commands back to python. That's the exec part of the code.

For this I needed an excuse : so I added buttons to change minutes/hours back and forth.

That's the moment we all are gonna agree that tcl/tk that tcl/tk biggest sin is its default look. Don't worry, next part is about using themes.

Compared to the first post, changes are minimal :D This is how it should look :
And here is the code, largely still below 100 sloc (by 3 lines).
#!/usr/bin/env python
from subprocess import Popen, PIPE
from time import sleep, time, localtime
import select
# import fcntl
import os

# let's talk to tk/tcl directly through p.stdin
p = Popen(['wish'], stdin=PIPE, stdout=PIPE)

# best non portable answer on stackoverflow
#fd = p.stdout.fileno()
#flag = fcntl.fcntl(fd, fcntl.F_GETFL)
#fcntl.fcntl(fd, fcntl.F_SETFL, flag | os.O_NONBLOCK)
# ^-- this 3 lines can be replaced with this one liner --v
# portable non blocking IO
os.set_blocking(p.stdout.fileno(), False)

def puts(s):
    for l in s.split("\n"):
        select.select([], [p.stdin], [])
        p.stdin.write((l + "\n").encode())
        p.stdin.flush()

def gets():
    ret=p.stdout.read()
    p.stdout.flush()
    return ret

WIDTH=HEIGHT=400

puts(f"""
canvas .c -width {WIDTH} -height {HEIGHT} -bg white
pack .c
. configure -background white

ttk::button  .ba -command {{  puts ch-=1 }} -text <<
pack .ba -side left   -anchor w
ttk::button .bb -command {{  puts cm-=1 }} -text  <
pack .bb -side left -anchor w
ttk::button .bc -command {{  puts ch+=1 }} -text >> 
pack .bc  -side right -anchor e
ttk::button .bd -command {{  puts cm+=1 }} -text > 
pack .bd  -side right -anchor e
""")

# Constant are CAPitalized in python by convention
from cmath import  pi as PI, e as E
ORIG=complex(WIDTH/2, HEIGHT/2)

# correcting python notations j => I  
I = complex("j")
rad_per_sec = 2.0 * PI /60.0
rad_per_min = rad_per_sec / 60
rad_per_hour = rad_per_min / 12

origin_vector_hand = WIDTH/2 *  I

size_of_sec_hand = .9
size_of_min_hand = .8
size_of_hour_hand = .65

rot_sec = lambda sec : -E ** (I * sec * rad_per_sec )
rot_min = lambda min : -E ** (I *  min * rad_per_min )
rot_hour = lambda hour : -E ** (I * hour * rad_per_hour )

to_real = lambda c1,c2 : "%f %f %f %f" % (c1.real,c1.imag,c2.real, c2.imag)
for n in range(60):
    direction= origin_vector_hand * rot_sec(n)
    start=.9 if n%5 else .85
    puts(f".c create line {to_real(ORIG+start*direction,ORIG+.95*direction)}")
    sleep(.01)

diff_offset_in_sec = (time() % (24*3600)) - \
    localtime()[3]*3600 -localtime()[4] * 60.0 \
    - localtime()[5] 
ch=cm=0
while True:
    # eventually parsing tcl output 
    back = gets()
    # trying is more concise than checking
    try:
        back = back.decode()
        exec(back)
    except Exception as e:
        pass

    t = time()
    s= t%60
    m = m_in_sec = t%(60 * 60) + cm * 60
    h = h_in_sec = (t- diff_offset_in_sec)%(24*60*60) + ch * 3600 + cm * 60
    puts(".c delete second")
    puts(".c delete minute")
    puts(".c delete hour")
    c0=ORIG+ -.1 * origin_vector_hand * rot_sec(s)
    c1=ORIG+ size_of_sec_hand * origin_vector_hand * rot_sec(s)
    puts( f".c create line {to_real(c0,c1)} -tag second -fill blue -smooth true")
    c1=ORIG+size_of_min_hand * origin_vector_hand * rot_min(m)
    puts(f".c create line {to_real(ORIG, c1)} -tag minute -fill green -smooth true")
    c1=ORIG+size_of_hour_hand * origin_vector_hand * rot_hour(h)
    puts(f".c create line {to_real(ORIG,c1)} -tag hour -fill red -smooth true")
    puts("flush stdout")
    sleep(.1)


Some history about this code.



I have been mentored in a physical lab where we where doing the pipe, fork, dup2 dance to tcl/tk from C to give a nice output to our simulations so we could control our instuition was right and could extract pictures for the publications. This is a trick that is almost as new as my arteries.
My mentor used to say : we are not coders, we need stuff to work fast and neither get drowned in computer complexity or endless quest for « the one best way » nor being drowned in bugs, we aim for the Keep It Simple Stupid Ways.

Hence, this is a Keep It Simple Stupid approach that I revived for the sake of seeing if it was still robust after 35 years without using it.

Well, if it's robust and it's working: it ain't stupid even if it isn't the « one best idiomatic way ». :P

Simpler than PySimpleGUI and python tkinter: talking directly to tcl/tk

Well, the PySimpleGUI rug pulling of its licence reminded me how much dependencies are not a good thing.

Even though FreeSimpleGUI is a good approach to simpler tk/tcl binding in python : we can do better, especially if your linux distro split the python package and you don't have access to tkinter. I am watching you debian, splitting ALL packages and breaking them including ... tcl from tk (what a crime).

Under debian this stunt requires you to install tk :
apt install tk8.6


How hard is it when tcl/tk is installed to do GUI programming in tk without tkinter?

Well, it's fairly easy, first and foremost coders are coders, they code in whatever language. If you do code in one language you can't do docker, simple sysadmin tasks (shell), compile C extensions (make syntax) or web applications (HTML + javascript). Hence, learning more than one language is part of doing python applications.

How hard is coding in tcl/tk natively?

Fairly easy: its difficulty is a little above lua, and way below perl thanks to the absence of references.

What value tcl have ?

It's still used in domain specific field such as VLSI (Very Large Scale Integration of electronic component).

So here is the plan : we are gonna do an application that do the math in python which is perfect for expressing complex math in more readable way than tcl and push all the GUI to the tk interpreter (albeit wish).

We are gonna make a simple wall clock ... and all tcl commands are injected to tcl through the puts function.
#!/usr/bin/env python
from subprocess import Popen, PIPE
from time import sleep, time, localtime

# let's talk to tk/tcl directly through p.stdin
p = Popen(['wish'], stdin=PIPE)

def puts(s):
    for l in s.split("\n"):
        p.stdin.write((l + "\n").encode())
        p.stdin.flush()

WIDTH=HEIGHT=400

puts(f"""
canvas .c -width {WIDTH} -height {HEIGHT} -bg white
pack .c
. configure -background "white"
""")

# Constant are CAPitalized in python by convention
from cmath import  pi as PI, e as E
ORIG=complex(WIDTH/2, HEIGHT/2)

# correcting python notations j => I  
I = complex("j")
rad_per_sec = 2.0 * PI /60.0
rad_per_min = rad_per_sec / 60
rad_per_hour = rad_per_min / 12

origin_vector_hand = WIDTH/2 *  I

size_of_sec_hand = .9
size_of_min_hand = .8
size_of_hour_hand = .65

rot_sec = lambda sec : -E ** (I * sec * rad_per_sec )
rot_min = lambda min : -E ** (I *  min * rad_per_min )
rot_hour = lambda hour : -E ** (I * hour * rad_per_hour )

to_real = lambda c1,c2 : "%f %f %f %f" % (c1.real,c1.imag,c2.real, c2.imag)
for n in range(60):
    direction= origin_vector_hand * rot_sec(n)
    start=.9 if n%5 else .85
    puts(f".c create line {to_real(ORIG+start*direction,ORIG+.95*direction)}")
    sleep(.1)

diff_offset_in_sec = (time() % (24*3600)) - \
    localtime()[3]*3600 -localtime()[4] * 60.0 \
    - localtime()[5] 

while True:
    t = time()
    s= t%60
    m = m_in_sec = t%(60 * 60)
    h = h_in_sec = (t- diff_offset_in_sec)%(24*60*60)
    puts(".c delete second")
    puts(".c delete minute")
    puts(".c delete hour")
    c0=ORIG+ -.1 * origin_vector_hand * rot_sec(s)
    c1=ORIG+ size_of_sec_hand * origin_vector_hand * rot_sec(s)
    puts( f".c create line {to_real(c0,c1)} -tag second -fill blue -smooth true")
    c1=ORIG+size_of_min_hand * origin_vector_hand * rot_min(m)
    puts(f".c create line {to_real(ORIG, c1)} -tag minute -fill green -smooth true")
    c1=ORIG+size_of_hour_hand * origin_vector_hand * rot_hour(h)
    puts(f".c create line {to_real(ORIG,c1)} -tag hour -fill red -smooth true")
    sleep(.1)

Next time as a bonus, I'm gonna do something tkinter cannot do: bidirectional communications (REP/REQ pattern).