Search This Blog

Showing posts with label Python. Show all posts
Showing posts with label Python. Show all posts

Thursday, November 24, 2022

Python Weather app - current conditions from the national weather service

 So, I was reading through a course on XML from W3 schools and in there, it mentioned that current conditions are available as an XML file for various locations.  So I investigated it.  After a bit, I thought I should make a Python program to pull the information from the National Weather Service in XML format and then display this information to the user.

It took some time to refamiliarize myself on the use of the urllib functions in Python to make an http request and download the text of the response.  Then, I needed to learn how to use the XML functions to parse the XML to retrieve the values for each of the tags.  My solution is quite fragile in that any changes to the file format by the National Weather Service will break the code.  Nonetheless, I was quite pleased with myself when I had finally got this working!  So I thought I would share it with the world.

While I don't think that this is going to revolutionize anything, maybe you can re-use some of the code.  Probably the list of the different Oregon locations is the most useful thing.

Here's what the user sees after hitting Fetch:


Wednesday, November 23, 2022

Shopping list and recipe app development plan

 So this is going to be the first of a series of posts that are going to follow along developing a desktop application (mostly cause I don't have an IDE in which I can create a mobile app) that will store recipes, allow the user to make meal plans, and then based on those meal plans create shopping lists for all the ingredients.

The overall plan is going to include:'

  • Defining the requirements that the software has to fulfill from a user perspective.  This would be the functionality that the user expects the software to perform.
  • Defining the technical approach that is going to be used to fulfill the user's requirements defined in the previous bullet.
  • Create a test script to be used to ensure that the software is functioning adequately.  Each of the tests will check that one or more of the requirements have been fulfilled.
  • Perform the testing and then do any required remediation to correct any discovered test failures.
  • At this point, I may end up discussing what additional or changed user requirements could be used for the next version of the software.  But let's not get ahead of myself.
So what are the user requirements?  While I'd like to use a table here, I'd have to create it by writing raw HTML as the blog's UI isn't accommodating of tables so you will have to bear with me as I present it as a numbered list.  While the numbered list will not allow me to, within the list, add a prefix, for any cross-references, pretend that Recipe requirements are prefixed with an R, Ingredient requirements are prefixed with an I, Meal Plan requirements are prefixed with an M, Shopping list requirements are prefixed with an S, and general requirements with a G.

Recipe module

  1. The user will enter for each recipe:
    1. A title
    2. A category for the recipe
    3. The number of servings the recipe makes
    4. One or more ingredients
    5. One or more cooking steps
  2. Recipe categories can be created, edited, and deleted.
  3. Recipe categories will be pre-populated with:
    1. Entrée
    2. Side
    3. Appetizer
    4. Dessert
    5. Snack
  4. Do not allow Recipe Category to be deleted if it is used in an existing dish.  Advise the user of at least one recipe title that they need to address if they try to delete in this case.
  5. For each ingredient, the user will specify:
    1. Quantity to be used
    2. Unit of Measurement of consumption quantity
  6. For each cooking step, the user will specify:
    1. Step number
    2. Text of the step instructions
  7. Recipes can be exported.
  8. Recipes can be imported.

Ingredient Module

  1. For each ingredient, the user will enter:
    1. Ingredient name
    2. Purchase Quantity
    3. Purchase Unit of measurement
    4. Zero or more unit of measurement conversions
  2. Ingredients can be exported.
  3. Ingredients can be imported.
  4. Ingredients can be created, edited, and deleted.
  5. Ingredients cannot be deleted if they are used in a Recipe.

Meal Plan Module

  1. The user can populate the meal plan module with a meal record consisting of:
    1. A Recipe
    2. The number of people dining
    3. The date of the planned meal
  2. An export can be created for a date range showing all the meals planned within that date range.  The export will be a table modeled to simulate a calendar.  Also a multi-level list format will be available.
  3. Meal records can be created, edited, and deleted.

Shopping list Module

  1. The user can create a shopping list for a user-selected date range.  This will produce a shopping list for all the ingredients that will be consumed in the preparation of all the meals in the selected date range for the indicated number of diners and indicate to the next whole purchase Unit of measurement.

General requirements

  1. The user can create different databases.
  2. The user can open a specific database.
  3. There are no security requirements.
In writing this, I've had some thoughts already about other behaviours I'd like the program to have, but I think this is already enough to get on with.  The next post, hopefully within the coming week will discuss the technical approach I will take to providing the functionality defined above.

Saturday, April 11, 2020

Random Alien Writing

I've been thinking about this for some time and finally found some time to write the program.  The program is at the bottom of the post and written with Python 3.8.  Basically, it creates a canvas filled with strange looking writing.  I was inspired by some of the examples of "alien" writing used in various sci-fi movies and TV shows.  I thought to myself, should be easy to create a program that can create examples of this kind of writing.  I re-used the basic framework I had developed for the program to simulate cellular automata. (I have re-used the framework for some other bits I've made for myself, not (yet) posted here and it is becoming one of my favorites.)  My first attempts, frankly, looked like white noise on a screen.  Over a couple of days I refined the parameters to get it closer to what the brain will recognize as having some semantic content and organization and the current version is pretty good.  I've used it to generate the background I've added to the blog.  I hope you like it!  I'm not going to go into a detailed explanation of the program elements, but if you have questions about it, post a comment.

