```
1 """
2 penrose2.py is a python script for use in processing.py.
3 revised version uses my grammar module, output is a pdf file
4 wait until penrose appears before closing the applet (text does not
5 appear in applet, but should be in pdf file)
6 """
7
8
```**import** math
9 **import** grammar
10
11 # some globals
12 XPOS **=** 0
13 YPOS **=** 1
14 ANGLE **=** 2
15 DELTA **=** math.pi**/**5
16
17 RULES **=** {
18 'F' **:** '',
19 'W' **:** 'YBF2+ZRF4-XBF[-YBF4-WRF]2+',
20 'X' **:** '+YBF2-ZRF[3-WRF2-XBF]+',
21 'Y' **:** '-WRF2+XBF[3+YBF2+ZRF]-',
22 'Z' **:** '2-YBF4+WRF[+ZRF4+XBF]2-XBF',
23 }
24
25 AXIOM **=** '[X]2+[X]2+[X]2+[X]2+[X]'
26
27
28 **def** render**(**production**)****:** # avoiding switch and globals
29 """
30 Render evaluates the production string and calls draw_line
31 """
32 turtle **=** [0, 0, **-**DELTA]
33 stack **=** []
34 repeat **=** 1
35 **for** val **in** production**:**
36 **if** val **=****=** "F"**:**
37 turtle **=** __drawLine**(**turtle, 20**)**
38 **elif** val **=****=** "+"**:**
39 turtle[ANGLE] **+****=** DELTA ***** repeat
40 repeat **=** 1
41 **elif** val **=****=** "-"**:**
42 turtle[ANGLE] **-****=** DELTA ***** repeat
43 repeat **=** 1
44 **elif** val **=****=** "["**:**
45 temp **=** [turtle[XPOS], turtle[YPOS], turtle[ANGLE]]
46 stack.append**(**temp**)**
47 **elif** val **=****=** "]"**:**
48 turtle **=** stack.pop**(****)**
49 **elif** **(****(**val **=****=** '2'**)** **or** **(**val **=****=** '3'**)** **or** **(**val **=****=** '4'**)****)****:**
50 repeat **=** **int****(**val**)**
51 **else****:**
52 **pass**
53
54
55 **def** __drawLine**(**turtle, length**)****:**
56 """
57 private line draw uses processing 'line' function to draw a line
58 to a heading from the turtle with distance = length returns a new
59 turtle corresponding to end of the new line
60 """
61 new_xpos **=** turtle[XPOS] **+** length ***** math.cos**(**turtle[ANGLE]**)**
62 new_ypos **=** turtle[YPOS] **-** length ***** math.sin**(**turtle[ANGLE]**)**
63 line**(**turtle[XPOS], turtle[YPOS], new_xpos, new_ypos**)**
64 **return** [new_xpos, new_ypos, turtle[ANGLE]]
65
66 **def** renderToPDF**(**production**)****:**
67 """
68 Create a pdf file with penrose drawing and rules, you require a ttf font for use with pdf
69 standard linux font path below you will probaly need to modify the path to suit your system
70 """
71 myFont **=** createFont**(**"/usr/share/fonts/truetype/freefont/FreeSans.ttf", 18**)**
72 beginRecord**(**PDF, "penrose.pdf"**)**
73 translate**(**width**/**2, height*****0.4**)**
74 render**(**production**)**
75 textMode**(**SHAPE**)**
76 fill**(**0**)**
77 textFont**(**myFont, 18**)**
78 text**(**"Penrose Tiling", 300, 50**)**
79 text**(**grammar.toRuleString**(**AXIOM, RULES**)**, 100, 650**)**
80 endRecord**(****)**
81
82
83 **def** setup**(****)****:**
84 """
85 The processing setup statement
86 """
87 size**(**700, 900, P2D**)**
88 background**(**255**)**
89 hint**(**ENABLE_NATIVE_FONTS**)**
90 smooth**(****)**
91 production **=** grammar.repeat**(**5, AXIOM, RULES**)**
92 strokeWeight**(**2**)**
93 #render(production) # now redundant can be used if pdf render not working?
94 renderToPDF**(**production**)**
95
96 **def** draw**(****)****:**
97 """
98 An empty processing draw statement seems to be required to prevent premature exit()?
99 """
100 **pass**
101

