Spaces:
Runtime error
Runtime error
fix: CLI tests and FAISS edge case handling
Browse files- Fix mock calibration data format (use dicts with S_i/exact/partial keys)
- Filter out FAISS placeholder results (idx=-1) when k > database size
- Handle empty CSV when threshold filters all results
- Fix SLURM partition name for local cluster
All 24 CLI tests now pass.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- protein_conformal/cli.py +3 -0
- scripts/slurm_test_clean_embed.sh +1 -3
- tests/test_cli.py +36 -17
protein_conformal/cli.py
CHANGED
|
@@ -246,6 +246,9 @@ def cmd_search(args):
|
|
| 246 |
for j in range(args.k):
|
| 247 |
sim = D[i, j]
|
| 248 |
idx = I[i, j]
|
|
|
|
|
|
|
|
|
|
| 249 |
if args.threshold and sim < args.threshold:
|
| 250 |
continue
|
| 251 |
row = {
|
|
|
|
| 246 |
for j in range(args.k):
|
| 247 |
sim = D[i, j]
|
| 248 |
idx = I[i, j]
|
| 249 |
+
# Skip placeholder results (FAISS returns -1 for non-existent neighbors)
|
| 250 |
+
if idx < 0:
|
| 251 |
+
continue
|
| 252 |
if args.threshold and sim < args.threshold:
|
| 253 |
continue
|
| 254 |
row = {
|
scripts/slurm_test_clean_embed.sh
CHANGED
|
@@ -1,8 +1,6 @@
|
|
| 1 |
#!/bin/bash
|
| 2 |
#SBATCH --job-name=test_clean_embed
|
| 3 |
-
#SBATCH --partition=
|
| 4 |
-
#SBATCH --account=co_doudna
|
| 5 |
-
#SBATCH --qos=doudna_gpu4_normal
|
| 6 |
#SBATCH --nodes=1
|
| 7 |
#SBATCH --ntasks=1
|
| 8 |
#SBATCH --cpus-per-task=4
|
|
|
|
| 1 |
#!/bin/bash
|
| 2 |
#SBATCH --job-name=test_clean_embed
|
| 3 |
+
#SBATCH --partition=gpu
|
|
|
|
|
|
|
| 4 |
#SBATCH --nodes=1
|
| 5 |
#SBATCH --ntasks=1
|
| 6 |
#SBATCH --cpus-per-task=4
|
tests/test_cli.py
CHANGED
|
@@ -205,12 +205,18 @@ def test_search_with_threshold(tmp_path):
|
|
| 205 |
assert result.returncode == 0
|
| 206 |
assert output_file.exists()
|
| 207 |
|
| 208 |
-
|
| 209 |
-
#
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
assert
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
|
| 215 |
|
| 216 |
def test_search_with_metadata(tmp_path):
|
|
@@ -263,15 +269,18 @@ def test_prob_with_mock_data(tmp_path):
|
|
| 263 |
"""Test prob command with mock calibration data and scores."""
|
| 264 |
np.random.seed(42)
|
| 265 |
|
| 266 |
-
# Create mock calibration data (
|
| 267 |
-
# Each sample should have similarity scores and labels
|
| 268 |
n_calib = 50
|
| 269 |
cal_data = []
|
| 270 |
for i in range(n_calib):
|
| 271 |
-
# Each sample: (query_emb, lookup_emb, sims, labels, metadata...)
|
| 272 |
sims = np.random.uniform(0.998, 0.9999, size=10).astype(np.float32)
|
| 273 |
-
|
| 274 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
|
| 276 |
cal_file = tmp_path / "calibration.npy"
|
| 277 |
np.save(cal_file, np.array(cal_data, dtype=object))
|
|
@@ -314,13 +323,18 @@ def test_prob_with_csv_input(tmp_path):
|
|
| 314 |
"""Test prob command with CSV input (e.g., from search results)."""
|
| 315 |
np.random.seed(42)
|
| 316 |
|
| 317 |
-
# Create mock calibration data
|
| 318 |
n_calib = 30
|
| 319 |
cal_data = []
|
| 320 |
for i in range(n_calib):
|
| 321 |
sims = np.random.uniform(0.998, 0.9999, size=5).astype(np.float32)
|
| 322 |
-
|
| 323 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
|
| 325 |
cal_file = tmp_path / "calibration.npy"
|
| 326 |
np.save(cal_file, np.array(cal_data, dtype=object))
|
|
@@ -366,14 +380,19 @@ def test_calibrate_with_mock_data(tmp_path):
|
|
| 366 |
"""Test calibrate command with mock calibration data."""
|
| 367 |
np.random.seed(42)
|
| 368 |
|
| 369 |
-
# Create mock calibration data with
|
| 370 |
n_samples = 100
|
| 371 |
cal_data = []
|
| 372 |
for i in range(n_samples):
|
| 373 |
sims = np.random.uniform(0.997, 0.9999, size=10).astype(np.float32)
|
| 374 |
# Create labels: higher similarity -> higher chance of being positive
|
| 375 |
-
|
| 376 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 377 |
|
| 378 |
cal_file = tmp_path / "calibration.npy"
|
| 379 |
np.save(cal_file, np.array(cal_data, dtype=object))
|
|
|
|
| 205 |
assert result.returncode == 0
|
| 206 |
assert output_file.exists()
|
| 207 |
|
| 208 |
+
# With high threshold on random embeddings, file may be empty or have few results
|
| 209 |
+
# Random unit vectors have expected cosine similarity ~0, so 0.9 threshold filters most
|
| 210 |
+
try:
|
| 211 |
+
df = pd.read_csv(output_file)
|
| 212 |
+
# With high threshold, we should have fewer results
|
| 213 |
+
assert len(df) <= 3 * 10 # At most 3 queries * 10 neighbors
|
| 214 |
+
# All results should be above threshold
|
| 215 |
+
if len(df) > 0:
|
| 216 |
+
assert df['similarity'].min() >= 0.9
|
| 217 |
+
except pd.errors.EmptyDataError:
|
| 218 |
+
# Empty file is valid - no results passed threshold
|
| 219 |
+
pass
|
| 220 |
|
| 221 |
|
| 222 |
def test_search_with_metadata(tmp_path):
|
|
|
|
| 269 |
"""Test prob command with mock calibration data and scores."""
|
| 270 |
np.random.seed(42)
|
| 271 |
|
| 272 |
+
# Create mock calibration data (format: array of dicts with S_i, exact, partial)
|
|
|
|
| 273 |
n_calib = 50
|
| 274 |
cal_data = []
|
| 275 |
for i in range(n_calib):
|
|
|
|
| 276 |
sims = np.random.uniform(0.998, 0.9999, size=10).astype(np.float32)
|
| 277 |
+
exact_labels = (np.random.random(10) < 0.2).astype(bool)
|
| 278 |
+
partial_labels = exact_labels | (np.random.random(10) < 0.1)
|
| 279 |
+
cal_data.append({
|
| 280 |
+
"S_i": sims,
|
| 281 |
+
"exact": exact_labels,
|
| 282 |
+
"partial": partial_labels,
|
| 283 |
+
})
|
| 284 |
|
| 285 |
cal_file = tmp_path / "calibration.npy"
|
| 286 |
np.save(cal_file, np.array(cal_data, dtype=object))
|
|
|
|
| 323 |
"""Test prob command with CSV input (e.g., from search results)."""
|
| 324 |
np.random.seed(42)
|
| 325 |
|
| 326 |
+
# Create mock calibration data (format: array of dicts with S_i, exact, partial)
|
| 327 |
n_calib = 30
|
| 328 |
cal_data = []
|
| 329 |
for i in range(n_calib):
|
| 330 |
sims = np.random.uniform(0.998, 0.9999, size=5).astype(np.float32)
|
| 331 |
+
exact_labels = (np.random.random(5) < 0.2).astype(bool)
|
| 332 |
+
partial_labels = exact_labels | (np.random.random(5) < 0.1)
|
| 333 |
+
cal_data.append({
|
| 334 |
+
"S_i": sims,
|
| 335 |
+
"exact": exact_labels,
|
| 336 |
+
"partial": partial_labels,
|
| 337 |
+
})
|
| 338 |
|
| 339 |
cal_file = tmp_path / "calibration.npy"
|
| 340 |
np.save(cal_file, np.array(cal_data, dtype=object))
|
|
|
|
| 380 |
"""Test calibrate command with mock calibration data."""
|
| 381 |
np.random.seed(42)
|
| 382 |
|
| 383 |
+
# Create mock calibration data (format: array of dicts with S_i, exact, partial)
|
| 384 |
n_samples = 100
|
| 385 |
cal_data = []
|
| 386 |
for i in range(n_samples):
|
| 387 |
sims = np.random.uniform(0.997, 0.9999, size=10).astype(np.float32)
|
| 388 |
# Create labels: higher similarity -> higher chance of being positive
|
| 389 |
+
exact_labels = (sims > 0.9995).astype(bool)
|
| 390 |
+
partial_labels = (sims > 0.999).astype(bool)
|
| 391 |
+
cal_data.append({
|
| 392 |
+
"S_i": sims,
|
| 393 |
+
"exact": exact_labels,
|
| 394 |
+
"partial": partial_labels,
|
| 395 |
+
})
|
| 396 |
|
| 397 |
cal_file = tmp_path / "calibration.npy"
|
| 398 |
np.save(cal_file, np.array(cal_data, dtype=object))
|