The Program




from tkinter import *
from random import randint

class App:
    """This class is derived, I think, from a TK class and is designed to
    work with the TK environment.
    Requires imports from Tkinter, math, and random"""
    def __init__(self, master):
        """This class is the whole of the application """
        # Necessary Frames
        frame = Frame(master)
        control_frame = Frame(frame)
        control_frame.grid(row = 0, column = 0, sticky = N)
        canvas_frame = Frame(frame)
        canvas_frame.grid(row =0, column = 1)

        #Application variables
        self.char_pts = [(1,1),(7,1),(14,1),(1,7),(7,7),(14,7),(1,14),(7,14)
                         ,(14,14)]
        self.chars = []
        self.text = []
        self.tmpstr = '\nFor another,\nHit Randomize'
        
        #Control FrameWidgets
        self.lab = Label(control_frame, text=self.tmpstr)
        self.lab.pack(side = TOP)
        self.b1 = Button(control_frame, text='Randomize')
        self.b1.config(command=self.randomize)
        self.b1.pack()
        
        # The Canvas
        self.canvas = Canvas(canvas_frame, width = 800, height = 800)
        self.canvas.config(bg='white')
        self.canvas.pack()
        frame.pack()

        #Menu
        menubar = Menu(root)
        menu_1 = Menu(menubar, tearoff=0)
        menu_1.add_command(label='Quit',command=root.destroy)
        menubar.add_cascade(label='File', menu=menu_1)
        master.config(menu=menubar)
        self.randomize()

    def randomize(self):
        self.canvas.delete(ALL)
        # create characters
        self.chars = []
        for j in range(10):
            new_char = []
            b = randint(0,8)
            for i in range(randint(2,7)):
                a = b
                b = (a + randint(1,8))%9
                new_char.append((a,b,randint(1,2)))
            self.chars.append(new_char)
        # create text, in 2-7 letter long words.
        tl = 0
        self.text = []
        while tl <3000:
            wl = randint(2,7)
            for k in range(wl):
                self.text.append(randint(1,len(self.chars)-1))
            tl = tl + wl + 1
            self.text.append(0)
        # now write the text using the characters
        i = 0
        j = 0
        for char in self.text:
            if char != 0:
                for seg in self.chars[char]:
                    start = seg[0]
                    end = seg[1]
                    wt = seg[2]
                    x1 = i*15+self.char_pts[start][0]
                    y1 = j*17+self.char_pts[start][1]
                    x2 = i*15+self.char_pts[end][0]
                    y2 = j*17+self.char_pts[end][1]
                    if x1==x2 or y1==y2:
                        self.canvas.create_line(x1,y1,x2,y2,width=wt)
                    else:
                        self.canvas.create_arc(x1,y1,x2,y1,width=wt)
            if i == 50:
                j = j + 1
                i =0
            else:
                i= i + 1
        self.canvas.update()

if __name__ == '__main__':
    root = Tk()
    root.wm_title('Alien Text')
    app = App(root)
    root.mainloop()
     


Thursday, April 9, 2020

About "random" numbers

It is an interesting thing that if you were to ask some person to give you a "random" 6-digit number, they would tend to try really hard to not repeat digits.  People have a tendency to think that random number do not have repeating digits.  But an analysis of these numbers shows that, in fact, for longer numbers, it starts to be more likely that there are repeated digits than not.  Below is a table of the number of digits and the likelihood that there are repeating digits.
(argh, excuse me but there is not a control to create a table!!)
Number of digits vs. likelihood of repeated digits:

  1. None
  2. 9.99%
  3. 28%
  4. 50%
  5. 70%
  6. 85%
  7. 94%
  8. 98%
  9. 99%
  10. nearly 100%
So the psychology going on here is that people have a sense in 2 digit and maybe 3 digit numbers that repeated digits in a number are rarer than unique.  However, in reality, when you get to 5 and 6 digit numbers, most numbers have repeated digits.
The math of permutations and combinations is explained here .
The python math module functions are explained here.  The ones that are relevant are the factorial, comb and perm functions.  I wrote some code to re-create these.  I found out that after 10 levels of recursion, Python gives you an error message.  So I had to use the math module version of the factorial function.

# This defines the permutation function
# and the combination function

from math import factorial

def perm(n,r):
    return factorial(n)/factorial(n-r)

def comb(n,r):
    return factorial(n)/factorial(n-r)/factorial(r)

for i in range(2,11):
    print(i,1-perm(10,i)/10**i)

Also, if you don't believe the math, I also wrote a piece of code that does a brute force calculation.  It does take some time to work its way through all the different combinations.  And it will not give the exact same results since it is not considering the numbers that start with 0.
 
# This program prints out how many of the numbers in a range
# have repeating digits

n = 2
flag = True
while flag:
    test = 10**(n-1)
    count = 0
    print(test)
    while test < 10**n:
        a = str(test)
        dupe = False
        for i in range(10):
            if a.count(str(i))>1:
                dupe = True
        if dupe:
            count = count + 1
        test = test +1
    ratio = count / 10**n
    print(n,count, ratio)
    ask = input("continue? (n to end) >")
    if ask == 'n':
        flag = False
    n = n+1