```
1 """
2 grammar.py module supports the parsing of both stochastic and non-stochastic rules
3 axiom/rules are evaluated by the produce function, which uses the __weightedRule function
4 to return any stochastic rule according to the input dict, the repeat function is used to repeatedly
5 iterate the rules in a recursive fashion.
6 Example Rules:
7
8 Non-Stochastic = { "A": "A+F", "G": "GG", "X" :"G-G"}
9
10 Stochastic = {"A" : {"BCD": 5, "C+C+C":10, "ACA": 40}, "B" : {"DE-F": 5, "CCC":10, "A[C]A": 40}}
11
12 The Stochastic rule may contain non stochastic elements, in the above example there are two stochastic, elements,
13 with keys "A" and "B". The stochastic rule is detected by checking the 'value' type is dict. The dict needs to of the
14 form "string substitution" as key with the weighting as value. A test function is included for the test conscious or
15 skeptic.
16 """
17
18
19
```**import** random
20
21 **def** __weightedRule**(**rules**)****:**
22 """
23 A private method used to choose a substitution rule from a dict of rules, according to its
24 weighted probality. 'rules' is expected to be a dict where the substition string is the 'key'
25 and the 'value' is the rule weight
26 """
27 rand **=** random.random**(****)**
28 prob **=** 0
29 tot **=** **sum****(**rules.values**(****)****)** # sum probabilities
30 **for** rule **in** rules.keys**(****)****:** # iterate over rule choices
31 prob **+****=** rules.get**(**rule**)** # add assigned probalities
32 **if** **(****(**rand ***** tot**)** **<** prob**)****:** # compare running total with scaled random value
33 **return****(**rule**)**
34
35 **def** produce**(**axiom, rules**)****:**
36 """
37 The single rule substitution utility, that uses type to check for dict or str
38 as key value, else declares 'Unrecognized grammar'. Does not throw exception!!!
39 """
40 output **=** ""
41
42 **for** i **in** axiom**:**
43 temp **=** rules.get**(**i, i**)**
44 **if** **(****type****(**temp**)** **is** **dict****)****:**
45 output **+****=** __weightedRule**(**temp**)**
46 **elif** **(****type****(**temp**)** **is** **str****)****:**
47 output **+****=** temp
48 **else****:**
49 **print****(**"Unrecognized grammar"**)**
50 **return** output
51
52
53 **def** repeat**(**rpx, axiom, rules**)****:**
54 """
55 Repeat rule substitution in a recursive fashion rpx times
56 """
57 production **=** axiom
58 **for** i **in** **range****(**0, rpx**)****:**
59 production **=** produce**(**production, rules**)**
60 **return** production
61
62 **def** __testWeighting**(**rules, key, total**)****:**
63 """
64 Private test function see module header for examples of rules format
65 Takes a dict containing a stochastic rule with 'key' as key.
66 Tests the weighted rule function a 'total' number of times.
67 Frequency result is printed.
68 """
69 wordList **=** [] # create a big test list of replacement rules
70 **for** z **in** **range****(**0, total**)****:**
71 wordList.append**(**__weightedRule**(**rules.get**(**key**)****)****)**
72
73 # calculate each word frequency in generated list (NB: does not test the
74 # randomness of order though)
75 freqD2 **=** {}
76 **for** word2 **in** wordList**:**
77 freqD2[word2] **=** freqD2.get**(**word2, 0**)** **+** 1
78 keyList **=** freqD2.keys**(****)**
79 keyList.sort**(****)**
80
81 **print** "Frequency of each substitution string in the word list (sorted):"
82 **for** key2 **in** keyList**:**
83 **print** "%-10s %d" **%** **(**key2, freqD2[key2]**)**
84
85 **def** toRuleString**(**axiom, rules**)****:**
86 """
87 Creates a string representing the pythonic rules in a more conventional
88 manner
89 """
90 output **=** "Axiom:\t%s\n" **%** axiom
91 keys **=** rules.keys**(****)**
92 **for** key **in** keys**:**
93 temp **=** rules.get**(**key**)**
94 **if** **(****type****(**temp**)** **is** **dict****)****:**
95 keys2 **=** temp.keys**(****)**
96 **for** key2 **in** keys2**:**
97 output **+****=** "Rule:\t%s => %s\t%d\n" **%** **(**key, key2, temp.get**(**key2**)****)**
98 **elif** **(****type****(**temp**)** **is** **str****)****:**
99 output **+****=** "Rule:\t%s => %s\n" **%** **(**key, temp**)**
100 **else****:**
101 output **+****=** "Unrecognized grammar\n"
102 **return** output
103
104
105

It probably isn't clear from below PDF output, but the F is substituted with a "",

or as some would write it by a delete, since the default substitution is to return

the key as the value. Which can be read from the code lines 18 (

*penrose.py*), and

43 (

*grammar.py*) respectively.