How We Iterated on Optimization as the Usage Grew
The optimization layer was part of a marketing investment planning platform used to allocate budgets across markets, channels, and campaigns. The response curves used in optimization were modeled upstream and treated as fixed inputs.
In the early stages, the focus was on getting the right answers. Performance was not a primary concern initially, as the module was still stabilizing and usage was limited.
Starting Point: Accuracy Over Speed
When we first built the optimization module, our priority was correctness and confidence in the results. We needed a reliable baseline to validate the objective formulation, constraints, and overall behavior of the system.
At this stage, the goal was not to optimize runtime, but to ensure that the outputs made sense and could be trusted before the platform saw wider usage.
First Approach: Genoud as a Baseline
The initial implementation used genoud, a genetic algorithm–based optimizer. While complex and computationally expensive, it handled constraints well and required relatively little custom development.
In hindsight, this approach was clearly overengineered for long-term use. However, it played an important role:
-
It produced highly accurate and stable results
-
It helped validate assumptions and constraint logic
-
It built confidence in the optimization outputs among stakeholders
The trade-off became evident over time. Even smaller optimizations took several minutes, and portfolio-level runs could take more than a full day, which limited how frequently the system could be used.
When Performance Became the Bottleneck
As the platform matured and optimization moved closer to real planning workflows, performance emerged as the biggest pain point. By this point, the module was stable, well-tested, and trusted — which made performance limitations more visible.
This is when it became clear that the optimization approach needed to change.
Second Approach: Switching to solnp
The move to solnp was driven by a very pragmatic goal: make the optimization faster without changing too much code.
At this point, large refactors carried real risk. More changes meant more testing, more validation effort, and a higher chance of introducing new bugs. solnp allowed us to replace the optimization step while keeping around 90% of the surrounding code unchanged.
This resulted in a noticeable improvement:
-
Smaller optimizations completed in a few minutes
-
Full portfolio runs completed in a few hours, depending on size
At the time, this felt like a strong and possibly final solution.
When “Faster” Became the New Normal
Before the move to solnp, portfolio-level optimization was used sparingly. Long runtimes made it something teams ran only when absolutely necessary, rather than as part of regular planning workflows.
Once execution times dropped, portfolio optimization became more accessible and more frequently used. As usage increased, expectations changed — what initially felt fast enough soon became the new baseline.
This shift in behavior exposed new performance limits. As portfolio optimizations became routine rather than exceptional, further improvements were needed to keep the workflow responsive at scale.
Final Approach: Python and a Custom Algorithm
This realization led to a more deliberate decision: a full refactor of the optimization module.
Rather than iterating further on solver libraries, we rebuilt the optimization logic from scratch:
-
Migrated the implementation from R to Python
-
Rewrote the core logic using NumPy-based vectorized operations
-
Implemented a custom, gradient-based optimization algorithm tailored to the structure of the problem
This was a longer effort and effectively a complete remake of the module. The payoff was significant: all tested portfolios completed within an hour, even when using performance-testing datasets several times larger than production data.
What Surprised Us
One of the biggest learnings was how quickly performance expectations evolve. Each improvement reset the baseline for what users considered “fast enough”.
Another insight was that solver choice mattered less than algorithm structure, data movement, and constraint handling, especially at scale.
Takeaways
-
Optimization systems often evolve organically, not by upfront design
-
Accuracy-first approaches are valuable when problem understanding is still developing
-
Low-risk changes buy time but rarely solve scaling problems completely
-
Meaningful performance gains usually require architectural changes
-
User expectations shift as systems improve, redefining bottlenecks over time