So it's an interesting bit of psychology so I hope you enjoy it.             

Thursday, December 26, 2019

Social Security Administration Baby Name Database

Introduction

At this link, you can access the SSA's so-called Baby Name data broken down both nationally and by state.  Each file is a zip file.  When you unzip the State file, it gives you a text file named for each 2-letter state code.  These files use a comma separated values format and each line includes, the state (which seems to me to be redundant), the sex, the year, the name and number of births.  The national data files are broken down by year.  In each year's file, again formatted in CSV, it includes the name, the sex and the number of births.  I downloaded these files and decided I wanted to make a program that would show a graph for a given name, for a given locale over the years.  In the program, I used Python's CSV module to read the file data into lists of lists.  One thing is that CSV defaults every field to strings so you have to convert things to floats.

The UI

So the UI has been made with Tkinter.  It consists of an Entry for the name, a couple of radio selectors for sex, a OptionMenu for the state, a button to generate the graph and a bunch of labels.
I used an Entry for the name information.  For the sex, so as to reduce user entry errors, I used radio buttons.  Again, to ensure data validity in calls to subroutines, I used an OptionMenu object (usually called a dropdown).

The Action

Basically, everything starts after the user hits the Graph button.  This then calls the calc function.  The calc function figures out whether the user selected a national or state level request.  If it is national, then the code calls the national function, if it is state level then it calls the bystate function.
Both the national and bystate functions go and read the appropriate files to build two lists which have corresponding x and y values to be graphed.  These both then call the graph function which uses PyPlot API calls to draw and display the graph in a separate window.  Typical results are below and of course, there's no reason why I would have picked the name Martin for the examples.


The Code

## Code to process downloaded national name data - Using GUI

from tkinter import *
from tkinter import ttk
import matplotlib.pyplot as plt
import csv

filelocation = ##INSERT STRING OF WHERE YOUR FILES ARE

## This function reads the information from the national files.
## Data is Name, M/F, number of births
def national(the_name,sex):
    xdata=[]
    ydata=[]
    for year in range(1880,2019):
        file = open(filelocation+"yob"+str(year)+'.txt', newline='')
        r = csv.reader(file)
        for row in r:
            if row[0]==the_name and row[1]==sex:
                xdata.append(float(year))
                ydata.append(float(row[2]))
        file.close()
    graph(xdata,ydata,the_name,sex,"USA")


## This function reads the information from the State files.
## Data is State,Sex (M/F), Year,Name, Number of births
def bystate(the_name,sex,state):
    xdata=[]
    ydata=[]
    file=open(filelocation+state+'.txt',newline='')
    r = csv.reader(file)
    for row in r:
        if row[3]==the_name and sex==row[1]:
            xdata.append(float(row[2]))
            ydata.append(float(row[4]))
    file.close()
    graph(xdata,ydata,the_name,sex,state)
            
## This part creates the graph using Matplotlib (plt)
def graph(xdata,ydata,the_name,sex,state):
    fig,ax = plt.subplots()
    line1, = ax.plot(xdata,ydata,label=the_name)
    ax.legend(loc='upper left')
    ax.set_title('Births per year in '+state+': '+the_name+' ('+sex+')')
    plt.ylabel('Number of births')
    plt.xlabel('Year')
    plt.show()

def calc():
    mf = ['M','F']
    if sel_state.get()=='USA':
        national(getn.get(),mf[sel_sex.get()])
    else:
        bystate(getn.get(),mf[sel_sex.get()],sel_state.get())
## Below is the data for the state/national selection dropdown
states=['USA','AK','AL','AR','AZ','CA','CO','CT','DC','DE','FL','GA',
        'HI','IA','ID','IL','IN','KS','KY','LA','MA','MD','ME',
        'MI','MN','MO','MS','MT','NC','ND','NE','NH','NJ','NM',
        'NV','NY','OH','OK','OR','PA','RI','SC','SD','TN','TX',
        'UT','VA','VT','WA','WI','WV','WY']
##Below is the set up for the GUI window
root = Tk()
content = ttk.Frame(root)
frame = ttk.Frame(content)
sel_state = StringVar()
sel_sex = IntVar()
lblinstr = ttk.Label(content, text="Enter Name and location")
getn= ttk.Entry(content, text="Name")
male= ttk.Radiobutton(content, text='Male', variable=sel_sex, value=0)
female=ttk.Radiobutton(content, text='Female',variable=sel_sex,value=1)
ok = ttk.Button(content, text="Graph", command=calc)
rlbl = ttk.Label(content, text="Enter Name")
slbl = ttk.Label(content, text="Enter Sex (M/F)")
stlbl = ttk.Label(content, text="Select USA or state")
statedd = ttk.OptionMenu(content, sel_state, *states)
##Below, we put the GUI together
content.grid(column = 0, row = 0)
frame.grid(column=0, row=0)
lblinstr.grid(column=0, row=0)
rlbl.grid(column=0, row=1)
getn.grid(column=1, row=1, sticky=N)
slbl.grid(column=0, row=2)
male.grid(column=1, row=2, sticky=N)
female.grid(column=2, row=2)
statedd.grid(column=1, row=3)
ok.grid(column=0, row=4)
## And run!!
root.mainloop()

