Now that we have covered some of the theoretical foundations of generative design, we will begin to address some of its technical aspects. The first question we have to ask is:
How do we go about specifying the design space?
As we have seen, our goal is to define a design space as a system which can create many different design iterations based on a finite set of input parameters. Unfortunately, the software tools traditionally used by architects cannot do this because they represent design in a static way. They typically provide a digital 3d environment and a set of modelling tools with which designers can craft singular design solutions as static representations of physical forms.
To define dynamic design space models, we need a different kind of design tool. Instead of specifying a single static form, we need to define the process by which a design is created. With this process description, we can then go back and tweak the individual steps to create different designs.
One way to do this would be to have the design software actually remembered every step that we took to achieve a final design, and then allow us to go back, change some intermediate condition, and then play the whole process out again from that point. In fact, the Rhino modelling software has a feature called Record History that does exactly this. In practice, however, this type of direct recording of operations tends to be messy and opaque, and does not allow us to be explicit in how we define our system or how we abstract the input parameters of the design space.
What we need is a separate design environment, abstracted from the representation of physical geometry, which would allow us to explicitly encode a set of steps or instructions for how the design should be generated. In computer terminology, a set of instructions is called an algorithm. Thus we can call the process of designing through a set of instructions algorithmic design. In practice such a design process is also referred to as parametric design, or more generally computational design.
Design by algorithm
So what does it mean to design with algorithms? Basically it means that instead of using computer tools to design a form, we are now designing a specific set of instructions which will tell the computer the series of steps it needs to take to make it — a kind of computational ‘how to’ for creating a certain design.
Before we consider how we might do this with a computer, let’s think about how we would describe a design process to another human. Let’s say we wanted to tell another designer working in a different country how to model a box 5" on each side in a digital design tool. To do this we could write out a set of instructions such as:
1. Choose a starting point 2. Draw a square 5" x 5" in the xy-plane 3. Extrude the square 5" up to form a solid box
Pretty simple, right? In fact, the algorithm we would write for a computer to make this box is quite similar, except it has to be way more explicit. This is because the computer, unlike a human designer, has no inherent knowledge or intuition about design, space, form, or boxes. So when we develop design algorithms for computers, we need to specify them in the computer’s own language. The languages used to communicate instructions directly to a computer are called programming languages, and the process of writing algorithms for computers is called computer programming.
If you are not a computer programmer, it is unlikely that you have used a programming language directly, although you are indirectly interacting with them every time you use a computer. All computer software is written in a programming language which describes all the functionalities of the program to the computer. However, to make the software easy to use for humans, most software include a graphic user interface (UI) — a series of graphic elements such as windows and buttons that allow the human to interact with it. Behind the scenes, however, each time you push a button or enter text, the software interprets your action and sends instructions to the computer in the programming language. In short, programming languages are our most basic means for communicating directly with computers.
The first major programming language was called FORTRAN and was developed at IBM in 1957. Since then many (it is impossible to count them all but most estimates have it in the hundreds) different programming languages have been developed, each tailored to specific applications in computer programming. The language we will use in this class is called Python, which is one of the most modern, versatile, and easiest to learn languages out there. This is how our box algorithm would look written in Python, using Rhino’s rhinoscriptsyntax library:
import rhinoscriptsyntax as rslength = 5 width = 5 height = 5pt_origin = rs.AddPoint(0,0,0) pt_x = rs.MoveObject(rs.CopyObject(pt_origin), [length,0,0]) pt_y = rs.MoveObject(rs.CopyObject(pt_origin), [0,width,0]) pt_z = rs.MoveObject(rs.CopyObject(pt_origin), [0,0,height])plane = rs.PlaneFromPoints(pt_origin, pt_x, pt_y) rect = rs.AddRectangle(plane, length, width) box = rs.ExtrudeCurve(rect, rs.AddLine(pt_origin, pt_z))
This is quite a bit more instructions that in our previous three lines, and may be hard to decipher at first. However, it is telling the computer to make the same exact box, except in an explicit way that it can understand. The text used to give computer instructions is called computer code, and is always written line by line, with each line giving the computer a specific task. Given a piece of code, the computer will start at the first line, and execute every instruction in order until it reaches the end of the code.
Unlike human language, computer code has to adhere to very strict rules so there is never any confusion about what the computer has to do. These rules vary language by language, and are called the syntax of the language. Writing computer code requires the programmer to know exactly how to write each operation in the syntax of the language, and although there are some tools to help the programmer find the proper syntax, in general all programming is done by directly writing the code in a text editor without any kind of graphical UI.
In comparison to computer code, the three instructions we wrote earlier are often called pseudo-code, because they describe the basic structure of the algorithm but are written informally in human language. Though pseudo-code cannot be directly run on a computer, it is often a very useful early step in developing a computer algorithm.
Simple pieces of code such as the one above are sometimes called ‘scripts’, and thus you often hear designers describing computational design as ‘scripting’. This terminology evolved from early design software which incorporated their own rudimentary proprietary programming languages which they called ‘scripting languages’. In general, a collection of code is referred to as a ‘program’, and can be as simple as a few lines of code or as complex as an entire operating system such as Windows.
Elements of computer programming
Although computer code may be difficult to read and understand at first, much of this difficulty actually stems from the syntax — the specific rules that guide how each language is written and formatted. Behind their specific syntax, however, all programming languages are based on a small set of common structures that drive the logic of how then work. In fact, nearly all common languages including Python are based on only 5 basic elements. Instead of diving right away into code and syntax, let’s first try to understand these elements and how they work. This will give us a better intuition about computer programming, and will make the learning of actual syntax much easier in the future.
Variables are containers that store some information. This can be a single piece of data such as the height of a building, a list of multiple pieces of data such as a list of rooms in a building, or more complex sets of data which we will see later.
Loops are structures within a program which can repeat sets of instructions for a given amount of time. Loops make it easier to write instructions for repetitive tasks, such as creating multiple windows on a façade.
Conditionals are structures within a program which change which instructions are executed based on a certain condition. For example, if you were writing an algorithm for specifying the types of windows in a house, you might have a conditional which creates an operable window if it is inside a bedroom, or a fixed window otherwise. Together, loops and conditionals are the two primary mechanisms for altering the flow or sequence in which instructions are executed within a program.
Functions are structures which wrap up a series of operations to accomplish a given task which may be reused multiple times within a program. You can think of functions as mini programs. Functions typically have one or more inputs which guide the way their internal program works, and one or more outputs which are returned to the main program once the function has run.
One of the simplest examples of a function is the ‘+’ operator, which accepts two numbers as inputs, and return their sum as the output. In architecture you might create a function which arranges a set of furniture within a room. The inputs might be the room’s type and boundary, while the outputs would be the furniture geometry.
Functions are critical for creating more modular and efficient programs which can accomplish more complex tasks. Computer programming which uses only these first four elements is commonly referred to as procedural programming — since it defines a finite set of procedures for the computer to execute. Although this is a more traditional and limited kind of computer programming, it is usually sufficient for most design tasks.
Objects (sometimes called classes) are structures which define a particular type of entity within a program. Once an objet is defined, a program usually works with individual instances of an object which are created based on the object’s specification. Objects may have a set of variables which store unique information about each instance. Since these variables pertain only to the instance and are typically kept hidden from the rest of the program, they are commonly referred to as local variables. Objects also typically define a set of internal functions which describe certain tasks or behaviors of the object. Functions defined locally within an object are commonly referred to as the object’s methods.
For example, if you wanted to create a program to simulate the behavior of a dog, you could create an object of type ‘dog’. This object would store a set of local variables about the dog’s breed, fur color, weight, etc. You would also define a set of methods that describe the dog’s behavior — for example you might have methods for ‘eat’, ‘run’, and ‘bark’. Once you have defined the ‘dog’ object, you can create specific dogs by creating instances of the object and assigning them the proper local variables. Then you could run the methods on the individual dogs to have them perform the action. In architecture, you might define an object of type ‘house’, whose local variables store information such as the number of rooms, materials, and height, and whose methods define the way in which the house’s geometry is created.
Computer programming which is based on objects is referred to as object-oriented programming (OOP). With OOP, we can think of a computer program as a simulated environment containing many interacting agents (objects) which interact with the environment and other agents based on the their object definitions. Although it is easy to think of these agents as physical things like dogs, the same framework can easily be applied to other entities in a program such as a display window or a button. Unlike simple scripts or procedural programs, object-based programs typically do not have a fixed order of execution and no fixed ending time.
OOP is a much more modern paradigm than procedural programming, and is used to design almost all of the software we use today. For example, beneath its surface even a simple word processor is really a collection of agents such as windows, menus, and buttons which listen for user interaction and influence each other’s behavior. Such a program does not have any fixed behavior or ending time. Once it is started it will wait for user input, perform certain tasks based on this input, and will stay around until it is told to end. Although the use of objects is not common in design tasks, but integrating some of the ideas from object-oriented programming has many potentials for defining more interesting and complex design spaces.
Computational strategies for defining design spaces
Based on our discussion of computer programming so far, we can think of a number of strategies for defining our design spaces computationally:
- Morphological control through continuous variables
- State-change control through discrete variables
- Recursive control through functions and rule sets
- Behavioral control through object-oriented programming
These strategies describe different ways that we can translate a finite set of parameters into instructions for the computer to create form. They go from very simple and direct methods to more complex systems which can create more complex design spaces with the potential to create unexpected designs. Each strategy relies on some of the elements of programming we described above, with the more complex strategies requiring the use of more complex elements.
Continuous variables (decimal numbers) can be used directly as inputs to drive the shape of various forms in the design space. For example, you might use three continuous variables to control the length, width, and height of the box described above. Discrete (whole numbers) or categorical variables may define different types of something within a model, and be used in conjunction with loops or conditionals to discretely change certain elements within a model. Morphological and state-change strategies are effective but tend to require too many input parameters. For example, imagine having a separate input to describe each window in a high-rise tower.
Models described only with such direct methods are often either too simple to be interesting, or have too many inputs to be effectively searched by the genetic algorithm. The last three strategies, on the other hand, can limit the number of variables while preserving the complexity of our design space by creating more complex relationships between the inputs and the final form.
Recursive strategies use recursive functions to describe complex formal structure based on a set of parameterized rules. For example, you might have a single set of inputs that define a set of rules for placing windows in the tower, and the individual windows are then placed according to those rules and their individual locations in the building. This allows you to maintain control of all the windows, while minimizing and abstracting the input parameters required. This technique can define highly complex geometries such as fractal systems from a very small set of starting parameters, and is very common in the geometries of nature.
Behavioral strategies provide the most advanced methods for controlling the forms in our design space model. Such strategies use ideas from object-oriented programming to further abstract the relationship between input parameters and the final forms. Here, the input parameters might describe the methods and initial conditions of a set of objects, and the final design is the result of the interaction between these objects after a given period of time.
Tools for computational design
In this class, we will use the Python programming to help us design complex design space according to this set of strategies. However, instead of starting from scratch, we will write the code inside another computational framework — Rhino’s Grasshopper. This is a different kind of programming environment called a visual scripting language. Instead of individual lines of code, Grasshopper uses a collection of visual nodes to represent operations in the model, with connections between the nodes specifying how data flows between the operations. Grasshopper has become very popular for computational design in architecture because it is initially much more intuitive and easier to learn than actual code (you can think of it as another example of a user interface abstracting the complexities of computer code for the human user).
Because of its simplicity and popularity, Grasshopper is a great place to start building our design space models. In the next section we will spend some time describing the Grasshopper system and to what extent it can implement the four design strategies we outlined above. We will conclude that section with some of Grasshopper’s limitations, which will lead us to a discussion of how we can extend its possibilities for computational design by implementing actual Python computer code within our design space models.