ai-nk-cce 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. ai_nk_cce-0.1.0.dist-info/METADATA +118 -0
  2. ai_nk_cce-0.1.0.dist-info/RECORD +46 -0
  3. ai_nk_cce-0.1.0.dist-info/WHEEL +4 -0
  4. api/__init__.py +0 -0
  5. api/mpcdf_vllm.py +94 -0
  6. evals/nk_model.py +277 -0
  7. model/README.md +64 -0
  8. model/config/dataset_conv_v1.yml +9 -0
  9. model/config/dataset_conv_v2_m2.yml +9 -0
  10. model/config/dataset_conv_v3_m2_assembl_nearest.yml +9 -0
  11. model/config/dataset_debug.yml +9 -0
  12. model/config/dataset_v4_int_format.yml +9 -0
  13. model/config/dataset_v5.yml +9 -0
  14. model/config/inference.yml +7 -0
  15. model/config/train.yml +24 -0
  16. model/config/train_debug.yml +19 -0
  17. model/config/train_from_checkpoint.yml +24 -0
  18. model/config/train_from_checkpoint_debug.yml +19 -0
  19. model/config/train_grpo.yml +30 -0
  20. model/config/train_grpo_debug.yml +30 -0
  21. model/config/train_grpo_debug_vllm.yml +32 -0
  22. model/config.py +54 -0
  23. model/dataset.py +324 -0
  24. model/inference.py +51 -0
  25. model/nk_assistant.py +207 -0
  26. model/parser.py +70 -0
  27. model/run_slurm.py +335 -0
  28. model/score.ipynb +596 -0
  29. model/scripts/template.slurm +54 -0
  30. model/scripts/template_rl.slurm +54 -0
  31. model/train.py +293 -0
  32. nk_model/__init__.py +0 -0
  33. nk_model/assembler.py +112 -0
  34. nk_model/biased_prediction_agent.py +389 -0
  35. nk_model/dataset.py +434 -0
  36. nk_model/enums.py +21 -0
  37. nk_model/landscape_cache.py +149 -0
  38. nk_model/models.py +172 -0
  39. nk_model/nk_landscape.py +498 -0
  40. simulation/hill_climber_simulation.py +211 -0
  41. simulation/hill_climber_vs_ai_simulation.py +132 -0
  42. simulation/landscape_selection.py +179 -0
  43. utils/__init__.py +0 -0
  44. utils/binary_conversion.py +128 -0
  45. utils/logging.py +33 -0
  46. utils/utils.py +51 -0