Sunday, December 22, 2019

Creating graphs of Federal Reserve GDP data

After having read some interesting speculations about the future of work, AI, and robotics, I ran across a free resource in the form of the St. Louis Federal Reserve bank FRED system.  You do have to sign up with a valid email address, but there is no charge and the government is, well, supposed to spam or sell your email address.
After I had signed up, I configured to download some data.  This was the total annualized quarterly GDP as well as the goods and services components, see the picture below which is a screen shot of data from the FRED system.
The download was a text file, it had a header row and was delimited by tabs.  This is not ideal, but it is something that the csv Python standard library can handle.  The idea was to use matplotlib to graph the data.  I could have tried to create the graph on a Tkinter canvas using the draw functions.  But I am getting ahead of myself...
First we need to read the data and create Lists for the x and y values of the two different lines.  I will run through the code I wrote.  First we need to open the text file:
file = open(filelocation, newline='')
r = csv.reader(file,delimiter='\t')
Then I will initialize variables:
period=[]
goods=[]
services=[]
header=True
The variable header is there so that we can skip over the header row during the loop which reads the data and creates the iterables that will be used to graph the data.  The Pyplot api expects NumPy arrays for the data to be graphed but Lists and most other iterables work.
for row in r:
    if header:
        header=False
    else:
        date=row[0]
        serv=float(row[1])
        good=float(row[2])
        total=serv+good
        dateval=float(date[0:4])+float(date[5:7])/12
        period.append(dateval)
        goods.append(good/total*100)
        services.append(serv/total*100)

Now at the start I had imported matplotlib.pyplot as plt, and the next part of the code creates the graph and shows it using rather simple matplotlib calls.  I did look up a lot of stuff on this in the examples and tutorials, but it is not very clear so I struggled a bit to get it to work right.
fig,ax = plt.subplots()
line1, = ax.plot(period,goods,label='Goods')
line2, = ax.plot(period,services,label='Services')
ax.legend(loc='upper left')
ax.set_title('Change in GDP composition over time')
plt.ylabel('% of GDP')
plt.xlabel('Year')
plt.show()
Here's the final product:

What does this mean?

In this graph we see a long-terms trend wherein the American economy has been more and more reliant on services instead of goods.  We don't make things any more, or maybe a better way to put it, we make things with such high efficiency and productivity that to feed, house, cloth, provide transport and other utilities, some one third of us can provide for everyone.  So we have to find something for the other 2/3rds to do.  That turns out to be providing any and all kinds of services to the general population.  This trend has been around for a while and it probably extends to the left back to the turn of the century although likely it flattens out.  During that time, many have treated it like the weather: something to complain about, but nothing can be done about it.  Even the current politicians are not going to be able to reverse this trend no matter how hard they try.  About the only possible strategy would be to do everything to limit or even reverse the growth in population.  However, this would have other significant adverse consequences, putting the economy into a depression.
One take-away is that if you are starting a new business, concentrate on services rather than goods.  If you have a goods-related business, then consider adding some kind of service provision to complement what you make.  Growth in Services is much more likely, if not easier.

Friday, January 4, 2019

Python version of Game of Life Cellular Automata

Hi again everyone!  I've been busy and have made a nice Game of Life/Cellular Automata program in Python.  It took a couple of hours but here it is.  I used the TK interface for the windows and not a whole lot else, it is, after all a pretty simple program. 

Here are a few screen shots:


I think the red with black borders is pretty.



Here's the program:

