0

DISCLAIMER: Unsure if title even fits and this is my first post so I'm learning how to organize this

CODE BACKSTORY:

I create fractal flames (iterated images) using Apophysis7x and open the .flame file (this file is not rendered, it simply holds the information to open in another editor) in Fractal Fr0st where I can automate for example, an iteration to be constantly rotated thus creating an animation. Fractal Fr0st uses python for such automations however they're constant. I produce music and hope to someday complete my code below to have the rotations alternate based on the BPM of my song. I started to code on my own to produce a tick tock sequence before even editing the actual python file associated with the program (that code is not entirely important for now, but is posted at the end)

INFO SO FAR ON PROBLEM:

I know right off the bat Python is not the fastest language out there, but I am not 100% sure if my code is just not efficient enough to keep up with what I am trying to have output. Please take a look at my code and comments as I try to explain as thorough as I can what is going on.

timer.py:

import time

#Feel free to test as you'd like
#Suppose we have a song at 140 Beat Per Minute (BPM) -> There is approximately 2.33 Beats Per Second (BPS)
#So it takes approximately 0.43 (s) per beat
#So  if freq = 0.43, leng = *length of song*
#This program should alternate printing "tick" "tock" (or some action that I hope to code in the future)
#The Tick Tock output should come out every (in this case) .43 seconds

class timer(object):
    "takes in a frequency and duration of loop in seconds"

    def __init__(self,freq,leng):
        "input a frequency and duration in seconds"
        #rounded to 2 decimal places

        self.freq= round (freq,2)
        self.leng= round (leng,2)

    def ticker (self):
        "Starts the time and loop to be broken when max time is reached"

        counter = 0 #recorded out of curiosity to check just how many loops were made overall
        ticker = 0 #alternates the "tick" "tock"
        initTime = time.time() #records the initial upon starting the loop
        rnded = 0 #initial rounded time set at 0 *see the first if statement below*


        while True:

            stop = time.time() #The time recorded during the loop *stopwatch so-to-speak*
            passed = stop - initTime #The time passed from initial launch to time during the loop

            if rnded == self.leng:
                "When the time that has passed reaches time limit print stats and break"

                return print("Loops:".ljust(10).rjust(14) + "Initial Time:".ljust(17).rjust(21) + "Time Stopped:".ljust(17).rjust(21)+ "Passed:".ljust(11).rjust(15) + "Total Ticks:".ljust(16).rjust(20) + "\n" + (str (counter) ).ljust(10).rjust(14) +(str (initTime) ).ljust(17).rjust(21)+ (str (stop) ).ljust(17).rjust(21)+(str (rnded) ).ljust(11).rjust(15) + (str (ticker) ).ljust(11).rjust(15) )

            elif round(passed,2) == rnded:
                "Prevents duplicates, ie: previous stopped at .9999 (1.0) and current loop sopped at 1.001 (1.0) skip" 
                #If your current value happened to be rounded to the previous value don't continue

                #uncomment bellow for debugging
                #print ("*TEST* \t Initiated: ", initTime, "\t Time stopped: ",stop, "\nTime passed: ", rnded, "\t Tick Counter: ", ticker, "\n ---")
                pass

            else:
                "Tick tock code"

                rnded = round(passed,2) #now set current time differnce value after being checked for duplicates
                if rnded % self.freq == 0 and rnded != 0:

                    "if the time passed mod the frequency is 0 continue to execute tick tock"

                    if ticker % 2 == 0:
                        #gives stats for debugging purposes, delete, after "tick" / "tock" if desired
                        #prints in the meantime in the future I would like an action to be done with music in another program as precise as possible

                        print ("Tick \t Initiated: ", initTime, "\t Time stopped: ",stop, "\nTime passed: ", rnded, "\t Tick Counter: ", ticker, "\n ---")

                    elif ticker % 2 == 1:
                        print ("Tock \t Initiated: ", initTime, "\t Time stopped: ",stop, "\nTime passed: ", rnded, "\t Tick Counter: ", ticker, "\n ---")

                    ticker +=1 #only increases if tick/tock was processed        

        counter += 1 #counts how many times loop was accessed, use this as you'd like

    def chngFreq(self, freq):
        'Changes current frequency'
        self.freq = round (freq,2)

    def chngLeng(self,leng):
        'Changes current duration'
        self.leng = round(leng,2)

Example 1:

