Allocating budget across 100+ projects using VectorVariable
Published
February 8, 2026
1 Overview
This example demonstrates Optyx v1.2.0’s scalability by solving a resource allocation problem with 100 decision variables. Before VectorVariable, this would require tedious loops and error-prone indexing.
Notev1.2.0 Feature
This example uses VectorVariable to create 100 decision variables in one line, with vectorized operations for constraints and objectives.
2 The Problem
A company must allocate its annual budget across 100 projects. Each project has:
Constraints: - Total allocation must equal 100% (fully invested) - Can’t exceed available resources - Max 10% per project (diversification) - Max 30% per sector (sector limit)
# The old way: tedious loopsfrom optyx import Variable, Problemallocation = [Variable(f"alloc_{i}", lb=0, ub=0.1) for i inrange(100)]# Objective via loopobjective =sum(allocation[i] * expected_returns[i] for i inrange(100))# Budget constraint via loop budget =sum(allocation[i] for i inrange(100))# Resource constraints via nested loopsfor r inrange(3): usage =sum(allocation[i] * resource_requirements[r, i] for i inrange(100))# ...add constraint
Use Parameter to efficiently explore different risk aversion levels:
risk_levels = [0.5, 1.0, 2.0, 5.0]print("Risk Aversion Sensitivity:")print(f"{'Lambda':>10}{'Return':>10}{'Risk':>10}{'Time':>10}")print("-"*42)for lam in risk_levels: risk_aversion.set(lam) # Fast parameter update start = time.perf_counter() sol = problem.solve(method="SLSQP") elapsed = time.perf_counter() - start alloc = np.array([sol[f"alloc[{i}]"] for i inrange(n_projects)]) ret = alloc @ expected_returns risk = alloc @ risk_scoresprint(f"{lam:>10.1f}{ret*100:>9.2f}% {risk:>10.4f}{elapsed*1000:>8.1f} ms")
Risk Aversion Sensitivity:
Lambda Return Risk Time
------------------------------------------
0.5 21.63% 0.1720 44.2 ms
1.0 19.99% 0.1478 41.1 ms
2.0 17.84% 0.1331 29.0 ms
5.0 15.00% 0.1244 29.8 ms
TipParameter Efficiency
Notice the solve times after the first solve. The Parameter class allows updating values without rebuilding the problem structure, making repeated solves much faster.
9 Code Comparison
9.1 Lines of Code
Approach
Variable Creation
Objective
Constraints
Total
Loop-based
~100 lines
~5 lines
~20 lines
~125 lines
VectorVariable
1 line
3 lines
~10 lines
~15 lines
9.2 Build Time
def build_loop_based():from optyx import Variable, Problem alloc = [Variable(f"a_{i}", lb=0, ub=0.1) for i inrange(100)] obj =sum(alloc[i] * expected_returns[i] for i inrange(100)) prob = Problem().maximize(obj).subject_to(sum(alloc).eq(1))return probdef build_vector_based():from optyx import VectorVariable, Problem alloc = VectorVariable("a", 100, lb=0, ub=0.1) prob = Problem().maximize(expected_returns @ alloc).subject_to(alloc.sum().eq(1))return prob# Time comparisonstart = time.perf_counter()_ = build_loop_based()loop_time = time.perf_counter() - startstart = time.perf_counter()_ = build_vector_based()vec_time = time.perf_counter() - startprint(f"Loop-based build: {loop_time*1000:.2f} ms")print(f"VectorVariable build: {vec_time*1000:.2f} ms")print(f"Speedup: {loop_time/vec_time:.1f}x")
Loop-based build: 0.93 ms
VectorVariable build: 0.52 ms
Speedup: 1.8x
10 Summary
This example demonstrated:
Feature
Benefit
VectorVariable("alloc", 100)
Create 100 variables in one line
returns @ allocation
Vectorized dot product
allocation.sum().eq(1)
Clean constraint syntax
Parameter("lambda")
Fast sensitivity analysis
Key takeaways:
VectorVariable scales — 100+ variables without code bloat
Vectorized operations — No loops for objectives/constraints
Parameter for scenarios — Fast re-solves when data changes
Cleaner code — Easier to read, write, and maintain