from Tkinter import *
from random import randint
from time import sleep
class App:
    """This class is derived, I think, from a TK class and is designed to
    work with the TK environment.
    Requires imports from Tkinter, math, random and time"""
    def __init__(self, master):
        """This class is the whole of the application """
        # Necessary Frames
        frame = Frame(master)
        control_frame = Frame(frame)
        control_frame.grid(row = 0, column = 0, sticky = N)
        canvas_frame = Frame(frame)
        canvas_frame.grid(row =0, column = 1)
        #Application variables
        self.is_running = 0
        self.grid_data = []
        for i in range(160):
            self.grid_data.append([])
            for j in range(160):
                self.grid_data[i].append(0)
        self.color_code = 1
        # 0 = not used, 1 = blue, 2 = black, 3 = red, 4 = green
        self.colors = ['pink','blue','black','red','green']
        self.speed = 5
        self.speeds = [5,2,1,0.2,0.02]
        self.speed_str = ['Really slow','Slower','Slow','Fast','Faster',
                           'Max Speed']
        self.tmpstr = 'Speed:\n'+self.speed_str[self.speed]
       
        #Control FrameWidgets
        self.lab = Label(control_frame, text=self.tmpstr)
        self.lab.pack(side = TOP)
        self.b1 = Button(control_frame, text='Randomize')
        self.b1.config(command=self.randomize)
        self.b1.pack()
        self.b2 = Button(control_frame, text='Start')
        self.b2.config(command=self.go_live)
        self.b2.pack()
       
       
        # The Canvas
        self.canvas = Canvas(canvas_frame, width = 800, height = 800)
        self.canvas.config(bg='white')
        self.canvas.pack()
        frame.pack()
        #Menu's
        menubar = Menu(root)
        menu_1 = Menu(menubar, tearoff=0)
        menu_1.add_command(label='Quit',command=root.destroy)
        menubar.add_cascade(label='File', menu=menu_1)
        menu_2 = Menu(menubar, tearoff=0)
        menu_2.add_command(label='Really slow', command=self.menu_2)
        menu_2.add_command(label='Slower', command=self.menu_3)
        menu_2.add_command(label='Slow', command=self.menu_4)
        menu_2.add_command(label='Fast', command=self.menu_5)
        menu_2.add_command(label='Faster', command=self.menu_6)
        menu_2.add_command(label='Max Speed', command=self.menu_7)
        menubar.add_cascade(label='Speed', menu=menu_2)
        master.config(menu=menubar)
        menu_4 = Menu(menubar, tearoff = 0)
        menu_4.add_command(label='Blue', command=self.color_blue)
        menu_4.add_command(label='Black', command=self.color_black)
        menu_4.add_command(label='Red', command=self.color_red)
        menu_4.add_command(label='Green', command=self.color_grn)
        menubar.add_cascade(label='Color', menu=menu_4)
    def randomize(self):
        """ This function deletes the current data and seeds with a new
        set of randomly placed dots"""
        if self.is_running == 0:
            for i in range(160):
                for j in range(160):
                    self.grid_data[i][j] = 0
            for i in range(2400):
                x = randint(0,159)
                y = randint(0,159)
                self.grid_data[x][y] = 1
            self.canvas.delete(ALL)
            self.update_once()
    def go_live(self):
        """This function stops and starts the simulation."""
        if self.is_running == 0:
            self.b2.config(text='Stop')
            self.is_running = 1
            self.run_sim()
        else:
            self.b2.config(text='Start')
            self.is_running = 0
    def update_once(self):
        """This function draws the screen"""
        for x in range(160):
            for y in range(160):
                if self.grid_data[x][y] == 1:
                    self.canvas.create_rectangle(x*5,y*5,x*5+4,y*5+4,
                            fill=self.colors[self.color_code])
        self.canvas.update()
    def run_sim(self):
        """This is the main loop for the simulation.  Calc new grid,
        copy it, then calls update_once to redraw the screen."""
        new_grid = [] # local variable to store intermediate result
        for i in range(160):
            new_grid.append([])
            for j in range(160):
                new_grid[i].append(0)
        while self.is_running == 1:
            for i in range(160):
                for j in range(160):
                    neigh = self.grid_data[i-1][j-1]
                    neigh += self.grid_data[i][j-1]
                    neigh += self.grid_data[(i+1) % 160][j-1]
                    neigh += self.grid_data[(i+1) % 160][j]
                    neigh += self.grid_data[(i+1) % 160][(j+1) % 160]
                    neigh += self.grid_data[i][(j+1) % 160]
                    neigh += self.grid_data[i-1][(j+1) % 160]
                    neigh += self.grid_data[i-1][j]
                    if neigh < 2 or neigh > 3: # cell dies
                        new_grid[i][j] = 0
                    elif neigh == 3: # cell lives or is born
                        new_grid[i][j] = 1
                    else: # cell is the same as previous tick
                        new_grid[i][j] = self.grid_data[i][j]
            for i in range(160):
                for j in range(160):
                    self.grid_data[i][j] = new_grid[i][j]
            self.canvas.delete(ALL)
            self.update_once()
            if self.speed < 5:
                sleep(self.speeds[self.speed])
    def color_grn(self):
        """This function sets the color of th elines to green and then
        refreshes the screen."""
        self.color_code=4
    def color_red(self):
        """This function sets the color of the lines to red and then
        refreshes the screen."""
        self.color_code=3
       
    def color_black(self):
        """This function sets the color of the lines to black and then
        refreshes the screen."""
        self.color_code=2
    def color_blue(self):
        """This function sets the color of the lines to blue and then
        refreshes the screen."""
        self.color_code = 1
    def menu_2(self):
        """ Sets speed to really slow"""
        self.speed = 0
        self.tmpstr = 'Speed:\n'+self.speed_str[self.speed]
        self.lab.config(text=self.tmpstr)
        self.lab.update()
    def menu_3(self):
        """ Sets speed to slower"""
        self.speed = 1
        self.tmpstr = 'Speed:\n'+self.speed_str[self.speed]
        self.lab.config(text=self.tmpstr)
        self.lab.update()
    def menu_4(self):
        """ Sets speed to Slow"""
        self.speed = 2
        self.tmpstr = 'Speed:\n'+self.speed_str[self.speed]
        self.lab.config(text=self.tmpstr)
        self.lab.update()
    def menu_5(self):
        """ Sets speed to Fast"""
        self.speed = 3
        self.tmpstr = 'Speed:\n'+self.speed_str[self.speed]
        self.lab.config(text=self.tmpstr)
        self.lab.update()
    def menu_6(self):
        """Sets speed to Faster"""
        self.speed = 4
        self.tmpstr = 'Speed:\n'+self.speed_str[self.speed]
        self.lab.config(text=self.tmpstr)
        self.lab.update()
    def menu_7(self):
        """ Sets speed to max speed"""
        self.speed = 5
        self.tmpstr = 'Speed:\n'+self.speed_str[self.speed]
        self.lab.config(text=self.tmpstr)
        self.lab.update()