@@ -0,0 +1,211 @@
1
+ import logging
2
+ from enum import Enum
3
+ from typing import List, Optional, Type
4
+
5
+ import numpy as np
6
+ from pydantic import BaseModel
7
+
8
+ from src.model.config import ParserConfig
9
+ from src.model.dataset import binary_vectors_within_radius
10
+ from src.model.nk_assistant import NKAssistant, NKAssistantConfig
11
+ from src.nk_model.models import NKParams
12
+ from src.nk_model.nk_landscape import NKLandscape
13
+
14
+ logger = logging.getLogger()
15
+
16
+
17
+ class StopCondition(Enum):
18
+ MAX_ITERATIONS = "max_iterations"
19
+ REACH_PEAK = "reach_peak"
20
+
21
+
22
+ class AgentType(Enum):
23
+ HILL_CLIMBER = "hill_climber"
24
+ AI_HILL_CLIMBER = "ai_hill_climber"
25
+
26
+ def agent_class(self) -> Type["HillClimberAgent"]:
27
+ if self == AgentType.HILL_CLIMBER:
28
+ return HillClimberAgent
29
+ elif self == AgentType.AI_HILL_CLIMBER:
30
+ return AIHillClimberAgent
31
+ else:
32
+ raise ValueError(f"Invalid agent type: {self}")
33
+
34
+
35
+ class HillClimberSimConfig(BaseModel):
36
+ nk_params: NKParams
37
+ hamming_distance: int
38
+ agent_type: AgentType
39
+ start_idxs: List[int]
40
+ agent_start_idx: Optional[int] = None
41
+ use_trajectory: Optional[bool] = False
42
+ stop_condition: StopCondition
43
+ stop_condition_kwargs: dict
44
+
45
+
46
+ class HillClimberAgent:
47
+ def __init__(self, start_idx: int):
48
+ self.trajectory = [start_idx]
49
+
50
+ def _get_current_idx(self) -> int:
51
+ return self.trajectory[-1]
52
+
53
+ def hill_climb(
54
+ self,
55
+ n: int,
56
+ hamming_distance: int,
57
+ ) -> int:
58
+ neighbors = binary_vectors_within_radius(
59
+ origin=self._get_current_idx(),
60
+ radius=hamming_distance,
61
+ n_dim=n,
62
+ )
63
+ next_idx = np.random.choice(neighbors)
64
+
65
+ # Only advance if the next index is not in the trajectory
66
+ if next_idx is not self._get_current_idx():
67
+ self.trajectory.append(next_idx)
68
+ return next_idx
69
+ else:
70
+ return self.hill_climb(n, hamming_distance)
71
+
72
+
73
+ class AIHillClimberAgent(HillClimberAgent):
74
+ def __init__(self, start_idx: int, use_trajectory: bool):
75
+ super().__init__(start_idx)
76
+ self.use_trajectory = use_trajectory
77
+ assistant_config = NKAssistantConfig(
78
+ parser_config=ParserConfig(include_payoff=True),
79
+ use_mpcdf_vllm=True,
80
+ generation_params={"temperature": 0.7},
81
+ )
82
+ self.assistant = NKAssistant(config=assistant_config)
83
+
84
+ def hill_climb(
85
+ self,
86
+ n: int,
87
+ k: int,
88
+ power_scale: float,
89
+ hamming_distance: int,
90
+ sample_idxs: List[int],
91
+ payoffs: List[float],
92
+ ) -> int:
93
+ logger.debug(f"sample_idxs: {sample_idxs}")
94
+ if self.use_trajectory:
95
+ sample_idxs = self.trajectory
96
+ logger.debug(f"Using trajectory: {sample_idxs}")
97
+ else:
98
+ sample_idxs = sample_idxs[-16:]
99
+ extracted_payoffs = [payoffs[idx] for idx in sample_idxs]
100
+ logger.debug(f"extracted_payoffs: {extracted_payoffs}")
101
+
102
+ origin_idx = self._get_current_idx()
103
+ suggestion = self.assistant.suggest(
104
+ n=n,
105
+ k=k,
106
+ power_scale=power_scale,
107
+ hamming_distance=hamming_distance,
108
+ sample_idxs=sample_idxs,
109
+ origin_idx=origin_idx,
110
+ payoffs=extracted_payoffs,
111
+ )
112
+ self.trajectory.append(suggestion)
113
+ return suggestion
114
+
115
+
116
+ class HillClimberSimulation:
117
+ def __init__(self, config: HillClimberSimConfig):
118
+ landscape = NKLandscape(params=config.nk_params)
119
+ self._initialize(config, landscape)
120
+
121
+ def _initialize(
122
+ self, config: HillClimberSimConfig, landscape: NKLandscape
123
+ ):
124
+ """Initialize shared components of the simulation.
125
+
126
+ Args:
127
+ config: Configuration for the simulation
128
+ landscape: The NK landscape instance to use
129
+ """
130
+ self.config = config
131
+ self.visited_idxs = set(config.start_idxs)
132
+ self.landscape = landscape
133
+ self.ordered_payoffs = landscape.get_ordered_payoffs()
134
+
135
+ # Initialize the agent
136
+ if (
137
+ config.agent_start_idx is None
138
+ or config.agent_start_idx not in config.start_idxs
139
+ ):
140
+ self.agent_start_idx = np.random.choice(config.start_idxs)
141
+ else:
142
+ self.agent_start_idx = config.agent_start_idx
143
+
144
+ if config.agent_type == AgentType.HILL_CLIMBER:
145
+ self.agent = HillClimberAgent(start_idx=self.agent_start_idx)
146
+ elif config.agent_type == AgentType.AI_HILL_CLIMBER:
147
+ self.agent = AIHillClimberAgent(
148
+ start_idx=self.agent_start_idx,
149
+ use_trajectory=config.use_trajectory,
150
+ )
151
+
152
+ @classmethod
153
+ def from_cached_landscape(
154
+ cls, config: HillClimberSimConfig, landscape_uuid: str
155
+ ) -> "HillClimberSimulation":
156
+ """Create simulation using a cached landscape by UUID.
157
+
158
+ Args:
159
+ config: Configuration for the simulation
160
+ landscape_uuid: UUID of the cached landscape to use
161
+
162
+ Returns:
163
+ HillClimberSimulation instance with the cached landscape
164
+ """
165
+ instance = cls.__new__(cls)
166
+ landscape = NKLandscape.from_cache(landscape_uuid)
167
+ instance._initialize(config, landscape)
168
+ return instance
169
+
170
+ def run(self):
171
+ while not self._break_hill_climber(self.agent.trajectory):
172
+ if self.config.agent_type == AgentType.HILL_CLIMBER:
173
+ self.agent.hill_climb(
174
+ n=self.config.nk_params.n,
175
+ hamming_distance=self.config.hamming_distance,
176
+ )
177
+ elif self.config.agent_type == AgentType.AI_HILL_CLIMBER:
178
+ self.agent.hill_climb(
179
+ n=self.config.nk_params.n,
180
+ k=self.config.nk_params.k,
181
+ power_scale=self.config.nk_params.power,
182
+ hamming_distance=self.config.hamming_distance,
183
+ sample_idxs=list(self.visited_idxs),
184
+ payoffs=self.ordered_payoffs,
185
+ )
186
+ # Update visited indices
187
+ self.visited_idxs.update(self.agent.trajectory)
188
+ logger.debug(f"visited_idxs: {self.visited_idxs}")
189
+
190
+ return self.agent.trajectory
191
+
192
+ def _break_hill_climber(self, trajectory: List[int]) -> bool:
193
+ if self.config.stop_condition == StopCondition.MAX_ITERATIONS:
194
+ # Break if the number of iterations where reached
195
+ return (
196
+ len(trajectory)
197
+ >= self.config.stop_condition_kwargs["max_iterations"]
198
+ )
199
+ elif self.config.stop_condition == StopCondition.REACH_PEAK:
200
+ # Break if the agent is stuck on a local maximum
201
+ stuck_for_until_break = self.config.stop_condition_kwargs[
202
+ "stuck_for_until_break"
203
+ ]
204
+ if len(trajectory) < stuck_for_until_break * 2:
205
+ return False
206
+ if set(trajectory[:-stuck_for_until_break]) >= set(
207
+ trajectory[-stuck_for_until_break:]
208
+ ):
209
+ return True
210
+ else:
211
+ return False
@@ -0,0 +1,132 @@
1
+ import numpy as np
2
+ from tqdm.auto import tqdm
3
+
4
+ from src.nk_model.models import NKParams
5
+ from src.nk_model.nk_landscape import NKLandscape
6
+ from src.simulation.hill_climber_simulation import (
7
+ AgentType,
8
+ HillClimberSimConfig,
9
+ HillClimberSimulation,
10
+ StopCondition,
11
+ )
12
+
13
+
14
+ def hill_climber_vs_ai_simulation(
15
+ config: HillClimberSimConfig,
16
+ landscape: NKLandscape,
17
+ ):
18
+ if config.agent_start_idx is None:
19
+ config.agent_start_idx = np.random.choice(config.start_idxs)
20
+
21
+ if config.agent_type == AgentType.HILL_CLIMBER:
22
+ hill_climber_config = config
23
+ ai_config = config.model_copy(
24
+ update={"agent_type": AgentType.AI_HILL_CLIMBER}
25
+ )
26
+ elif config.agent_type == AgentType.AI_HILL_CLIMBER:
27
+ ai_config = config
28
+ hill_climber_config = config.model_copy(
29
+ update={"agent_type": AgentType.HILL_CLIMBER}
30
+ )
31
+
32
+ hill_climber_simulation = HillClimberSimulation.from_cached_landscape(
33
+ config=hill_climber_config, landscape_uuid=landscape.uuid
34
+ )
35
+ ai_simulation = HillClimberSimulation.from_cached_landscape(
36
+ config=ai_config, landscape_uuid=landscape.uuid
37
+ )
38
+
39
+ hill_climber_trajectory = hill_climber_simulation.run()
40
+ ai_trajectory = ai_simulation.run()
41
+
42
+ return hill_climber_trajectory, ai_trajectory
43
+
44
+
45
+ def compare_hill_climber_vs_ai(
46
+ nk_params: NKParams,
47
+ hamming_distance: int,
48
+ max_iterations: int,
49
+ repetitions: int,
50
+ n_start_idxs: int = 1,
51
+ ):
52
+ hill_climber_payoff_trajectories = []
53
+ ai_payoff_trajectories = []
54
+ for _ in tqdm(range(repetitions), desc="Comparing hill climber vs AI"):
55
+ start_idxs = [
56
+ np.random.choice(list(range(2**nk_params.n)))
57
+ for _ in range(n_start_idxs)
58
+ ]
59
+ start_idx = np.random.choice(start_idxs)
60
+
61
+ config = HillClimberSimConfig(
62
+ nk_params=nk_params,
63
+ agent_type=AgentType.HILL_CLIMBER,
64
+ start_idxs=start_idxs,
65
+ agent_start_idx=start_idx,
66
+ use_trajectory=True,
67
+ stop_condition=StopCondition.MAX_ITERATIONS,
68
+ stop_condition_kwargs={"max_iterations": max_iterations},
69
+ hamming_distance=hamming_distance,
70
+ )
71
+ landscape = NKLandscape(params=nk_params)
72
+ payoff_list = landscape.get_ordered_payoffs()
73
+
74
+ hill_climber_trajectory, ai_trajectory = hill_climber_vs_ai_simulation(
75
+ config, landscape
76
+ )
77
+
78
+ hill_climber_payoff_traj = [
79
+ payoff_list[idx] for idx in hill_climber_trajectory
80
+ ]
81
+ ai_payoff_traj = [payoff_list[idx] for idx in ai_trajectory]
82
+
83
+ hill_climber_payoff_trajectories.append(hill_climber_payoff_traj)
84
+ ai_payoff_trajectories.append(ai_payoff_traj)
85
+
86
+ return hill_climber_payoff_trajectories, ai_payoff_trajectories
87
+
88
+
89
+ def average_payoff_trajectories(
90
+ payoff_trajectories: list[list[float]],
91
+ ) -> list[float]:
92
+ """Transform list of payoff trajectories into list of averaged payoffs.
93
+
94
+ Args:
95
+ payoff_trajectories: List of payoff trajectories, where each
96
+ trajectory is a list of payoffs.
97
+
98
+ Returns:
99
+ List of averaged payoffs at each step position.
100
+ """
101
+ if not payoff_trajectories:
102
+ return []
103
+
104
+ max_len = max(len(traj) for traj in payoff_trajectories)
105
+ averaged = []
106
+
107
+ for step_idx in range(max_len):
108
+ step_payoffs = [
109
+ traj[step_idx]
110
+ for traj in payoff_trajectories
111
+ if step_idx < len(traj)
112
+ ]
113
+ averaged.append(np.mean(step_payoffs))
114
+
115
+ return averaged
116
+
117
+
118
+ def average_last_payoff(payoff_trajectories: list[list[float]]) -> float:
119
+ """Return the average of the last payoff in each trajectory.
120
+
121
+ Args:
122
+ payoff_trajectories: List of payoff trajectories, where each
123
+ trajectory is a list of payoffs.
124
+
125
+ Returns:
126
+ Average of the last payoff across all trajectories.
127
+ """
128
+ if not payoff_trajectories:
129
+ return 0.0
130
+
131
+ last_payoffs = [traj[-1] for traj in payoff_trajectories if traj]
132
+ return np.mean(last_payoffs)
@@ -0,0 +1,179 @@
1
+ """Utilities for finding and selecting average performing landscapes."""
2
+
3
+ import json
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from typing import List, Tuple
7
+
8
+ import numpy as np
9
+ from tqdm import tqdm
10
+
11
+ from src.nk_model.models import NKParams
12
+ from src.nk_model.nk_landscape import NKLandscape
13
+ from src.simulation.hill_climber_simulation import (
14
+ AgentType,
15
+ HillClimberSimConfig,
16
+ HillClimberSimulation,
17
+ StopCondition,
18
+ )
19
+
20
+
21
+ def run_simulation_with_landscape(
22
+ landscape_uuid: str,
23
+ nk_params: NKParams,
24
+ hamming_distance: int,
25
+ max_iterations: int,
26
+ ) -> float:
27
+ """Run simulation on a specific landscape and return final payoff.
28
+
29
+ Args:
30
+ landscape_uuid: UUID of the cached landscape
31
+ nk_params: NK model parameters
32
+ hamming_distance: Hamming distance for neighbor selection
33
+ max_iterations: Maximum number of iterations
34
+ start_idxs: List of starting indices
35
+
36
+ Returns:
37
+ Final payoff from the simulation
38
+ """
39
+
40
+ start_idxs = np.random.choice(list(range(2**nk_params.n)), 1)
41
+
42
+ config = HillClimberSimConfig(
43
+ nk_params=nk_params,
44
+ agent_type=AgentType.HILL_CLIMBER,
45
+ start_idxs=start_idxs,
46
+ stop_condition=StopCondition.MAX_ITERATIONS,
47
+ stop_condition_kwargs={"max_iterations": max_iterations},
48
+ hamming_distance=hamming_distance,
49
+ )
50
+
51
+ simulation = HillClimberSimulation.from_cached_landscape(
52
+ config=config, landscape_uuid=landscape_uuid
53
+ )
54
+ trajectory = simulation.run()
55
+
56
+ final_idx = trajectory[-1]
57
+ final_payoff = simulation.ordered_payoffs[final_idx]
58
+
59
+ return final_payoff
60
+
61
+
62
+ def select_average_landscape(
63
+ results: List[Tuple[str, float]],
64
+ percentile_low: float = 25.0,
65
+ percentile_high: float = 75.0,
66
+ ) -> str:
67
+ """Select an average landscape from tested results.
68
+
69
+ Args:
70
+ results: List of tuples (landscape_uuid, average_payoff)
71
+ percentile_low: Lower percentile threshold (default: 25.0)
72
+ percentile_high: Upper percentile threshold (default: 75.0)
73
+
74
+ Returns:
75
+ UUID of the selected landscape
76
+ """
77
+ # Sort by average payoff
78
+ results.sort(key=lambda x: x[1])
79
+
80
+ # Select from percentile range
81
+ start_idx = int((percentile_low / 100.0) * len(results))
82
+ end_idx = int((percentile_high / 100.0) * len(results))
83
+ candidates = results[start_idx:end_idx]
84
+
85
+ # Randomly select one landscape
86
+ selected_idx = np.random.choice(range(len(candidates)))
87
+ return candidates[selected_idx][0]
88
+
89
+
90
+ def find_average_landscape_for_k(
91
+ nk_params: NKParams,
92
+ num_landscapes: int = 100,
93
+ repetitions: int = 100,
94
+ hamming_distance: int = 1,
95
+ max_iterations: int = 10,
96
+ ) -> str:
97
+ """Find an average landscape for a specific k value.
98
+
99
+ Args:
100
+ nk_params: NK model parameters
101
+ num_landscapes: Number of landscapes to generate and test
102
+ repetitions: Number of repetitions per landscape
103
+ hamming_distance: Hamming distance for neighbor selection
104
+ max_iterations: Maximum number of iterations
105
+
106
+ Returns:
107
+ UUID of the selected average landscape
108
+ """
109
+
110
+ # Generate landscapes
111
+ landscape_uuids = [
112
+ NKLandscape(params=nk_params).uuid for _ in range(num_landscapes)
113
+ ]
114
+
115
+ # Test landscapes and collect results
116
+ results = []
117
+ for uuid in tqdm(landscape_uuids, desc="Testing landscapes"):
118
+ final_payoffs = []
119
+ for _ in range(repetitions):
120
+ payoff = run_simulation_with_landscape(
121
+ landscape_uuid=uuid,
122
+ nk_params=nk_params,
123
+ hamming_distance=hamming_distance,
124
+ max_iterations=max_iterations,
125
+ )
126
+ final_payoffs.append(payoff)
127
+ avg_payoff = np.mean(final_payoffs)
128
+ results.append((uuid, avg_payoff))
129
+
130
+ # Select average landscape
131
+ selected_uuid = select_average_landscape(results)
132
+
133
+ return selected_uuid
134
+
135
+
136
+ def save_landscape_json(
137
+ landscape_uuid: str,
138
+ output_path: Path,
139
+ n: int,
140
+ k: int,
141
+ add_timestamp: bool = True,
142
+ ) -> Path:
143
+ """Save a landscape to disk as JSON.
144
+
145
+ Args:
146
+ landscape_uuid: UUID of the landscape to save
147
+ output_path: Path where to save the JSON file
148
+ n: Number of components
149
+ k: Number of interactions
150
+ add_timestamp: Whether to add timestamp to filename
151
+
152
+ Returns:
153
+ Path to the saved file (with timestamp if added)
154
+ """
155
+ landscape = NKLandscape.from_cache(landscape_uuid)
156
+ json_data = []
157
+ nk_str = f"({n}, {k})"
158
+
159
+ for item in landscape.items:
160
+ location = "".join([str(int(coord)) for coord in item.coordinates])
161
+ fitness = int(item.payoff)
162
+
163
+ json_data.append(
164
+ {"N_NK": nk_str, "location": location, "fitness": fitness}
165
+ )
166
+
167
+ # Add timestamp to filename if requested
168
+ if add_timestamp:
169
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
170
+ stem = output_path.stem
171
+ suffix = output_path.suffix
172
+ output_path = output_path.parent / f"{stem}_{timestamp}{suffix}"
173
+
174
+ output_path.parent.mkdir(parents=True, exist_ok=True)
175
+ print(f"Saving landscape to {output_path}")
176
+ with open(output_path, "w") as f:
177
+ json.dump(json_data, f, indent=4)
178
+
179
+ return output_path
utils/__init__.py ADDED
File without changes
@@ -0,0 +1,128 @@
1
+ """Binary conversion utilities for transforming between int, array, and string.
2
+
3
+ This module provides functions to convert between different representations
4
+ of binary states:
5
+ - Integer (0-255): Represents an 8-bit binary number
6
+ - Binary array: NumPy array of shape (8,) with values 0 or 1
7
+ - Binary string: String representation like "0,0,0,0,0,0,0,0"
8
+ """
9
+
10
+ import numpy as np
11
+
12
+ from src.utils.utils import BIN_ARRAY
13
+
14
+
15
+ def int_to_binary_str(value: int, separator: str = ",") -> str:
16
+ """Convert integer to binary string representation.
17
+
18
+ Args:
19
+ value: Integer value (0-255) to convert
20
+ separator: String separator for binary digits. Defaults to ",".
21
+
22
+ Returns:
23
+ Binary string representation, e.g., "0,0,0,0,0,0,0,0"
24
+
25
+ Examples:
26
+ >>> int_to_binary_str(0)
27
+ '0,0,0,0,0,0,0,0'
28
+ >>> int_to_binary_str(255)
29
+ '1,1,1,1,1,1,1,1'
30
+ >>> int_to_binary_str(15)
31
+ '0,0,0,0,1,1,1,1'
32
+ """
33
+ return separator.join([str(v) for v in BIN_ARRAY[value]])
34
+
35
+
36
+ def binary_str_to_int(binary_str: str, separator: str = ",") -> int:
37
+ """Convert binary string to integer.
38
+
39
+ Args:
40
+ binary_str: Binary string representation, e.g., "0,0,0,0,0,0,0,0"
41
+ separator: String separator used in binary_str. Defaults to ",".
42
+
43
+ Returns:
44
+ Integer value (0-255)
45
+
46
+ Examples:
47
+ >>> binary_str_to_int("0,0,0,0,0,0,0,0")
48
+ 0
49
+ >>> binary_str_to_int("1,1,1,1,1,1,1,1")
50
+ 255
51
+ >>> binary_str_to_int("0,0,0,0,1,1,1,1")
52
+ 15
53
+ """
54
+ return int(binary_str.replace(separator, ""), 2)
55
+
56
+
57
+ def int_to_binary_array(value: int) -> np.ndarray:
58
+ """Convert integer to binary array.
59
+
60
+ Args:
61
+ value: Integer value (0-255) to convert
62
+
63
+ Returns:
64
+ Binary array of shape (8,) with values 0 or 1
65
+
66
+ Examples:
67
+ >>> int_to_binary_array(0)
68
+ array([0, 0, 0, 0, 0, 0, 0, 0])
69
+ >>> int_to_binary_array(255)
70
+ array([1, 1, 1, 1, 1, 1, 1, 1])
71
+ """
72
+ return BIN_ARRAY[value].copy()
73
+
74
+
75
+ def binary_array_to_int(binary_array: np.ndarray) -> int:
76
+ """Convert binary array to integer.
77
+
78
+ Args:
79
+ binary_array: Binary array of shape (8,) with values 0 or 1
80
+
81
+ Returns:
82
+ Integer value (0-255)
83
+
84
+ Examples:
85
+ >>> binary_array_to_int(np.array([0, 0, 0, 0, 0, 0, 0, 0]))
86
+ 0
87
+ >>> binary_array_to_int(np.array([1, 1, 1, 1, 1, 1, 1, 1]))
88
+ 255
89
+ """
90
+ return int("".join([str(int(v)) for v in binary_array]), 2)
91
+
92
+
93
+ def binary_array_to_str(binary_array: np.ndarray, separator: str = ",") -> str:
94
+ """Convert binary array to string representation.
95
+
96
+ Args:
97
+ binary_array: Binary array of shape (n,) with values 0 or 1
98
+ separator: String separator for binary digits. Defaults to ",".
99
+
100
+ Returns:
101
+ Binary string representation, e.g., "1,0,1,0,1"
102
+
103
+ Examples:
104
+ >>> binary_array_to_str(np.array([1, 0, 1, 0, 1]))
105
+ '1,0,1,0,1'
106
+ >>> binary_array_to_str(np.array([1, 0, 1]), separator=" ")
107
+ '1 0 1'
108
+ """
109
+ return separator.join([str(int(item)) for item in binary_array])
110
+
111
+
112
+ def binary_str_to_array(binary_str: str, separator: str = ",") -> np.ndarray:
113
+ """Convert binary string to array.
114
+
115
+ Args:
116
+ binary_str: Binary string representation, e.g., "0,0,0,0,0,0,0,0"
117
+ separator: String separator used in binary_str. Defaults to ",".
118
+
119
+ Returns:
120
+ Binary array of shape (n,) with values 0 or 1
121
+
122
+ Examples:
123
+ >>> binary_str_to_array("0,0,0,0,0,0,0,0")
124
+ array([0, 0, 0, 0, 0, 0, 0, 0])
125
+ >>> binary_str_to_array("1,1,1,1,1,1,1,1")
126
+ array([1, 1, 1, 1, 1, 1, 1, 1])
127
+ """
128
+ return np.array([int(x) for x in binary_str.split(separator)])
utils/logging.py ADDED
@@ -0,0 +1,33 @@
1
+ import functools
2
+ import logging
3
+ from time import time
4
+
5
+ # logging settings
6
+ logging.basicConfig(
7
+ format=("%(asctime)s %(levelname)-8s %(message)s"),
8
+ # log.INFO for normal run
9
+ level=logging.INFO,
10
+ # log.DEBUG for diagnostics
11
+ # level=logging.DEBUG,
12
+ datefmt="%Y-%m-%d %H:%M:%S",
13
+ )
14
+
15
+
16
+ def log_dec(func):
17
+ @functools.wraps(func)
18
+ def wrapper(*args, **kwargs):
19
+ logger = logging.getLogger(func.__module__)
20
+ try:
21
+ start_time = time()
22
+ logger.info("%-30s started", func.__name__)
23
+ return func(*args, **kwargs)
24
+ except Exception as ex:
25
+ logger.error(f"%-30s raised an exception: {ex}", func.__name__)
26
+ raise ex
27
+ finally:
28
+ duration = time() - start_time
29
+ logger.info(
30
+ "%-30s finished in %s seconds", func.__name__, duration
31
+ )
32
+
33
+ return wrapper