Skip to content

Commit b897f53

Browse files
Fix flake8 and black linting violations across the codebase
1 parent 2c0e069 commit b897f53

13 files changed

Lines changed: 283 additions & 194 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
flake8 data/ qubo/ qaoa/ classical/ \
3030
--max-line-length=100 \
3131
--exclude=__pycache__,*.ipynb_checkpoints \
32-
--ignore=E402
32+
--ignore=E402,W503
3333
3434
- name: Run black (formatting check)
3535
run: |

classical/greedy.py

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
import os
1717

1818

19-
def load_portfolio_data(cached_dir='/Users/johngeorgealexander/qc/quantum-portfolio-opt/data/cached'):
19+
def load_portfolio_data(
20+
cached_dir="/Users/johngeorgealexander/qc/quantum-portfolio-opt/data/cached",
21+
):
2022
"""
2123
Load portfolio data from Day 2 cached results.
2224
@@ -26,11 +28,13 @@ def load_portfolio_data(cached_dir='/Users/johngeorgealexander/qc/quantum-portfo
2628
stock_names (list): List of stock names
2729
"""
2830
# Load mean returns (μ) and covariance (Σ)
29-
mu = np.load(os.path.join(cached_dir, 'mu.npy'))
30-
sigma = np.load(os.path.join(cached_dir, 'sigma.npy'))
31+
mu = np.load(os.path.join(cached_dir, "mu.npy"))
32+
sigma = np.load(os.path.join(cached_dir, "sigma.npy"))
3133

3234
# Load stock names from CSV
33-
expected_returns_df = pd.read_csv(os.path.join(cached_dir, 'expected_returns.csv'), index_col=0)
35+
expected_returns_df = pd.read_csv(
36+
os.path.join(cached_dir, "expected_returns.csv"), index_col=0
37+
)
3438
stock_names = expected_returns_df.index.tolist()
3539

3640
return mu, sigma, stock_names
@@ -66,8 +70,11 @@ def optimize_portfolio(mu, sigma, target_return):
6670

6771
# Constraints
6872
constraints = [
69-
{'type': 'eq', 'fun': lambda w: w.sum() - 1}, # weights sum to 1
70-
{'type': 'eq', 'fun': lambda w: portfolio_return(w, mu) - target_return} # target return
73+
{"type": "eq", "fun": lambda w: w.sum() - 1}, # weights sum to 1
74+
{
75+
"type": "eq",
76+
"fun": lambda w: portfolio_return(w, mu) - target_return,
77+
}, # target return
7178
]
7279

7380
# Bounds: no short selling (w_i >= 0)
@@ -78,10 +85,10 @@ def optimize_portfolio(mu, sigma, target_return):
7885
portfolio_variance,
7986
w0,
8087
args=(sigma,),
81-
method='SLSQP',
88+
method="SLSQP",
8289
bounds=bounds,
8390
constraints=constraints,
84-
options={'disp': False}
91+
options={"disp": False},
8592
)
8693

8794
if result.success:
@@ -118,12 +125,14 @@ def generate_efficient_frontier(mu, sigma, n_points=50):
118125
for target_ret in target_returns:
119126
w_opt, ret_opt, var_opt = optimize_portfolio(mu, sigma, target_ret)
120127
if w_opt is not None:
121-
portfolios.append({
122-
'return': ret_opt,
123-
'variance': var_opt,
124-
'volatility': np.sqrt(var_opt),
125-
'weights': w_opt
126-
})
128+
portfolios.append(
129+
{
130+
"return": ret_opt,
131+
"variance": var_opt,
132+
"volatility": np.sqrt(var_opt),
133+
"weights": w_opt,
134+
}
135+
)
127136

128137
return portfolios
129138