if __name__ == '__main__':
    root = Tk()
    root.wm_title('Cellular Automata')
    app = App(root)
    root.mainloop()
       

Sunday, December 30, 2018

Python Program to create spirals

I was reading a book on artificial intelligence and in the book it mentioned the Archimedes spiral.  There was a drawing that was fairly interesting.  So I decided to see if I could draw it on the screen using a Python script.  After some reading on Wikipedia about different kinds of spirals, and some work, the result was the program below.
But first, some pictures of the results.  The first one is a set of Lituus curves.

This next one is a set of logarithmic spirals.

And a final taste, an Archimedean spiral:

The code below allows you to vary the color of the lines, the number of spirals displayed and what type.  The types include the ones shown above plus Fermat's spiral and hyperbolic spirals.




# Draws spirals on a canvas
from Tkinter import *
from math import *
from random import randint
from time import sleep
class App:
    """This class is derived, I think, from a TK class and is designed to
    work with the TK environment.
    Requires imports from Tkinter, math, random and time"""
    def __init__(self, master):
        """This class is the whole of the application """
        # Necessary Frames
        frame = Frame(master)
        control_frame = Frame(frame)
        control_frame.grid(row = 0, column = 0, sticky = N)
        canvas_frame = Frame(frame)
        canvas_frame.grid(row =0, column = 1, rowspan=2)
        status_frame = Frame(frame)
        status_frame.grid(row = 1, column =0, sticky = S)
        #Entry box variables
        self.cmd_var = IntVar()
        self.parm1_var = DoubleVar()
        self.parm2_var = DoubleVar()
        self.parm1_var.set(12.0)
        self.color_code = 0
        # 0 = random
        # 1 = blue
        # 2 = black
        # 3 = red
        # 4 = green
        self.spiral_type = 5
        # 1 = normal,
        # 2 = logarithmic,
        # 3 = Hyperbolic
        # 4 = Fermat's spiral
        # 5 = Lituus spiral
               
        #Control FrameWidgets
        self.tmpstr = 'Number of spirals:\n'+str(self.parm1_var.get())
        self.lab = Label(control_frame, text=self.tmpstr)
        self.lab.pack(side = TOP)
       
       
        # The Canvas
        self.canvas = Canvas(canvas_frame, width = 800, height = 800)
        self.canvas.config(bg='white')
        self.canvas.pack()
        self.clear()
        frame.pack()
        #Menu's
        menubar = Menu(root)
        menu_1 = Menu(menubar, tearoff=0)
        menu_1.add_command(label='Quit',command=root.destroy)
        menubar.add_cascade(label='File', menu=menu_1)
        menu_2 = Menu(menubar, tearoff=0)
        menu_2.add_command(label='2', command=self.menu_2)
        menu_2.add_command(label='3', command=self.menu_3)
        menu_2.add_command(label='4', command=self.menu_4)
        menu_2.add_command(label='5', command=self.menu_5)
        menu_2.add_command(label='6', command=self.menu_6)
        menu_2.add_command(label='7', command=self.menu_7)
        menu_2.add_command(label='8', command=self.menu_8)
        menu_2.add_command(label='9', command=self.menu_9)
        menu_2.add_command(label='10', command=self.menu_10)
        menu_2.add_command(label='11', command=self.menu_11)
        menu_2.add_command(label='12', command=self.menu_12)
        menubar.add_cascade(label='Spirals', menu=menu_2)
        master.config(menu=menubar)
        menu_3 = Menu(menubar, tearoff=0)
        menu_3.add_command(label='Archimedean', command=self.set_normal)
        menu_3.add_command(label='Logarithmic', command=self.set_log)
        menu_3.add_command(label='Hyperbolic', command=self.set_hyp)
        menu_3.add_command(label='Fermat', command=self.set_fermat)
        menu_3.add_command(label='Lituus', command=self.set_lituus)
        menubar.add_cascade(label='Type', menu = menu_3)
        menu_4 = Menu(menubar, tearoff = 0)
        menu_4.add_command(label='Random', command=self.color_ran)
        menu_4.add_command(label='Blue', command=self.color_blue)
        menu_4.add_command(label='Black', command=self.color_black)
        menu_4.add_command(label='Red', command=self.color_red)
        menu_4.add_command(label='Green', command=self.color_grn)
        menubar.add_cascade(label='Color', menu=menu_4)
    def color_grn(self):
        """This function sets the color of th elines to green and then
        refreshes the screen."""
        self.color_code=4
        self.clear()
    def color_red(self):
        """This function sets the color of the lines to red and then
        refreshes the screen."""
        self.color_code=3
        self.clear()
       
    def color_black(self):
        """This function sets the color of the lines to black and then
        refreshes the screen."""
        self.color_code=2
        self.clear()
    def color_ran(self):
        """This function sets the color of the lines to random and then
        refreshes the screen."""
        self.color_code = 0
        self.clear()
    def color_blue(self):
        """This function sets the color of the lines to blue and then
        refreshes the screen."""
        self.color_code = 1
        self.clear()
    def set_lituus(self):
        """This function sets the spiral type to Lituus and then
        refreshes the screen."""
        self.spiral_type = 5
        self.clear()
    def set_fermat(self):
        """This function sets the spiral type to Fermat and then refreshes
        the screen."""
        self.spiral_type = 4
        self.clear()
    def set_hyp(self):
        """This function sets the spiral type to hyperbolic and then
        refreshes the screen"""
        self.spiral_type=3
        self.clear()
    def set_normal(self):
        """This function sets the spiral type to Archimedean and then
        refreshes the screen"""
        self.spiral_type=1
        self.clear()
    def set_log(self):
        """This function sets the spiral type to Logarithmic and then
        refreshes the screen"""
        self.spiral_type=2
        self.clear()
    def clear(self):
        """This function clears the screen, calls the appropriate drawing
        function, and updates the message."""
        self.canvas.delete(ALL)
        if self.spiral_type ==1:
            self.draw_spiral()
        elif self.spiral_type ==2:
            self.draw_log()
        elif self.spiral_type ==3:
            self.draw_hyp()
        elif self.spiral_type ==4:
            self.draw_fermat()
        else:
            self.draw_lituus()
        self.tmpstr = 'Number of spirals:\n'+str(self.parm1_var.get())+\
        "\nType of Spirals:\n"
        types = ['Archimedean','Logarithmic','Hyperbolic',
                 'Fermat','Lituus']
        self.tmpstr = self.tmpstr + types[self.spiral_type -1]
        self.lab.config(text=self.tmpstr)
       
    def draw_spiral(self):
        """This is the drawing function for the Archimedean spiral"""
        for k in range(0,int(self.parm1_var.get())):
            x=0
            y=0
            if self.color_code == 0:
                tk_rgb = "#%02x%02x%02x" % (randint(0,255), randint(0,255),
                                        randint(0,255))
            else:
                tk_rgb = ['null','blue','black','red','green'][self.color_code]
            for r in range(0,400): #scaled 100 = pi
                theta = r*2*pi/200.0 - 2*k*pi/self.parm1_var.get()
                new_x = r*sin(theta)
                new_y = r*cos(theta)
                self.canvas.create_line(x+400,y+400,new_x+400,new_y+400,
                                        fill=tk_rgb)
                x = new_x
                y = new_y
            sleep(0.1)
            self.canvas.update()
        for k in range(0,int(self.parm1_var.get())):
            x=0
            y=0
            if self.color_code == 0:
                tk_rgb = "#%02x%02x%02x" % (randint(0,255), randint(0,255),
                                        randint(0,255))
            else:
                tk_rgb = ['null','blue','black','red','green'][self.color_code]
            for r in range(0,400): #scaled 100 = pi
                theta = -(r*2*pi/200.0 - 2*k*pi/self.parm1_var.get())
                new_x = r*sin(theta)
                new_y = r*cos(theta)
                self.canvas.create_line(x+400,y+400,new_x+400,new_y+400,
                                        fill=tk_rgb)
                x = new_x
                y = new_y
            sleep(0.1)
            self.canvas.update()
    def draw_log(self):
        """This is the drawing function for the logarithmic spiral"""
        for k in range(0,int(self.parm1_var.get())):
            x=0
            y=0
            if self.color_code == 0:
                tk_rgb = "#%02x%02x%02x" % (randint(0,255), randint(0,255),
                                        randint(0,255))
            else:
                tk_rgb = ['null','blue','black','red','green'][self.color_code]
            for r in range(10,1000,10):
                theta=log(r) - 2*k*pi/self.parm1_var.get()
                #r = exp(theta*2*pi/100.0 + 2*k*pi/self.parm1_var.get())
                new_x = r*sin(theta)*0.4
                new_y = r*cos(theta)*0.4
                self.canvas.create_line(x+400,y+400,new_x+400,new_y+400,
                                        fill=tk_rgb)
                x = new_x
                y = new_y
            sleep(0.1)
            self.canvas.update()
        for k in range(0,int(self.parm1_var.get())):
            x=0
            y=0
            if self.color_code == 0:
                tk_rgb = "#%02x%02x%02x" % (randint(0,255), randint(0,255),
                                        randint(0,255))
            else:
                tk_rgb = ['null','blue','black','red','green'][self.color_code]
            for r in range(10,1000,10):
                theta=-(log(r) - 2*k*pi/self.parm1_var.get())
                #r = -(exp(theta*2*pi/100.0 + 2*k*pi/self.parm1_var.get()))
                new_x = r*sin(theta)*0.4
                new_y = r*cos(theta)*0.4
                self.canvas.create_line(x+400,y+400,new_x+400,new_y+400,
                                        fill=tk_rgb)
                x = new_x
                y = new_y
            sleep(0.1)
            self.canvas.update()
           
    def draw_hyp(self):
        """This is the drawing function for the hyperbolic spiral"""
        for k in range(0,int(self.parm1_var.get())):
            x=0
            y=0
            if self.color_code == 0:
                tk_rgb = "#%02x%02x%02x" % (randint(0,255), randint(0,255),
                                        randint(0,255))
            else:
                tk_rgb = ['null','blue','black','red','green'][self.color_code]
            for r in range(1,100):
                theta=30.0/r + 2*k*pi/self.parm1_var.get()
                new_x = r*sin(theta)*4
                new_y = r*cos(theta)*4
                self.canvas.create_line(x+400,y+400,new_x+400,new_y+400,
                                        fill=tk_rgb)
                x = new_x
                y = new_y
            sleep(0.1)
            self.canvas.update()
        for k in range(0,int(self.parm1_var.get())):
            x=0
            y=0
            if self.color_code == 0:
                tk_rgb = "#%02x%02x%02x" % (randint(0,255), randint(0,255),
                                        randint(0,255))
            else:
                tk_rgb = ['null','blue','black','red','green'][self.color_code]
            for r in range(1,100):
                theta=-(30.0/r + 2*k*pi/self.parm1_var.get())
                new_x = r*sin(theta)*4
                new_y = r*cos(theta)*4
                self.canvas.create_line(x+400,y+400,new_x+400,new_y+400,
                                        fill=tk_rgb)
                x = new_x
                y = new_y
            sleep(0.1)
            self.canvas.update()
    def draw_fermat(self):
        """This is the drawing function for Fermat's spiral"""
        for k in range(0,int(self.parm1_var.get())):
            x=0
            y=0
            if self.color_code == 0:
                tk_rgb = "#%02x%02x%02x" % (randint(0,255), randint(0,255),
                                        randint(0,255))
            else:
                tk_rgb = ['null','blue','black','red','green'][self.color_code]
            for r in range(0,100):
                theta=(r/40.0)**2 + 2.0*k*pi/self.parm1_var.get()
                new_x = r*sin(theta)*3
                new_y = r*cos(theta)*3
                self.canvas.create_line(x+400,y+400,new_x+400,new_y+400,
                                        fill=tk_rgb)
                x = new_x
                y = new_y
            sleep(0.1)
            self.canvas.update()
        for k in range(0,int(self.parm1_var.get())):
            x=0
            y=0
            if self.color_code == 0:
                tk_rgb = "#%02x%02x%02x" % (randint(0,255), randint(0,255),
                                        randint(0,255))
            else:
                tk_rgb = ['null','blue','black','red','green'][self.color_code]
            for r in range(0,100):
                theta=-((r/40.0)**2 + 2.0*k*pi/self.parm1_var.get())
                new_x = r*sin(theta)*3
                new_y = r*cos(theta)*3
                self.canvas.create_line(x+400,y+400,new_x+400,new_y+400,
                                        fill=tk_rgb)
                x = new_x
                y = new_y
            sleep(0.1)
            self.canvas.update()
          
    def draw_lituus(self):
        """This is the drawing function for a Lituus spiral"""
        for k in range(0,int(self.parm1_var.get())):
            x=0
            y=0
            if self.color_code == 0:
                tk_rgb = "#%02x%02x%02x" % (randint(0,255), randint(0,255),
                                        randint(0,255))
            else:
                tk_rgb = ['null','blue','black','red','green'][self.color_code]
            for r in range(1,100):
                theta=1000.0/r**2 - 2*k*pi/self.parm1_var.get()
                new_x = r*sin(theta)*4
                new_y = r*cos(theta)*4
                self.canvas.create_line(x+400,y+400,new_x+400,new_y+400,
                                        fill=tk_rgb)
                x = new_x
                y = new_y
            sleep(0.1)
            self.canvas.update()
        for k in range(0,int(self.parm1_var.get())):
            x=0
            y=0
            if self.color_code == 0:
                tk_rgb = "#%02x%02x%02x" % (randint(0,255), randint(0,255),
                                        randint(0,255))
            else:
                tk_rgb = ['null','blue','black','red','green'][self.color_code]
            for r in range(1,100):
                theta=-(1000.0/r**2 - 2*k*pi/self.parm1_var.get())
                new_x = r*sin(theta)*4
                new_y = r*cos(theta)*4
                self.canvas.create_line(x+400,y+400,new_x+400,new_y+400,
                                        fill=tk_rgb)
                x = new_x
                y = new_y
            sleep(0.1)
            self.canvas.update()
    def menu_2(self):
        self.parm1_var.set(2)
        self.clear()
    def menu_3(self):
        self.parm1_var.set(3)
        self.clear()
    def menu_4(self):
        self.parm1_var.set(4)
        self.clear()
    def menu_5(self):
        self.parm1_var.set(5)
        self.clear()
    def menu_6(self):
        self.parm1_var.set(6)
        self.clear()
    def menu_7(self):
        self.parm1_var.set(7)
        self.clear()
    def menu_8(self):
        self.parm1_var.set(8)
        self.clear()
    def menu_9(self):
        self.parm1_var.set(9)
        self.clear()
    def menu_10(self):
        self.parm1_var.set(10)
        self.clear()
    def menu_11(self):
        self.parm1_var.set(11)
        self.clear()
    def menu_12(self):
        self.parm1_var.set(12)
        self.clear()
if __name__ == '__main__':
    root = Tk()
    root.wm_title('Various kinds of Spirals')
    app = App(root)
    root.mainloop()