Name: Huynh Tan Tien
Student ID: 2413468
Ensure Python 3.x and matplotlib are installed (pip install matplotlib). Execute the following commands from the project root (ga-assignment/):
- Run OOP:
python oop/run.py - Run FP:
python fp/run.py - Run Tests:
python -m unittest discover -s oop/testsandpython -m unittest discover -s fp/tests
Output: Convergence plots (.png) and execution logs (.json) will be generated directly in the reports/ directory.
The OOP model leverages abstraction and encapsulation to map biological entities to code. The Chromosome and Population classes manage local state internally. Crucially, I implemented the Strategy Design Pattern via abstract base classes (SelectionStrategy, CrossoverStrategy, MutationStrategy). This decouples algorithmic behaviors from the GeneticAlgorithm engine, adhering to the Open/Closed Principle and ensuring seamless extensibility.
The FP approach is strictly declarative, discarding classes in favor of immutable data pipelines. State mutation is eliminated by representing chromosomes as immutable tuples. All evolutionary operators are designed as pure functions—they accept inputs and return newly allocated tuples without side-effects. Higher-order functions like map() are utilized for concise, robust fitness evaluations.
Implementing the exact same optimization algorithm across two fundamentally different paradigms highlighted distinct architectural trade-offs between state management and data predictability.
The OOP paradigm provides a highly intuitive mental model for simulation. The biological nature of a Genetic Algorithm maps perfectly to objects interacting within a system. Furthermore, the Strategy pattern makes swapping heuristics (e.g., changing to a Two-Point Crossover) structurally elegant without modifying the core execution loop. However, OOP relies heavily on mutable states. In Python, this introduces the risk of accidental reference modifications (e.g., mutating a parent chromosome instead of its offspring), which required defensive programming techniques like copy.deepcopy to resolve.
Conversely, the FP paradigm excels in systemic predictability and mathematical purity. By strictly enforcing immutability, the entire class of bugs related to hidden state changes is eliminated by design. The logic flows as a clear transformation pipeline. However, this mathematical safety comes with a computational trade-off. Because tuples cannot be mutated in place, the FP approach requires reallocating memory for the entire population array at every single generation, leading to a slightly higher memory and garbage collection overhead compared to OOP.
Conclusion: OOP is structurally superior for modeling extensible simulations with intuitive hierarchies and state tracking. FP, while requiring a shift toward data transformation and incurring minor memory overhead, offers unmatched robustness, declarative clarity, and thread-safety.