@@ -136,8 +145,8 @@ def get_efficient_frontier_data(mu, sigma, n_points=30):
136145
tuple: (returns_list, volatilities_list, portfolios_list)
137146
"""
138147
portfolios = generate_efficient_frontier(mu, sigma, n_points)
139-
returns = [p['return'] for p in portfolios]
140-
volatilities = [p['volatility'] for p in portfolios]
148+
returns = [p["return"] for p in portfolios]
149+
volatilities = [p["volatility"] for p in portfolios]
141150
return returns, volatilities, portfolios
142151

143152

@@ -168,22 +177,28 @@ def get_efficient_frontier_data(mu, sigma, n_points=30):
168177
portfolios = generate_efficient_frontier(mu, sigma, n_points=20)
169178
if portfolios:
170179
print(f"\nGenerated {len(portfolios)} portfolios on efficient frontier")
171-
print(f"Return range: {min(p['return'] for p in portfolios):.4f} to {max(p['return'] for p in portfolios):.4f}")
172-
print(f"Volatility range: {min(p['volatility'] for p in portfolios):.4f} to {max(p['volatility'] for p in portfolios):.4f}")
180+
min_ret = min(p["return"] for p in portfolios)
181+
max_ret = max(p["return"] for p in portfolios)
182+
print(f"Return range: {min_ret:.4f} to {max_ret:.4f}")
183+
184+
min_vol = min(p["volatility"] for p in portfolios)
185+
max_vol = max(p["volatility"] for p in portfolios)
186+
print(f"Volatility range: {min_vol:.4f} to {max_vol:.4f}")
173187

174188
print("=" * 70)
175189
import time
176190

191+
177192
def greedy_qubo_search(Q, k):
178193
"""
179194
Greedy heuristic to find an approximate solution for a QUBO.
180195
Iteratively adds the single asset that minimizes the objective
181196
until k assets are selected.
182-
197+
183198
Args:
184199
Q: QUBO matrix (numpy array)
185200
k: Int, number of stocks to pick
186-
201+
187202
Returns:
188203
best_x: numpy array (binary vector)
189204
best_obj: float
@@ -192,28 +207,28 @@ def greedy_qubo_search(Q, k):
192207
start_time = time.time()
193208
n = len(Q)
194209
x = np.zeros(n, dtype=int)
195-
210+
196211
# We want exactly k stocks
197212
for _ in range(k):
198213
best_gain = float("inf")
199214
best_idx = -1
200-
215+
201216
# Test flipping each 0 to 1
202217
for i in range(n):
203218
if x[i] == 0:
204219
x_test = x.copy()
205220
x_test[i] = 1
206221
obj = float(x_test @ Q @ x_test)
207-
222+
208223
if obj < best_gain:
209224
best_gain = obj
210225
best_idx = i
211-
226+
212227
# Permanently flip the best one
213228
if best_idx != -1:
214229
x[best_idx] = 1
215-
230+
216231
final_obj = float(x @ Q @ x)
217232
exec_time = time.time() - start_time
218-
233+
219234
return x, final_obj, exec_time

classical/sim_annealing.py

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,95 +2,94 @@
22
import time
33
import math
44

5-
def simulated_annealing_qubo(Q, k, T_start=1000.0, cooling_rate=0.99, max_iter=10000, seed=None):
5+
6+
def simulated_annealing_qubo(
7+
Q, k, T_start=1000.0, cooling_rate=0.99, max_iter=10000, seed=None
8+
):
69
"""
710
Simulated Annealing heuristic for the QUBO formulation of the portfolio problem.
811
Maintains exactly k selected assets at all times to satisfy the constraint naturally.
9-
12+
1013
Args:
1114
Q: QUBO matrix (numpy array)
1215
k: Int, number of stocks to pick
1316
T_start: Float, initial high temperature
1417
cooling_rate: Float, multiplier to cool temperature (e.g. 0.99)
1518
max_iter: Int, max iterations
1619
seed: Int, optional random seed for reproducibility
17-
20+
1821
Returns:
1922
best_x: numpy array (binary vector)
2023
best_obj: float
2124
exec_time: float
2225
"""
2326
if seed is not None:
2427
np.random.seed(seed)
25-
28+
2629
start_time = time.time()
2730
n = len(Q)
28-
31+
2932
# Random initial valid portfolio (exactly k ones)
3033
x_current = np.zeros(n, dtype=int)
3134
random_indices = np.random.choice(n, k, replace=False)
3235
x_current[random_indices] = 1
33-
36+
3437
current_obj = float(x_current @ Q @ x_current)
35-
38+
3639
best_x = x_current.copy()
3740
best_obj = current_obj
38-
41+
3942
T = T_start
40-
43+
4144
for _ in range(max_iter):
4245
# Stop if temperature is virtually 0
4346
if T < 1e-8:
4447
break
45-
48+
4649
# Generate a neighbor: flip one 1 to 0, and one 0 to 1
4750
x_neighbor = x_current.copy()
48-
51+
4952
# Find indices of current 1s and 0s
5053
ones_idx = np.where(x_neighbor == 1)[0]
5154
zeros_idx = np.where(x_neighbor == 0)[0]
52-
55+
5356
if len(ones_idx) > 0 and len(zeros_idx) > 0:
5457
# Pick a random 1 to flip to 0
5558
flip_to_zero = np.random.choice(ones_idx)
5659
# Pick a random 0 to flip to 1
5760
flip_to_one = np.random.choice(zeros_idx)
58-
61+
5962
x_neighbor[flip_to_zero] = 0
6063
x_neighbor[flip_to_one] = 1
61-
64+
6265
neighbor_obj = float(x_neighbor @ Q @ x_neighbor)
63-
66+
6467
# Delta E (change in energy)
6568
delta = neighbor_obj - current_obj
66-
69+
6770
# If neighbor is better (delta < 0), accept it!
6871
# If neighbor is worse, accept with probability e^(-delta / T)
6972
if delta < 0 or np.random.rand() < math.exp(-delta / T):
7073
x_current = x_neighbor.copy()
7174
current_obj = neighbor_obj
72-
75+
7376
# Keep track of absolute best found so far
7477
if current_obj < best_obj:
7578
best_obj = current_obj
7679
best_x = x_current.copy()
77-
80+
7881
# Cool down the temperature
7982
T *= cooling_rate
80-
83+
8184
exec_time = time.time() - start_time
82-
85+
8386
return best_x, best_obj, exec_time
8487

