Coverage for src/sparkle/selector/extractor_cli.py: 92%
48 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-08 12:00 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-08 12:00 +0000
1#!/usr/bin/env python3
2# -*- coding: UTF-8 -*-
3"""Execute Feature Extractor for an instance, write features to FeatureDataFrame."""
5import argparse
6from pathlib import Path
7from filelock import FileLock
9from sparkle.structures import FeatureDataFrame
10from sparkle.selector import Extractor
11from sparkle.instance import resolve_instance_pair
14if __name__ == "__main__":
15 # Define command line arguments
16 parser = argparse.ArgumentParser()
17 parser.add_argument(
18 "--extractor", required=True, type=Path, help="path to feature extractor"
19 )
20 parser.add_argument(
21 "--instance",
22 required=True,
23 type=Path,
24 nargs="+",
25 help="path to instance file(s) to run on",
26 )
27 parser.add_argument(
28 "--feature-csv", required=True, type=Path, help="path to feature data CSV file"
29 )
30 parser.add_argument(
31 "--cutoff",
32 required=True,
33 type=str,
34 help="the maximum CPU time for the extractor.",
35 )
36 parser.add_argument(
37 "--feature-group",
38 required=False,
39 type=str,
40 help="the group of features to compute, if available for the "
41 "extractor. If not available or provided, all groups will"
42 " be computed.",
43 )
44 parser.add_argument(
45 "--log-dir", type=Path, required=True, help="path to the log directory"
46 )
47 args = parser.parse_args()
49 # Process command line arguments
50 log_dir = args.log_dir
52 # Instance agument is a list to allow for multifile instances
53 instance_path: list[Path] = args.instance
54 # We only receive the instance path on the CLI, but the FeatureDataFrame is keyed by
55 # the canonical (set_name, instance_name) pair, so resolve it from the path. All files
56 # of a multi-file instance share the same pair, so the first file resolves it.
57 instance_set_name, instance_name = resolve_instance_pair(instance_path[0])
58 extractor_path = args.extractor
59 feature_data_csv_path = args.feature_csv
60 cutoff_extractor = args.cutoff
62 # Ensure stringifcation of path objects
63 if isinstance(instance_path, list):
64 instance_list = [str(filepath) for filepath in instance_path]
65 else:
66 instance_list = [str(instance_path)]
68 extractor = Extractor(extractor_path)
69 if args.feature_group:
70 print(
71 f"Calling {extractor.name} with feature group {args.feature_group} for instance {instance_list} with cutoff {cutoff_extractor}"
72 )
73 else:
74 print(
75 f"Calling {extractor.name} for instance {instance_list} with cutoff {cutoff_extractor}"
76 )
78 features = extractor.run(
79 instance_list,
80 feature_group=args.feature_group,
81 cutoff_time=cutoff_extractor,
82 log_dir=log_dir,
83 )
85 if features is None or len(features) == 0:
86 raise ValueError(
87 "No features found! This may be due to a timeout. Check extractor logs."
88 )
90 feature_data_per_group = {}
91 for feature_group, feature_name, value in features:
92 if feature_group not in feature_data_per_group:
93 feature_data_per_group[feature_group] = [[], []]
94 print(
95 f"{extractor_path.name} {instance_set_name} {instance_name} {feature_group} {feature_name} | {value}"
96 ) # For logging purposes
97 feature_data_per_group[feature_group][0] += [feature_name]
98 feature_data_per_group[feature_group][1] += [float(value)]
100 # Now that we have our result, we write it to the FeatureDataCSV with a FileLock
101 lock = FileLock(f"{feature_data_csv_path}.lock")
102 if features is not None:
103 print("Writing features to file...")
104 with lock.acquire(timeout=600):
105 feature_data = FeatureDataFrame(feature_data_csv_path)
106 for feature_group, (
107 feature_names,
108 feature_values,
109 ) in feature_data_per_group.items():
110 # for feature_group, feature_name, value in features:
111 feature_data.set_value(
112 instance_set_name,
113 instance_name,
114 extractor_path.name,
115 feature_group,
116 feature_names,
117 feature_values,
118 append_write_csv=True,
119 )
120 lock.release()
121 print("Writing successful!")
122 else:
123 print(
124 "EXCEPTION during retrieving extractor results.\n"
125 f"****** WARNING: Feature vector computation on instance {instance_path}"
126 " failed! ******"
127 )