Coverage for src/sparkle/instance/instances.py: 99%
87 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"""Objects and methods relating to instances for Sparkle."""
3from __future__ import annotations
4from pathlib import Path
6import csv
7import numpy as np
10class InstanceSet:
11 """Base object representation of a set of instances."""
13 def __init__(self: InstanceSet, target: Path | list[str, Path]) -> None:
14 """Initialise an Instances object from a directory.
16 Args:
17 target: The Path, or list of paths to create the instance set from.
18 """
19 self.directory: Path = target
20 self._instance_names: list[str] = []
21 self._instance_paths: list[Path] = []
23 @property
24 def size(self: InstanceSet) -> int:
25 """Returns the number of instances in the set."""
26 return len(self._instance_paths)
28 @property
29 def all_paths(self: InstanceSet) -> list[Path]:
30 """Returns all file paths in the instance set as a flat list."""
31 return self._instance_paths
33 @property
34 def instance_paths(self: InstanceSet) -> list[Path]:
35 """Get processed instance paths."""
36 return self._instance_paths
38 @property
39 def instance_names(self: InstanceSet) -> list[str]:
40 """Get processed instance names for instances."""
41 return self._instance_names
43 @property
44 def instance_pairs(self: InstanceSet) -> list[tuple[str, str]]:
45 """Return list of (set_name, instance_name) tuples."""
46 return [(self.directory.name, name) for name in self._instance_names]
48 @property
49 def instances(self: InstanceSet) -> list[str]:
50 """Get instance names with relative path."""
51 return [str(p.with_suffix("")) for p in self._instance_paths]
53 @property
54 def name(self: InstanceSet) -> str:
55 """Get instance set name."""
56 return self.directory.name
58 def __str__(self: InstanceSet) -> str:
59 """Get the string representation of an Instance Set."""
60 return self.name
62 def __repr__(self: InstanceSet) -> str:
63 """Get detailed representation of an Instance Set."""
64 return (
65 f"{self.name}:\n"
66 f"\t- Type: {type(self).__name__}\n"
67 f"\t- Directory: {self.directory}\n"
68 f"\t- # Instances: {self.size}"
69 )
71 def get_path_by_name(self: InstanceSet, name: str) -> Path | list[Path]:
72 """Retrieves an instance paths by its name. Returns None upon failure."""
73 for idx, instance_name in enumerate(self._instance_names):
74 if instance_name == name:
75 return self._instance_paths[idx]
76 return None
79class FileInstanceSet(InstanceSet):
80 """Object representation of a set of single-file instances."""
82 def __init__(self: FileInstanceSet, target: Path) -> None:
83 """Initialise an InstanceSet, where each instance is a file in the directory.
85 Args:
86 target: Path to the instances directory. If multiple files are found,
87 they are assumed to have the same number of instances per file.
88 """
89 super().__init__(target)
90 self._name: str = target.stem
91 if target.is_file():
92 # Single instance set
93 self._instance_paths = [target]
94 self.directory = target.parent
95 # NOTE: We name instances by their stem, assuming every instance in the set
96 # shares the same extension (which is the case in general). If a set ever
97 # mixes extensions on the same stem (e.g. instance1.cnf and instance1.csv),
98 # the bare stems would collide; in that case keep the full filename
99 # (target.name) here to disambiguate.
100 self._instance_names = [target.stem]
101 else:
102 # Default situation, treat each file in the directory as an instance
103 self._instance_paths = [p for p in self.directory.iterdir()]
104 self._instance_names = [p.stem for p in self._instance_paths]
106 @property
107 def name(self: FileInstanceSet) -> str:
108 """Get instance set name."""
109 return self._name
112class MultiFileInstanceSet(InstanceSet):
113 """Object representation of a set of multi-file instances."""
115 instance_csv = "instances.csv"
117 def __init__(self: MultiFileInstanceSet, target: Path) -> None:
118 """Initialise an Instances object from a directory.
120 Args:
121 target: Path to the instances directory. Will read from instances.csv.
122 """
123 target_dir = target.parent if not target.is_dir() else target
124 super().__init__(target_dir)
125 # A path pointing to the directory of instances
126 self.instance_file = self.directory / MultiFileInstanceSet.instance_csv
127 # Read from instance_file
128 if not target.is_dir():
129 # Single file
130 instance_list = [
131 line
132 for line in csv.reader(self.instance_file.open())
133 if target.stem in line
134 ]
135 else:
136 # Multi file
137 instance_list = [line for line in csv.reader(self.instance_file.open())]
139 for instance in instance_list:
140 self._instance_names.append(instance[0])
141 self._instance_paths.append(
142 [(self.directory / f) if isinstance(f, str) else f for f in instance[1:]]
143 )
145 @property
146 def all_paths(self: MultiFileInstanceSet) -> list[Path]:
147 """Returns all file paths in the instance set as a flat list."""
148 return [p for instance in self._instance_paths for p in instance] + [
149 self.instance_file
150 ]
152 @property
153 def instances(self: InstanceSet) -> list[str]:
154 """Get instance names with relative path for multi-file instances."""
155 return [self.directory / inst_name for inst_name in self.instance_names]
158class IterableFileInstanceSet(InstanceSet):
159 """Object representation of files containing multiple instances."""
161 supported_filetypes = set([".csv", ".npy"])
163 def __init__(self: IterableFileInstanceSet, target: Path) -> None:
164 """Initialise an InstanceSet from a single file.
166 Args:
167 target: Path to the instances directory. If multiple files are found,
168 they are assumed to have the same number of instances.
169 """
170 super().__init__(target)
171 self._instance_paths = [
172 path
173 for path in self.directory.iterdir()
174 if path.suffix in IterableFileInstanceSet.supported_filetypes
175 ]
176 self._size = IterableFileInstanceSet.__determine_size__(self._instance_paths[0])
177 self._instance_names = [path.name for path in self._instance_paths]
179 @property
180 def size(self: IterableFileInstanceSet) -> int:
181 """Returns the number of instances in the set."""
182 return self._size
184 @staticmethod
185 def __determine_size__(file: Path) -> int:
186 """Determine the number of instances in a file."""
187 match file.suffix:
188 case ".csv":
189 return len(file.open().readlines())
190 case ".npy":
191 return len(np.load(file))