summaryrefslogtreecommitdiff
path: root/tutorials/module_1/classes_and_objects.md
blob: ba1dab9e918a6847b944e682ee9abfc98279773e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# Modular Programming

## 1. Introduction

- A. What is Object-Oriented Programming?
- B. Why use OOP? (vs. procedural)
- C. Real-world analogies (e.g., modeling components like pumps, motors, or vehicles)

## 2. Core OOP Concepts
- A. **Classes and Objects**
    - Definitions
    - Syntax in Python
- B. **Attributes and Methods**
    - Instance variables
    - Functions inside classes
- C. **Encapsulation**
    - Public vs private variables
    - Using `__init__` and `self`
- D. **Inheritance**
    - Parent and child classes
    - Reuse and extension of code
- E. **Polymorphism** _(brief overview)_
    - Method overriding
    - Flexibility in interfaces

## 3. Python OOP Syntax and Examples
- A. Define a simple class (e.g., `Spring`)
- B. Instantiate objects and use methods
- C. Show `__init__`, `__str__`, custom methods
- D. Add a derived class (e.g., `DampedSpring` inherits from `Spring`)

## 4. Engineering Applications of OOP
- A. Modeling a mechanical system using classes
    - Example: Mass-Spring-Damper system
- B. Creating reusable components (e.g., `Material`, `Beam`, `Force`)
- C. Organizing simulation code with OOP

## 5. Hands-On Coding Activity
- A. Write a class for a basic physical component (e.g., `Motor`)
- B. Add behavior (e.g., `calculate_torque`)
- C. Extend with inheritance (e.g., `ServoMotor`)
- D. Bonus: Integrate two objects to simulate interaction

---
# Background
To understand what modular programming is and why using it, let's take a look a the origin of computer code.
# Traditional Programming
Procedural Oriented Programming. 

