'From the time of ancient Vitruvian geometric ideas to modem Corbusian regulating lines and Miesian modular grids, architecture has always been bound to (if not by) a conscious use of numbers.'
Rather than rely on an intuitive search for a solution, parametric design often involves precise, step-by- step techniques that yield a result according to rules and inputs. This way of thinking about the process of design as a rigorous rule-based system is referred to as algorithmic thinking. As Steele’s quote above hints, mathematical knowledge and algorithmic thinking have always been the traits of an architect, certainly at least since the times of the ancient Egyptians and Greeks. Today, individuals who wish to use parametric design in architecture find themselves faced with the challenge of learning algorithmic concepts (as well as mathematics) that are more familiar to software programmers than to designers.
Behind every piece of software is a set of precise instructions and techniques that interact with the user, respond to events, and read, manipulate and display data. Collectively, we call these instructions and techniques algorithms. Derived from the name of a Persian mathematician (Muhammad ibn Musa Al-Kwarizmi), an algorithm is defined as a set of precise instructions to calculate a function. An algorithm usually takes input (which can be empty or undefined), goes through a number of successive states, and ends with a final state and a set of outputs.
Learning programming concepts does not necessarily ensure that a designer will learn algorithmic thinking. The challenge is not dissimilar to learning cooking: one can learn the basics of mixing ingredients, heating, baking and so on, yet there is no guarantee of becoming an accomplished chef. As with most things, it takes a love for the craft, a methodical mind, some talent and, most importantly, practice. The metaphor also applies to the process itself: in the same way that cooking recipes vary in complexity, elegance and the taste of the final result, algorithms also vary in complexity, elegance, and the aesthetic and performative characteristics of the resulting design solution.
While some recipes are invented from scratch, most are modifications of and variations on older recipes. The same applies in parametric design. The Internet is teeming with open source algorithms that are offered for others to learn from, modify and expand. Beware, however, of the microwave variety: algorithms and definitions that are pre-packaged such that you cannot investigate and modify them. These types of algorithms are not always clear and readable, and this is where a book such as this becomes useful. The elegance, modularity and readability of an algorithm usually have a direct relationship to its ability not only to produce elegant design solutions, but also to be understood and modified by others.
The good news, however, is that most designers wishing to use parametric techniques usually need to solve a relatively small and well-bounded design problem (unlike software developers, who create large and complex software products). For example, they might need to create a parametric building facade or a roof structure. They might need to mimic a natural phenomenon to create a design concept for their project. Algorithmic thinking allows designers to rationalize, control, iterate, analyze, and search for alternatives within a defined solution space. In the next section, we will explore the basics of algorithms. This brief introduction cannot replace a full discussion of algorithms and the inner workings of computer programming languages. For that information, there is a plethora of books on programming, online resources and university courses that are dedicated to this topic.
A traditional scripting environment is usually text- based. It provides an empty file to write out algorithms (steps) to perform a variety of operations. You type in the algorithm using very precise syntax. Any omissions, even something as small as a parenthesis or a semi-colon, can cause errors. The help files will instruct you in the syntax and peculiarities of the computer language you choose (e.g. C, C++, Java, Python or MAXScript). All computer languages will allow you to insert comments that are not interpreted as computer instructions. Comments are usually enclosed by the notation/* and */ (/* this is a comment*/) or, for single lines, by the characters — (– this is a comment).
Each language will have its own syntax for how to indicate that something is a comment. Comments allow you to explain to yourself and others what the code is doing at that point. I made the difficult decision to remove the comments in some of the provided code examples due to space limitations, but also because the code is explained in the main text of the book. Your code, however, should always be clearly documented with comments within the code itself. Computer languages differ 10 their readability and some lines of code may look very cryptic, so comments can explain what is going on. nee complete, the scripting environment will provide you With a menu item or button to execute the script.
When a script is executed, it is interpreted by a computer interpreter or compiler in order to produce machine code that runs and does all the things you have asked it to do (e.g. display buttons and checkboxes, accept user input, create and draw geometries). If the interpreter encounters an error, it usually will report that to the user and stop the interpretation process. Robust computing environments also give you debugging environments that allow you to pause and examine the code as it is running in order to find errors. A script is usually made of standard parts: a declaration of what the script is and does, variables (think of variables as storage units to store information), functions (specialized and self-contained algorithms that accept input, act on it and produce output) and interfaces (declarations of what buttons, sliders and checkboxes to display and how to react to them).
In more modem scripting environments the shift has been to what is called object-oriented programming (OOP). In OOP systems, instead of variables and functions you declare full objects that store in themselves their own variables and functions (called methods). For example, you can declare a door object that knows its own dimensions and what material it is made of. You can ask it to open and close itself using its publically declared methods and even enquire of it whether it is closed or open. In addition, objects that are similar are placed in families or classes, which share overall characteristics that can be inherited and, if needed, customized, by the individual object. This method of writing scripts has proven to be very powerful, elegant, modular and capable of being generalized.
Data types and variables
As we said above, a variable is a storage unit in which to place a value. When we store that value in a variable, we can recall it later in the code by using the name of the variable. The script will remember what we stored in it earlier. You can also change its value at any time. When writing scripts, we will need to store different types of information: numbers, words, lists of things, circles, rectangles, etc. Thus computer languages allow you to specify what type a variable is when you declare it. Different languages use different code words for specifying a variable’s type, so you need to consult the user manual to find out what code word to use. The most common types of variable are:
- a whole number that does not have a decimal point. This is useful for counting objects.
Float (or real)
- a number that does have a decimal point. This is useful for measuring things.
String (or characters)
- a string of characters (e.g. words, sentences). This is useful for storing, manipulating and displaying textual information.
Array (or list)
- a special container that stores many variables and items. An array can be examined for how many Items it has, what item exists in a certain row location, etc.
- this is a special number that can have one of two values: true or false. This is useful to do logic operations, as we will see below.
- think of a pointer as an address of something else. For example, If you create a box or a circle you can store a pointer to these objects in variables (e.g. b = box(), c = circle()). You can then access the attributes of the box and the circle by using the variables band c respectively.
There are many other types of variable, as well as larger structures and objects that you may encounter, such as queues, stacks and sets, but the variables above are the most commonly used for storing and manipulating information.
In programming languages, an expression is a contiguous series of tokens, variable, and constants that specifies how a value is to be computed. For example, if I stored the value 4 in a variable called n then the expression 2+n will return 6 and the expression 2(n+1) would return 10. In this book, you will encounter code that may look unintuitive, such as a = a + 1. That does not mean that a is equal to itself plus 1 (which is impossible), but that the interpreter should add 1 to a and then store the result back in a itself. This is very useful for counting numbers.
Logic and control
As we said earlier, algorithmic thinking is mainly about logic. When we use logic, we need to be able to assess a situation and, based on the presented conditions, choose from a menu of available decisions. ln order for this to happen, the programming language needs to allow us to do two things: first, compare values
to one another and report back the result (which we call the predicate) and, second, execute one of several groups of code based on these results. In order to compare values, computer languages allow you to use notation for equality(==), inequality (≠or!=), greater than(>), less than(<), the logical opposite (not(n) or !(n)), and the Boolean set operations (and(), or()) that test whether both values are true, if at least one or the other is true, and so on. Notice that for testing equality we use two equal signs(==) in order to differentiate it from the assignment operation as discussed in the previous section. If you make a mistake and use one
equal sign in a predicate, then the variable will be assigned the value and a true result would be returned regardless. This can lead to serious errors in the script.
Making a decision based on a predicate is usually done through what is called an if-then or an if-then else statement. That is, if (something is true, do this, else (i.e. otherwise) do that. Here is an example:
1 if [n<4] Than
3 do something here
7 do something else here
In order to keep the code manageable and readable, we often need to combine several coding steps in one group. If you find yourself repeating the same code multiple times, that is an indication that you should write it once as a function and call it on demand as many times as you need. A function is assigned a name, accepts input in the form of function arguments, and produces a result via return values as well as by storing values in global variables. For example, imagine that you repeatedly need to write a function. Function that squares a number, adds another number to it and returns the result. You could write a function that you would give a name to such as squareAndAdd and define it as such:
1 fn squareAndAdd x y =
3 return [x*x] + y
Once the function is defined, we can call it and assign the result to a variable by writing z = squareAndAdd x y. The function would then take the value x and multiply it by itself, then add y to it and return a value that would then be stored in z. Jn MAXScript a function is defined using the built-in word fn. The actual syntax varies from one programming language to another. However, the basic principle holds true: you can encapsulate any number of steps in one function, which can then be called with input arguments and evaluated for return values.
Iteration and recursion
In many situations, we would need to repeat the same block of code many times. Programming languages provide special notation for iteration usually called for loops or while loops. Imagine, for example, creating a row of squares. Each square Is drawn with the same code but given a different location. In such a case, we can combine a for loop with the notation to increment the location variable. For example:
2 for i =1 to n by 1 do
4 draw a square i=10
The above code would iterate the variable i from 1 to 4 and execute the code that exists between (and) four times – each time with the value of i automatically increased by I. The imaginary function draw A Square. Would then draw a square at a location that is 10 multiplied by i (i.e. 10, 20, 30 and 40). Much as the moon orbits around the earth while the earth orbits around the sun. You can nest for loops inside one another to create multi-dimensional solutions.
Recursion is a special case of repetition and function calling where a function calls itself for the next iteration. Recurrence can be difficult to grasp; it is somewhat akin to placing two mirrors opposite each other and creating a theoretically infinite number of reflections. Recursion, as you will see later in this book, is essential for fractal geometry and branching.
Objects, classes, attributes and methods
As mentioned above, modern scripting environments are object-oriented. They encapsulate attributes and methods in larger structures that are called objects. Objects that share similar features are grouped in classes. Rather than storing an attribute or a method in each object. These can be stored in the higher-level class and inherited by the member of the class when needed. Consider, for example, the MAXScript object box. Assume you have asked MAXScript to create a box. In addition you stored a pointer to that box in a variable called b. We can then ask b for its width and height by evaluating the expression b.width and b.height. This dot notation allows us access to the pre-defined attributes of an object.
We can use the same dot notation to change the value the attribute (e.g. b.width= 10). Some objects also have methods associated with them, and these methods can be inherited. For example, in MAXScript, all objects belong to a class called node. A node knows how to move itself. So you could ask a box named b to move itself 10 units in the x direction, 5 units in the y direction and 20 units in the z direction. By issuing the command move b [10,5,20]. You could issue the same command to cylinders and spheres because they too belong to the node class of object.
Events and callback functions
A modem scripting environment that has a graphical user interface usually gives you the ability, in your own code, to react to user events. Further more actions (e.g. the user has clicked a button or changed a value in the interface). In order to respond to these events, the scripting environment asks you to define a function that is given a pre-defined name. This is called a callback function because it gets called back from the system into your own code. For example, if you have defined a button called generate. MAXScript allows you to react to the event of the button having been pressed, using the following syntax:
1 On generate_button pressed do
3 generate the geometry
This function, in your own code, would only be called and executed if the user presses the generate button.
As mentioned earlier, this chapter covers only the basics of algorithmic concepts at the simplest level. In no way does it substitute for a full study of programming languages, syntax, object-oriented methodology. Also the capabilities of the particular scripting environment. However, if pressed for time, you now know enough to read and understand the tutorials discussed in the next chapter.
| ref. Parametric Design for Architecture – Wassim Jabi |
Dynamo Scripts for Structure
MSc Bartlomiej Jakub Kuczynski © 2023