From 23dded8b4962ee55c11fb650c053c370b242b5a3 Mon Sep 17 00:00:00 2001 From: Paul Sweeney Date: Fri, 14 Apr 2023 14:45:13 +0100 Subject: [PATCH] Update vSystem.py Added comments. --- vSystem.py | 265 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 206 insertions(+), 59 deletions(-) diff --git a/vSystem.py b/vSystem.py index daac1d2..ff3a6a3 100755 --- a/vSystem.py +++ b/vSystem.py @@ -5,70 +5,183 @@ from analyseGrammar import posneg def I(n, d0, val=3): + ''' + A recursive function to generate an "I" fractal pattern. + + Args: + n (int): the level of recursion. n = 0 returns "I". + d0 (float): the initial length of the line segment. + val (int, optional): a constant used in the function. Default is 3. + + Returns: + str: the resulting string representing the fractal pattern. + ''' if n > 0: + # Calculate parameters for bifurcation and string formation params = calBifurcation(d0) - p1 = calParam(str.join('co/',str(int(val))), params) + p1 = calParam(str.join('co/', str(int(val))), params) + + # Add a random rotation to the new line segment rotate = np.random.uniform(22.5, 27.5) + + # Recursive call to generate the string for the next level return 'f(' + p1 + ',' + str(params['d0']) + ')' + '+(' + str(rotate) + ')' +\ - '[' + R(n-1, params['d0']) + ']' - else: return 'I' + '[' + I(n-1, params['d0']) + ']' + else: + # Base case: return "I" + return 'I' def R(n, d0): - if n > 0: - params = calBifurcation(d0) - p1 = calParam(str.join('co/',str(int(3))), params) - p2 = calParam(str.join('co/',str(int(2))), params) - descrip = 'f(' + p1 + ')' + G(n, d0, val=7) +\ - G(n-1, d0, val=7) + G(n-1, d0, val=7) + '[' + B(n-1, params['d1']) +\ - ']' + 'f(' + p2 + ',' + str(params['d2']) + ')' + B(n-1, params['d2']) - return descrip - else: return 'R' + ''' + A recursive function to generate an "R" fractal pattern. + + Parameters: + - n (int): The level of recursion. Must be a non-negative integer. + - d0 (float): The diameter of the parent branch. Must be a positive float. + + Returns: + - A string description of a bifurcating tree. + ''' + if n == 0: + return 'R' + + params = calBifurcation(d0) # Calculates bifurcation parameters based on parent diameter + p1 = calParam('co/' + str(int(3)), params) # Calculates the parameters for the first child branch + p2 = calParam('co/' + str(int(2)), params) # Calculates the parameters for the second child branch + + # Calls G function recursively to generate descriptions of child branches + # The descriptions are concatenated with function calls and brackets to form the final description + descrip = 'f(' + p1 + ')' + G(n, d0, val=7) + G(n-1, d0, val=7) + G(n-1, d0, val=7) \ + + '[' + B(n-1, params['d1']) + ']' \ + + 'f(' + p2 + ',' + str(params['d2']) + ')' + B(n-1, params['d2']) + + return descrip + def B(n, d0): + """ + A recursive function to generate an "B" fractal pattern. + + Args: + - n (int): The current bifurcation level. + - d0 (float): The current diameter of the parent branch. + + Returns: + - A string describing the bifurcation structure. + + Raises: + - N/A + """ + if n > 0: - return G(n-1, d0, val=7) + G(n-1, d0, val=7) + G(n-1, d0, val=7) +\ - '/(' +str(90.0) + ')' + A(n, d0) - else: return 'B' + # Recursively generate descriptions of child branches + descrip = G(n-1, d0, val=7) + G(n-1, d0, val=7) + G(n-1, d0, val=7) +\ + '/(' +str(90.0) + ')' + A(n, d0) + return descrip + else: + # Return string representation of parent branch + return 'B' def F(n, d0): + """ + A recursive function to generate an "F" fractal pattern. + + Args: + - n (int): number of iterations to perform + - d0 (float): the initial distance + + Returns: + - str: a string of characters representing the fractal generated by the given parameters. + """ if n > 0: + # calculate the bifurcation parameters params = calBifurcation(d0) - theta1 = params['th1'] #+ np.random.uniform(-2.5, 2.5) - theta2 = params['th2'] #+ np.random.uniform(-2.5, 2.5) - tilt = np.random.uniform(22.5, 27.5)*random.randint(-1,1) - return S(n-1, d0)+'['+'+('+str(theta1)+')'+'/('+str(tilt)+')'+\ - F(n-1, params['d1'])+']'+'['+'-('+str(theta2)+')'+'/('+str(tilt)+')'+\ - F(n-1, params['d2'])+']' - else: return 'F' + + # calculate the angles for rotation and tilt + theta1 = params['th1'] + theta2 = params['th2'] + tilt = np.random.uniform(22.5, 27.5) * random.randint(-1, 1) + + # recursively generate the fractal string using the calculated parameters + return S(n-1, d0) + '[' + '+(' + str(theta1) + ')/(' + str(tilt) + ')' + \ + F(n-1, params['d1']) + ']' + '[' + '-(' + str(theta2) + ')/(' + str(tilt) + ')' + \ + F(n-1, params['d2']) + ']' + else: + # return the base character if n <= 0 + return 'F' def S(n, d0, val=5, margin=0.5): + """ + S function generates the shape of a tree's stem. + + Args: + n (int): The depth of the recursive algorithm. + d0 (float): The trunk diameter of the tree. + val (int, optional): Default is 5. The angle in degrees between a branch and the trunk. + margin (float, optional): Default is 0.5. The chance of using one of two sub-functions (S1, S2) to generate the stem. + + Returns: + str: A string representing the shape of a tree's stem. + """ + # Check if the depth of the recursion is greater than zero + if n <= 0: + return 'S' + + # Generate a random number between 0 and 1 r = random.random() - if r>= 0.0 and r < margin: return '{' + S1(n, d0, val) + '}' - if r >= margin and r < 1.0: return '{' + S2(n, d0, val) + '}' + + # Determine which sub-function to use based on the random number and margin value + if r >= 0.0 and r < margin: + return '{' + S1(n, d0, val) + '}' + if r >= margin and r < 1.0: + return '{' + S2(n, d0, val) + '}' def S1(n, d0, val=5): - if n>0: - # "Fanning" of trees - rotate = np.random.uniform(22.5, 27.5)*random.randint(-1,1) + """ + Recursive function that generates a string representation of a tree structure. + + Args: + - n (int): the recursion level, i.e. the number of times the function is called recursively. + - d0 (float): the initial diameter of the tree. + - val (int): value to be passed to function D(). Default value is 5. + + Returns: + - str: string representation of a tree structure. + """ + if n > 0: + # Randomly rotate the branches of the tree + rotate = np.random.uniform(22.5, 27.5) * random.randint(-1, 1) params = calBifurcation(d0) descrip = D(n-1, params['d0'], val) + '+(' + str(rotate) + ')' + \ D(n-1, params['d0'], val) + '-(' + str(rotate) + ')' + \ - D(n-1, params['d0'], val) + '-(' + str(rotate) + ')' + \ - D(n-1, params['d0'], val) + '+(' + str(rotate)+ ')' + \ - D(n-1, params['d0'], val) + D(n-1, params['d0'], val) + '-(' + str(rotate) + ')' + \ + D(n-1, params['d0'], val) + '+(' + str(rotate)+ ')' + \ + D(n-1, params['d0'], val) return descrip - else: return 'S' + else: + return 'S' def S2(n, d0, val=5): - if n>0: + """ + Recursive function that generates a string representation of a tree structure. + + Parameters: + n (int): the level of recursion. Controls the depth of the tree-like structure. + d0 (float): the trunk diameter of the tree at the base. + val (int): the numerical value to assign to the final string. Default value is 5. + + Returns: + descrip (str): the final string representation of the tree-like structure. + """ + if n > 0: # "Fanning" of trees - rotate = np.random.uniform(22.5, 27.5)*random.randint(-1,1) + rotate = np.random.uniform(22.5, 27.5) * random.randint(-1, 1) params = calBifurcation(d0) descrip = D(n-1, params['d0'], val) + '-(' + str(rotate) + ')' + \ D(n-1, params['d0'], val) + '+(' + str(rotate) + ')' + \ @@ -76,51 +189,85 @@ def S2(n, d0, val=5): D(n-1, params['d0'], val) + '-(' + str(rotate) + ')' + \ D(n-1, params['d0'], val) return descrip - else: return 'S' + else: + return 'S' def D(n, d0, val=5): - if n>0: + """ + Generate a string of L-system symbols for generating a fractal tree using a + deterministic context-free Lindenmayer system (D0L-system) with production rules. + + Parameters: + n (int): Number of iterations. + d0 (float): Starting diameter of the tree. + val (int): Value to be passed to `calParam` function (default is 5). + + Returns: + str: The generated L-system symbols string for the fractal tree. + + """ + if n > 0: + # Calculate the parameters for the bifurcation of the tree params = calBifurcation(d0) - p1 = calParam(str.join('co/',str(int(val))), params) + + # Calculate the parameter for the function call + p1 = calParam(str.join('co/', str(int(val))), params) + + # Return the L-system string with the calculated parameters return 'f(' + p1 + ',' + str(params['d0']) + ')' - else: return 'D' + else: + # If the number of iterations is 0, return the symbol 'D' + return 'D' def G(n, d0, val=5, shift=18.0): - if n>0: + """ + Generates a string representing the result of the function f with parameters p1 and d0. + + Args: + - n (int): number of iterations of the function f. + - d0 (float): parameter d0 of the bifurcation function. + - val (int): value to use for calculating the parameter p1. Default is 5. + - shift (float): value to add to the result of f. Default is 18.0. + + Returns: + - str: a string representing the result of the function f with parameters p1 and d0. + """ + if n > 0: params = calBifurcation(d0) - p1 = calParam(str.join('co/',str(int(val))), params) - return 'f(' + p1 + ',' + str(params['d0']) + ')' - else: return 'G' + p1 = calParam(str.join('co/', str(int(val))), params) + # Call function f with parameters p1 and d0, and add the value of shift to the result + return str(float(f(p1, params['d0'])) + shift) + else: + return 'G' def A(n, d0): + """ + Generates string description of fractal tree using recursive approach + Args: + n (int): Depth of the recursion + d0 (float): Initial branch length + Returns: + str: String description of the fractal tree + """ if n > 0: + # calculate bifurcation angles and branch lengths params = calBifurcation(d0) - return S(n-1, d0) + '[+(' + str(params['th1']) + ')' + A(n-1, params['d1']) + ']' +\ - '[+(' + str(params['th2']) + ')' + A(n-1, params['d2']) - else: return 'A' - - -# def A(d0): -# return 'f(' + str(1) + ',' + str(d0) + ')' + 'f(' + str(1) + ',' + str(2*d0) + ')' + \ -# 'f(' + str(1) + ',' + str(d0) + ')' + # recursive calls to A and S functions + return ( + S(n-1, d0) + + '[+(' + str(params['th1']) + ')' + A(n-1, params['d1']) + ']' + + '[+(' + str(params['th2']) + ')' + A(n-1, params['d2']) + ']' + ) + else: + return 'A' # def E(d0): # return 'f(' + str(1) + ',' + str(d0) + ')' + 'f(' + str(1) + ',' + str(0.5*d0) + ')' + \ # 'f(' + str(1) + ',' + str(d0) + ')' - - - - - - - - - - # 2D grammars - don't currently work with framework def simplest_gramma(n=None, theta1=20., theta2=20., params=None): return 'f' + '[' + '+' + F(n-1, params['d0']) + ']' + '-' + F(n-1, params['d0'])