| OOP                                                                                                              | POP                                                                                                                                               |
| ---------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Object oriented](https://www.geeksforgeeks.org/dsa/introduction-of-object-oriented-programming/).               | [Structure oriented](https://www.geeksforgeeks.org/computer-networks/difference-between-structured-programming-and-object-oriented-programming/). |
| Program is divided into objects.                                                                                 | Program is divided into functions.                                                                                                                |
| Bottom-up approach.                                                                                              | Top-down approach.                                                                                                                                |
| Inheritance property is used.                                                                                    | Inheritance is not allowed.                                                                                                                       |
| Encapsulation is used to hide the data.                                                                          | No data hiding.                                                                                                                                   |
| Concept of virtual function.                                                                                     | No virtual function.                                                                                                                              |
| Object functions are linked through message passing.                                                             | Parts of program are linked through parameter passing.                                                                                            |
| Adding new data and functions is easy                                                                            | Expanding new data and functions is not easy.                                                                                                     |
| The existing code can be reused.                                                                                 | No code reusability.                                                                                                                              |
| use for solving big problems.                                                                                    | Not suitable for solving big problems.                                                                                                            |
| Python, [C++](https://www.geeksforgeeks.org/cpp/c-plus-plus/), [Java](https://www.geeksforgeeks.org/java/java/). | [C](https://www.geeksforgeeks.org/c/c-programming-language/), Pascal.                                                                             |


https://www.geeksforgeeks.org/cpp/difference-between-oop-and-pop/
## POP vs. OOP



# Modular Programming
Modular programming or better known as Object-Oriented Programming (OOP) is a way we can structure programs so that properties and behaviors of objects are grouped together. It allows us to re-use code which simplifies the code for better readability in larger programs and reduces the potential for bugs. You're probably familiar with the saying "Don't re-invent the wheel". OOP is the programmers solution to this. Python allows us to import libraries so that we don't have to write everything from scratch. The libraries used in this course provide us with algorithms to calculate a solution, structure data and plot data. When looking at the source code of these library you may see keywords, such as `class`. This tutorial will delve into the fundamental concepts of OOP.

#### Classes
One analogy for OOP goes as follows. Imagine you are an urban planner in the town of Wellington, the town is rapidly expanding and housing is in high demand. You are tasked with developing a new housing area. Designing 50 different houses will take too much, but building the same house 50 times will make the neighborhood look like a subdivision from the 1950s. Your solution may be to come up with some structure that represents a house, this is our `class`. This structure may define how a house should look like, for example, a house should have doors, rooms and windows. Even though every house won't looks that same, they all will follow the same patterns. Properties such as color, window types, garage type can be introduced inside the `class` however it will not be define until we build the specific house.

#### Objects
Once we have the general structure of what a house is going to look like, we can use this blueprint to start building houses faster. With this blueprint defined. We can start building houses from the blueprint. In python we call this *initializing objects*. Where an actual house built from this blueprint with specific attributes (e.g. house #305, blue house, single-hung window, attached garage) is referred to as an `object`.

>[!NOTE] Difference between functions and objects
> **Functions:** A procedure. Such as a force.
> **Objects:** Include properties. Such as a spring, with a defined spring constant. 

#### Example
Let's take a look at an example. Of how this looks like in python. Do not worry if you don't understand the syntax below, we will explain this later. Let's define the class:
```python
class House():
    def __init__(self, house_number, color, garage_type):
        self.house_number = house_number 
        self.color = color
        self.garage_type = garage_type 
        
    def describe(self):
        print("House #" + self.house_number + " is painted " + self.color)
        print("and has a " + self.garage_type + " garage.")
```
Now we can use our class to define new objects:
```python
house1 = House(house_number=101, color='blue', garage_type='attached')
house1.describe()

house2 = House(house_number=102, color='green', garage_type='detached')
house2.describe()
```

In the example above, we first defined the class `House` with `house_number`, `color`, and `garage_type` as the data (attributes), and `describe()` as the method. Once we initialized objects such as `house1` and `house2`, we can see that the class provides the overall blueprint, while each object is an individual house built from that blueprint. It is clear that `house1` and `house2` are completely independent from one another, even though they were both created using the same `House` class.

>[!NOTE] Attributes
> Data that is stored within an object. 

>[!NOTE] Methods 
>A function that lives inside a class and has access to the data stored inside the object.  usually 

### Why use it?
When you start to represent more complex data primitive data structures, such as arrays become more difficult to manage. Let's consider you're trying to track employees in an organization. You may need to store name, age, position and year of recruitment. One way to represent this would be as follows:
```python
kirk = ["James Kirk", 34, "Captain", 2265]
spock = ["Spock", 35, "Science Officer", 2254]
mccoy = ["Leonard McCoy", "Chief Medical Officer", 2266]
```
There are a number of issues with this approach. First, it can make larger code files more difficult to manage. If you reference `kirk[0]` several lines away from where you declared the `kirk` list, will you remember that the element with index `0` is the employee’s name? Second, it can introduce errors if employees don’t have the same number of elements in their respective lists. In the `mccoy` list above, the age is missing, so `mccoy[1]` will return `"Chief Medical Officer"` instead of Dr. McCoy’s age.

The modularity of the programs allows us to re-use code which reduces the number of potential bugs.

#### Four Pillars of OOP
Now that we know how classes and objects look like. Let's take a look at the four fundamental concepts in OOP. 

The four pillars:
	1. Inheritance - enables the creation of hierarchical relationships between classes, allowing a subclass to inherit attributes and methods from a parent class. This promotes code reuse and reduces duplication.
	2. Encapsulation - allows you to bundle data (attributes) and behaviors (methods) within a class to create a cohesive unit. By defining methods to control access to attributes and its modification, encapsulation helps maintain data integrity and promotes modular, secure code.
	3. Polymorphism - allows you to treat objects of different types as instances of the same base type, as long as they implement a common interface or behavior. Python’s duck typing make it especially suited for polymorphism, as it allows you to access attributes and methods on objects without needing to worry about their actual class.
	4. Abstraction - focuses on hiding implementation details and exposing only the essential functionality of an object. By enforcing a consistent interface, abstraction simplifies interactions with objects, allowing developers to focus on what an object does rather than how it achieves its functionality.


## Examples:

#### Modelling physical systems
We can model components as classes where we specify it's properties as attributes and behavior as methods. Use the provided equations to calculate the torque 

a) Using the equations below, model the component as a class.
$$
\tau(I) = K_t * I \tag{}
$$
$\tau$ - is the torque produced by the motor
$K_t$ - torque constant
$I$ - current flowing through the motor
$$
I(f) = \frac{V - K_t * f}{R} \tag{}
$$
$V$ -> Voltage provided to the circuit
$K_t$ -> torque constant
$f$ -> motor speed (rev/s or Hz)
$R$ -> Resistance of the motor


```python
class Motor:
    def __init__(self, torque_constant, resistance, voltage):
        self.Kt = torque_constant
        self.R = resistance
        self.V = voltage

    def current(self, speed):
        return (self.V - self.Kt * speed) / self.R

    def torque(self, speed):
        return self.Kt * self.current(speed)
```

b) You've selected 3 candidates to power an electrical powered car. with the specs given in the table below. The motor you're given is spec'ed with a torque constant $0.05 Nm/A$, resistance in the motor of $2.0 \Omega$ which is powered by battery of $12V$. 

| Motor | $K_t$ | $R$  | Battery |
| ----- | ----- | ---- | ------- |
| 1     | 0.05  | 2    | 12V     |
| 2     | 0.05  | 2    | 24V     |
| 3     | 0.1   | 1.75 | 12V     |
```python
motor1 = Motor(0.05, 2.0, 12)
motor2 = Motor(0.05, 2.0, 12)
motor3 = Motor(0.05, 2.0, 12)
print(motor1.torque(100))  # torque at 100 rad/s
```


#### Simulation Frameworks
Use of *inheritance* and *composition*. 
```python
class PropulsionSystem:
    def __init__(self, mass_flow_rate):
        self.mass_flow_rate = mass_flow_rate

    def thrust(self, exhaust_velocity):
        return self.mass_flow_rate * exhaust_velocity


class RocketEngine(PropulsionSystem):
    def __init__(self, mass_flow_rate, chamber_pressure, area_ratio):
        super().__init__(mass_flow_rate)
        self.chamber_pressure = chamber_pressure
        self.area_ratio = area_ratio

    def efficiency(self):
        return 0.98  # simplified example
```

#### Data Processing Pipeline
```python
class DataLogger:
    def __init__(self, filepath):
        import pandas as pd
        self.df = pd.read_csv(filepath)

    def filter_noise(self):
        self.df['filtered'] = self.df['signal'].rolling(5).mean()

    def plot(self):
        self.df[['signal', 'filtered']].plot()
```


#### Simulation of a mass-spring-damper system
Model a spring-mass-damper system using classes and 

```python
class Spring:
	def __init__(self, spring_constant)
		self.K = spring_constant
		self.
```
- B. Creating reusable components (e.g., `Material`, `Beam`, `Force`)

https://github.com/enesdemirag/programming-exercises/blob/master/exercises/mass-spring-damper-simulation.md

### Summary

```mermaid
flowchart TD

    A[OOP] --> B[Class]
    B --> C["Attributes (variables and data)"]
    B --> D["Methods (functions and behaviors)"]

    C -->|define structure| E[Class Definition]
    D -->|define behavior| E

    E -->|instantiation| F[Object]
    F --> G[Object has its own<br>attribute values]
    F --> H[Object can call methods]

```




---

Starting a class is done with the `class` keyword followed by the name of the class.
```python
class Dog:
	pass
```
In this example `pass` is used a placeholder to indicate where code will eventually go. This prevents python to output any errors. Next, give our class some properties and behaviors. For the purpose of this demonstration we will keep it small but in reality the class may be significantly more detailed.

The properties are defined inside the method called `.__init__()`. It runs automatically when a new object of a class is created. It's main purpose is to initialize the object attributes and set up its initial state. When an object is created, memory is allocated for it, and `__init__` helps organize that memory by assigning values to attributes. 

Let's add a name, breed and age attibute to our 


```python
class Dog:
	def __init__(self, name, breed="Mixed", age=1):
		self.name = name
		self.breed = breed
		self.age = age
		
dog1 = Dog("Buddy")
dog2 = Dog("Bolt", "German Sheppard", 5)

print(dog1.name, dog1.breed, dog1.age)
print(dog2.name, dog2.breed, dog2.age)
```

Notice how the `__init__` method defaults the inputs`breed="Mixed"` and `age=1` when no parameters are given.


```python

```


```python

```


```python

```


```python

```

https://eng.libretexts.org/Courses/Arkansas_Tech_University/Engineering_Modeling_and_Analysis_with_Python/01%3A_Introduction_to_Engineering_Modeling_and_Analysis_with_Python