88+
8589
if __name__ == "__main__":
8690
# Simple test case
87-
Q_test = np.array([
88-
[-10, 2, 2, 5],
89-
[ 2, -8, 3, 1],
90-
[ 2, 3, -6, 4],
91-
[ 5, 1, 4,-12]
92-
])
93-
91+
Q_test = np.array([[-10, 2, 2, 5], [2, -8, 3, 1], [2, 3, -6, 4], [5, 1, 4, -12]])
92+
9493
print("Testing Simulated Annealing...")
9594
best_x, best_obj, t = simulated_annealing_qubo(Q_test, k=2)
9695
print(f"Best X: {best_x}")

classical/test_classical.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
# ── Fixtures ─────────────────────────────────────────────────────────────────
1818

19+
1920
@pytest.fixture
2021
def problem():
2122
"""Standard 5-asset test problem."""
@@ -32,12 +33,14 @@ def problem():
3233
def Q_matrix(problem):
3334
"""Pre-built Q matrix for SA tests."""
3435
from qubo.qubo_builder import build_Q_matrix
36+
3537
returns, cov, n, k = problem
3638
return build_Q_matrix(returns, cov, penalty=5.0, k=k), n, k
3739

3840

3941
# ── Greedy Tests ─────────────────────────────────────────────────────────────
4042

43+
4144
def test_greedy_output_length(Q_matrix):
4245
"""Greedy must return a binary vector of length n."""
4346
Q, n, k = Q_matrix
@@ -80,6 +83,7 @@ def test_greedy_prefers_high_return(Q_matrix, problem):
8083

8184
# ── Simulated Annealing Tests ─────────────────────────────────────────────────
8285

86+
8387
def test_sa_output_length(Q_matrix):
8488
"""SA must return a binary vector of length n."""
8589
Q, n, k = Q_matrix
@@ -120,6 +124,7 @@ def test_sa_reproducible_with_seed(Q_matrix):
120124
def test_sa_better_than_random(Q_matrix):
121125
"""SA should outperform a random feasible solution on average."""
122126
from qubo.qubo_builder import compute_objective
127+
123128
Q, n, k = Q_matrix
124129
_, sa_obj, _ = simulated_annealing_qubo(Q, k, seed=42)
125130

@@ -132,6 +137,6 @@ def test_sa_better_than_random(Q_matrix):
132137
random_objs.append(compute_objective(Q, x))
133138

134139
avg_random = np.mean(random_objs)
135-
assert sa_obj <= avg_random, (
136-
f"SA obj {sa_obj:.4f} worse than random avg {avg_random:.4f}"
137-
)
140+
assert (
141+
sa_obj <= avg_random
142+
), f"SA obj {sa_obj:.4f} worse than random avg {avg_random:.4f}"

0 commit comments

Comments
 (0)