The output skips multiple "tick" / "tock" as the frequency gets faster (I was just testing for example when the frequency was at .01(s) and total time of 4(s), there should be a total of 399 ticks yet it only made it to 5 ticks. My theory is that since it does take time to go through the loop (however a very small amount of time) it may skip a tick due to some probability that before getting to the next tick from .01 to .02 before it was being rounded it could've been a tick, but the code wasn't processed fast enough. I rounded till the end to be as accurate as possible and made the "elif" portion to prevent duplicates (see code above)

Output:

>>>s = timer(.01,4)
>>>s.ticker()
Tick     Initiated:  1502409793.6775877      Time stopped:  1502409793.6937597 
Time passed:  0.02   Tick Counter:  0 
---
Tock     Initiated:  1502409793.6775877      Time stopped:  1502409793.7562232 
Time passed:  0.08   Tick Counter:  1 
---
Tick     Initiated:  1502409793.6775877      Time stopped:  1502409793.8409605 
Time passed:  0.16   Tick Counter:  2 
 ---
Tock     Initiated:  1502409793.6775877      Time stopped:  1502409793.9940765 
Time passed:  0.32   Tick Counter:  3 
---
Tick     Initiated:  1502409793.6775877      Time stopped:  1502409794.9579115 
Time passed:  1.28   Tick Counter:  4 
---
Loops:        Initial Time:        Time Stopped:        Passed:        Total Ticks:    
5473896      1502409793.6775877    1502409797.680171    4.0            5

Please note that occasionally the tick is only counted for 2^x ie: .01, .02, .04, .08, .16, .32... Instead of: .01, .02, .03, .04, .05, .06...

I know this is not the example that I gave in my code but it is true whenever I try a more approximate frequency such as .23 or .01, yet the larger the frequency regardless of precision ie: 4.29 it will be more likely to accurately return the "ticks" 4.29, 8.58, 12.87 etc...

Example 2

In the following example you can tell it works perfectly fine as it is given longer time to process between ticks

>>>s = timer(1,4)
>>>s.ticker()
Tick     Initiated:  1502411198.8088021      Time stopped:  1502411199.8091898 
Time passed:  1.0    Tick Counter:  0 
---
Tock     Initiated:  1502411198.8088021      Time stopped:  1502411200.8089292 
Time passed:  2.0    Tick Counter:  1 
---
Tick     Initiated:  1502411198.8088021      Time stopped:  1502411201.81057 
Time passed:  3.0    Tick Counter:  2 
---
Tock     Initiated:  1502411198.8088021      Time stopped:  1502411202.8121786 
Time passed:  4.0    Tick Counter:  3 
---
Loops:        Initial Time:        Time Stopped:        Passed:        Total Ticks:    
5586557      1502411198.8088021   1502411202.8328226    4.0            4

Please let me know if any of this code is unclear, but please be sure to read each comment first and run the code/modify if needed.

Once my previous problem is resolved I hope to modify the "if x.animate:" section of the following code that is used in Fractal Fr0st.

default.py:

update_flame = False

while True:
    for x in flame.xform:
        if x.animate:
            x.rotate(-3)
    preview()

Finally thanks a million in advance I know this is really long but I am truly trying to understand what is going on with python/how it operates, regardless of what my end goal is.

Also if any other methods are faster/more efficient please let me know. Thanks!!!

BHerna
  • 1
  • 3
  • It looks like your fundamental problem here is that you are comparing floating-point numbers for exact equality. That is mere coincidence in the rare cases where it happens: floats simply do not represent any particular number, they are ranges. Many of the numbers you're using, such as 0.01, are not exactly representable as a float in the first place, and are only going to get further away from the proper value as you do math with them. – jasonharper Aug 11 '17 at 01:28
  • Interesting, thank you for clarifying that. I know the difference between a float and an int, but what you just mentioned has really got me thinking. As I am also starting to code in Java I now see the emphasis and use in a double rather than a float itself, while I know python does not have a double could you also elaborate more between those? Finally, how might I resolve that issue with comparing floats with precise values such as 0.01 and how exactly do programmers go about coding given precise values (0.01)? Thanks again! – BHerna Aug 11 '17 at 02:31
  • Python float is the same as a Java double - there's enough overhead in each Python object that it wouldn't be worth the effort to support both 32- and 64-bit floating-point values. The smaller size might be relevant if you're working with arrays of millions of values, in which case you'd want to use a module such as `numpy` to efficiently deal with such arrays. – jasonharper Aug 11 '17 at 02:37
  • OK so I did some research and came across `numpy` and `scipy` so I don't know which to get, but then in one of the comments [here](https://stackoverflow.com/questions/6200910/relationship-between-scipy-and-numpy) this was answered: _Last time I checked it, the scipy `__init__` method executes a `from numpy import *`_ which means that it basically entails all of `numpy` ? – BHerna Aug 11 '17 at 03:17
  • @jasonharper “floats simply do not represent any particular number, they are ranges” is absolutely false. Each finite float does represent a particular number, but the float resulting from a float computation generally differs from the real that would result applying the same operations on the reals. – Pascal Cuoq Aug 11 '17 at 09:14

1 Answers1

0

After a bit of research and some simple messing around, not only did I find some what of a solution (I found the real problem and is still occurring), but it also made my code A LOT SHORTER. Take a look.

NEW AND IMPROVED CODE:

import time

#Feel free to test as you'd like
#Suppose we have a song at 140 Beat Per Minute (BPM) -> There is approximately 2.33 Beats Per Second (BPS)
#So it takes approximately 0.43 (s) per beat
#So  if freq = 0.43, leng = *length of song*
#This program should alternate printing "tick" "tock" (or some action that I hope to code in the future)
#The Tick Tock output should come out every (in this case) .43 seconds

class timer2(object):
    "takes in a frequency and duration of loop in seconds"

    def __init__(self,freq,leng):
        "input a frequency and duration in seconds"
        #rounded to 2 decimal places

        self.freq= round (freq,2)
        self.leng= round (leng,2)

    def ticker (self):
        "Starts the time and loop to be broken by freq"

        self.ticker = 0 #alternates the "tick" "tock"
        self.passed = 0 

        while round (self.passed,2) != self.leng:

            time.sleep(self.freq)

            if self.ticker % 2 == 0:
            #prints in the meantime in the future I would like an action to be done with music in another program as precise as possible

                print ("***TICK*** \t Tick Counter: ", self.ticker)

            elif self.ticker % 2 == 1:

                print ("***TOCK*** \t Tick Counter: ", self.ticker)

            self.ticker +=1 #only increases if tick/tock was processed

            self.passed += self.freq
            round (self.passed,2)

            print ("Time passed: ", self.passed, "(s)","\n ---")

        print ("Ticks: \t Time Passed: \n {} \t {}(s)".format(self.ticker,self.passed))

    def chngFreq(self, freq):
        'Changes current frequency'
        self.freq = round (freq,2)

    def chngLeng(self,leng):
        'Changes current duration'
        self.leng = round(leng,2)

Please note the many round() methods as I will explain in a bit.

My basic input:

>>>s = timer2(1,4)
>>>s.ticker()

This works as expected: 1,2,3,4. However, going back to what I wanted initially, if my frequency is small such as .43 it will not accurately work. I include the first and last 4 outputs for a uniform frequency of .01.

OUTPUT:

FIRST FOUR:

>>>s = timer2(.01,4)
>>>s.ticker()
***TICK***   Tick Counter:  0
Time passed:  0.01 (s) 
---
***TOCK***   Tick Counter:  1
Time passed:  0.02 (s) 
---
***TICK***   Tick Counter:  2
Time passed:  0.03 (s) 
---
***TOCK***   Tick Counter:  3
Time passed:  0.04 (s) 
---

LAST FOUR:

***TICK***   Tick Counter:  396
Times passed:  3.9699999999999593 (s) 
---
***TOCK***   Tick Counter:  397
Times passed:  3.979999999999959 (s) 
---
***TICK***   Tick Counter:  398
Times passed:  3.989999999999959 (s) 
---
***TOCK***   Tick Counter:  399
Times passed:  3.9999999999999587 (s) 
---
Ticks:   Time Passed: 
 400     3.9999999999999587(s)

Note how at the very end the time passed should be 4 (s), just as it is when the frequency is 1. Also why is the time passed a really ugly float? It was fine for the first four after that it gives the long decimals. I will open up another thread giving a simple loop (I still would like for this to be revised).

Python is not adding correctly and it's quite annoying... .31 + .01 should be: .32 and onward, .34, .35, .36, .37, etc. Even after rounding the time to 2 decimals places, its not working. ie. say even if I can't fix the ugly floats (.310000015684668), rounding it should be (.31), yet Python won't execute this.

***IF I ELIMINATE ALL THE ROUNDING I STILL GET THE LONG FLOATS, SO IT IS NOT THE ROUND () METHOD TO BEGIN WITH***

BHerna
  • 1
  • 3