nimo 2.1.2__tar.gz → 2.1.3__tar.gz
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.
- {nimo-2.1.2 → nimo-2.1.3}/PKG-INFO +3 -1
- {nimo-2.1.2 → nimo-2.1.3}/README.md +1 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/__init__.py +1 -0
- nimo-2.1.3/nimo/ai_tools/ai_tool_llmep.py +346 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/nimo_modules.py +28 -1
- {nimo-2.1.2 → nimo-2.1.3}/nimo.egg-info/PKG-INFO +3 -1
- {nimo-2.1.2 → nimo-2.1.3}/nimo.egg-info/SOURCES.txt +1 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo.egg-info/requires.txt +1 -0
- {nimo-2.1.2 → nimo-2.1.3}/pyproject.toml +3 -2
- {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_blox.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_bomp.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_combi.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_doe.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_es.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_nts.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_pdc.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_physbo.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_ptr.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_re.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_rsvm.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_slesa.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_slesa_WAM.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/input_tools/preparation_input_certus.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/input_tools/preparation_input_certus_sc.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/input_tools/preparation_input_combat.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/input_tools/preparation_input_naree.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/input_tools/preparation_input_standard.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/output_tools/analysis_output_combat.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/output_tools/analysis_output_insert.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/output_tools/analysis_output_naree.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/output_tools/analysis_output_sd8.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/output_tools/analysis_output_standard.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/output_tools/analysis_output_update.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/visualization/plot_distribution.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/visualization/plot_history.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo/visualization/plot_phase_diagram.py +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo.egg-info/dependency_links.txt +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/nimo.egg-info/top_level.txt +0 -0
- {nimo-2.1.2 → nimo-2.1.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nimo
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.3
|
|
4
4
|
Summary: NIMO package
|
|
5
5
|
Author: NIMO developers
|
|
6
6
|
License-Expression: MIT
|
|
@@ -11,6 +11,7 @@ Requires-Dist: physbo>=3.1.0
|
|
|
11
11
|
Requires-Dist: scikit-learn
|
|
12
12
|
Requires-Dist: scipy
|
|
13
13
|
Requires-Dist: pyDOE3
|
|
14
|
+
Requires-Dist: anthropic
|
|
14
15
|
|
|
15
16
|
# nimo
|
|
16
17
|
|
|
@@ -33,6 +34,7 @@ NIMO is a Python library to realize a closed loop of robotic experiments and art
|
|
|
33
34
|
- scikit-learn
|
|
34
35
|
- scipy
|
|
35
36
|
- pyDOE3
|
|
37
|
+
- anthropic
|
|
36
38
|
|
|
37
39
|
# Install
|
|
38
40
|
|
|
@@ -23,6 +23,7 @@ from .ai_tools import ai_tool_es
|
|
|
23
23
|
from .ai_tools import ai_tool_combi
|
|
24
24
|
from .ai_tools import ai_tool_nts
|
|
25
25
|
from .ai_tools import ai_tool_doe
|
|
26
|
+
from .ai_tools import ai_tool_llmep
|
|
26
27
|
|
|
27
28
|
from .input_tools import preparation_input_standard
|
|
28
29
|
from .input_tools import preparation_input_naree
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import csv
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
import anthropic
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LLMEP():
|
|
9
|
+
"""Class of LLMEP
|
|
10
|
+
|
|
11
|
+
This class selects the next experimental candidates using a Large Language Model (LLM).
|
|
12
|
+
It is a standalone class with no dependency on ai_tool_re.
|
|
13
|
+
|
|
14
|
+
The user provides a Markdown prompt file that describes the experimental context.
|
|
15
|
+
The candidates CSV and any observed data are injected automatically into the prompt.
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, input_file, output_file, num_objectives, num_proposals,
|
|
20
|
+
prompt_file, system_prompt_file, llm_model,
|
|
21
|
+
num_runs, log_file, api_key, max_tokens):
|
|
22
|
+
"""Constructor
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
input_file (str): the file for candidates (CSV with descriptor columns + objective columns)
|
|
26
|
+
output_file (str): the file for proposals output
|
|
27
|
+
num_objectives (int): the number of objective columns (rightmost columns in CSV)
|
|
28
|
+
num_proposals (int): the number of proposals to select
|
|
29
|
+
prompt_file (str): path to the Markdown file describing the experimental context
|
|
30
|
+
system_prompt_file (str): path to a file containing the system prompt.
|
|
31
|
+
llm_model (str): Anthropic model ID to use
|
|
32
|
+
num_runs (int): number of independent LLM selection runs (for majority vote)
|
|
33
|
+
log_file (str): if provided, save the full selection log as Markdown
|
|
34
|
+
api_key (str): Anthropic API key. If None, reads from ANTHROPIC_API_KEY env variable.
|
|
35
|
+
max_tokens (int): maximum number of tokens in the LLM response (default: 8192).
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
self.input_file = input_file
|
|
39
|
+
self.output_file = output_file
|
|
40
|
+
self.num_objectives = num_objectives
|
|
41
|
+
self.num_proposals = num_proposals
|
|
42
|
+
self.prompt_file = prompt_file
|
|
43
|
+
self.system_prompt_file = system_prompt_file
|
|
44
|
+
self.model = llm_model
|
|
45
|
+
self.num_runs = num_runs
|
|
46
|
+
if num_runs == None: self.num_runs = 10
|
|
47
|
+
self.log_file = log_file
|
|
48
|
+
if log_file == None: self.log_file = "log"
|
|
49
|
+
self.client = anthropic.Anthropic(api_key = api_key)
|
|
50
|
+
self.max_tokens = max_tokens
|
|
51
|
+
if max_tokens == None: self.max_tokens = 8192
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ------------------------------------------------------------------
|
|
55
|
+
# Data loading (ported from RE.load_data)
|
|
56
|
+
# ------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
def load_data(self):
|
|
59
|
+
"""Load candidates CSV and split into observed / unobserved rows.
|
|
60
|
+
|
|
61
|
+
t_train is returned as a list of raw strings for compatibility with
|
|
62
|
+
calc_ai's signature, but LLMEP does not use it internally.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
t_train (list[list[str]]): observed objective values (raw strings)
|
|
66
|
+
X_all (list[list[str]]): all descriptor rows (raw strings; numeric, categorical, and text all accepted)
|
|
67
|
+
train_actions (np.ndarray): indices of observed rows
|
|
68
|
+
test_actions (list[int]): indices of unobserved rows
|
|
69
|
+
"""
|
|
70
|
+
with open(self.input_file, 'r') as f:
|
|
71
|
+
reader = csv.reader(f)
|
|
72
|
+
_headers = next(reader)
|
|
73
|
+
raw_rows = list(reader)
|
|
74
|
+
|
|
75
|
+
X_all = []
|
|
76
|
+
t_train = []
|
|
77
|
+
train_actions = []
|
|
78
|
+
test_actions = []
|
|
79
|
+
|
|
80
|
+
for idx, row in enumerate(raw_rows):
|
|
81
|
+
descriptors = row[:-self.num_objectives] # kept as strings (numeric, categorical, text all accepted)
|
|
82
|
+
objectives = row[-self.num_objectives:]
|
|
83
|
+
X_all.append(descriptors)
|
|
84
|
+
# A row is "observed" if all objective cells are non-empty
|
|
85
|
+
if all(v.strip() != '' for v in objectives):
|
|
86
|
+
t_train.append(objectives)
|
|
87
|
+
train_actions.append(idx)
|
|
88
|
+
else:
|
|
89
|
+
test_actions.append(idx)
|
|
90
|
+
|
|
91
|
+
train_actions = np.array(train_actions)
|
|
92
|
+
|
|
93
|
+
return t_train, X_all, train_actions, test_actions
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ------------------------------------------------------------------
|
|
97
|
+
# Internal helpers
|
|
98
|
+
# ------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
def _load_prompt(self):
|
|
101
|
+
"""Load the user-supplied Markdown prompt file."""
|
|
102
|
+
with open(self.prompt_file, 'r', encoding='utf-8', errors='replace') as f:
|
|
103
|
+
return f.read()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _build_candidates_text(self, X_all, train_actions, test_actions):
|
|
107
|
+
"""Build a text table of all candidates for injection into the prompt.
|
|
108
|
+
|
|
109
|
+
Descriptor values are kept as raw strings, supporting numeric, categorical, and free-text descriptors.
|
|
110
|
+
Observed rows include their measured objective value; unobserved rows show 'unknown'.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
str: CSV-like text block
|
|
114
|
+
"""
|
|
115
|
+
with open(self.input_file, 'r') as f:
|
|
116
|
+
reader = csv.reader(f)
|
|
117
|
+
headers = next(reader)
|
|
118
|
+
raw_rows = list(reader)
|
|
119
|
+
|
|
120
|
+
descriptor_headers = headers[:-self.num_objectives]
|
|
121
|
+
objective_headers = headers[-self.num_objectives:]
|
|
122
|
+
|
|
123
|
+
lines = ["index," + ",".join(descriptor_headers) + "," + ",".join(objective_headers)]
|
|
124
|
+
for idx, row in enumerate(raw_rows):
|
|
125
|
+
descriptors = row[:-self.num_objectives]
|
|
126
|
+
objectives_str = [v if v.strip() != '' else 'unknown' for v in row[-self.num_objectives:]]
|
|
127
|
+
lines.append(str(idx) + "," + ",".join(descriptors) + "," + ",".join(objectives_str))
|
|
128
|
+
|
|
129
|
+
return "\n".join(lines), objective_headers
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _build_messages(self, user_prompt_md, candidates_text, objective_headers):
|
|
133
|
+
"""Construct the system prompt and user message for the single LLM call."""
|
|
134
|
+
with open(self.system_prompt_file, 'r', encoding='utf-8') as f:
|
|
135
|
+
system = f.read()
|
|
136
|
+
|
|
137
|
+
obj_cols = ", ".join(f"`{h}`" for h in objective_headers)
|
|
138
|
+
is_are = 'is' if len(objective_headers) == 1 else 'are'
|
|
139
|
+
|
|
140
|
+
user = (
|
|
141
|
+
f"{user_prompt_md}\n"
|
|
142
|
+
"\n---\n"
|
|
143
|
+
"\n## Candidates\n"
|
|
144
|
+
"\nThe following table lists all candidate experimental conditions.\n"
|
|
145
|
+
f"- Rows with a value in {obj_cols} are already observed data points.\n"
|
|
146
|
+
f"- Rows where {obj_cols} {is_are} 'unknown' have not yet been measured.\n"
|
|
147
|
+
"\n```\n"
|
|
148
|
+
f"{candidates_text}\n"
|
|
149
|
+
"```\n"
|
|
150
|
+
"\n---\n"
|
|
151
|
+
"\n## Your Task\n"
|
|
152
|
+
f"\nPerform {self.num_runs} independent selection runs, then aggregate by majority vote.\n"
|
|
153
|
+
"\n### Step 1 Independent Runs\n"
|
|
154
|
+
f"\nFor each run (Run 1 through Run {self.num_runs}):\n"
|
|
155
|
+
"- Choose a **distinct exploration strategy**.\n"
|
|
156
|
+
f"- Select exactly {self.num_proposals} indices from the 'index' column above.\n"
|
|
157
|
+
f"- Only select rows where {obj_cols} {is_are} 'unknown'.\n"
|
|
158
|
+
"- Document the strategy and the selected indices.\n"
|
|
159
|
+
|
|
160
|
+
"\n### Step 2 Majority Vote\n"
|
|
161
|
+
f"\nAfter all {self.num_runs} runs:\n"
|
|
162
|
+
"- Count how many times each index was selected across all runs.\n"
|
|
163
|
+
"- Rank indices by vote count (descending). Break ties by preferring lower index values.\n"
|
|
164
|
+
f"- Select the top {self.num_proposals} indices as the final proposals.\n"
|
|
165
|
+
"- Document the vote counts for all indices that received at least one vote.\n"
|
|
166
|
+
"\n### Output Format\n"
|
|
167
|
+
"\nAfter your reasoning, output **exactly one** JSON object in the following format.\n"
|
|
168
|
+
"This must be the last thing in your response.\n"
|
|
169
|
+
"\n```json\n"
|
|
170
|
+
"{\n"
|
|
171
|
+
' "runs": [\n'
|
|
172
|
+
' {\n'
|
|
173
|
+
' "run": 1,\n'
|
|
174
|
+
' "strategy": "<one-line description>",\n'
|
|
175
|
+
' "selected_indices": [<index1>, <index2>, ...]\n'
|
|
176
|
+
' },\n'
|
|
177
|
+
' ...\n'
|
|
178
|
+
' ],\n'
|
|
179
|
+
' "vote_counts": {\n'
|
|
180
|
+
' "<index>": <count>,\n'
|
|
181
|
+
' ...\n'
|
|
182
|
+
' },\n'
|
|
183
|
+
' "final_selected_indices": [<index1>, <index2>, ...],\n'
|
|
184
|
+
' "reasons": {\n'
|
|
185
|
+
' "<index>": "<one-sentence reason why this composition was selected>",\n'
|
|
186
|
+
' ...\n'
|
|
187
|
+
' }\n'
|
|
188
|
+
"}\n"
|
|
189
|
+
"```\n"
|
|
190
|
+
)
|
|
191
|
+
return [{"role": "user", "content": user}], system
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _parse_result(self, response_text):
|
|
195
|
+
"""Extract the result JSON from the LLM response.
|
|
196
|
+
|
|
197
|
+
Raises:
|
|
198
|
+
ValueError: if parsing fails
|
|
199
|
+
"""
|
|
200
|
+
# Try last ```json ... ``` block first
|
|
201
|
+
json_blocks = re.findall(r'```json\s*(.*?)\s*```', response_text, re.DOTALL)
|
|
202
|
+
if json_blocks:
|
|
203
|
+
try:
|
|
204
|
+
return json.loads(json_blocks[-1])
|
|
205
|
+
except json.JSONDecodeError:
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
# Fallback: find the last {...} containing final_selected_indices
|
|
209
|
+
for m in reversed(list(re.finditer(r'\{', response_text))):
|
|
210
|
+
candidate = response_text[m.start():]
|
|
211
|
+
depth, end = 0, -1
|
|
212
|
+
for i, ch in enumerate(candidate):
|
|
213
|
+
if ch == '{':
|
|
214
|
+
depth += 1
|
|
215
|
+
elif ch == '}':
|
|
216
|
+
depth -= 1
|
|
217
|
+
if depth == 0:
|
|
218
|
+
end = i
|
|
219
|
+
break
|
|
220
|
+
if end != -1:
|
|
221
|
+
try:
|
|
222
|
+
data = json.loads(candidate[:end + 1])
|
|
223
|
+
if "final_selected_indices" in data:
|
|
224
|
+
return data
|
|
225
|
+
except json.JSONDecodeError:
|
|
226
|
+
continue
|
|
227
|
+
|
|
228
|
+
raise ValueError(
|
|
229
|
+
f"Could not parse result JSON from LLM response:\n{response_text[:500]}"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# ------------------------------------------------------------------
|
|
234
|
+
# Core selection
|
|
235
|
+
# ------------------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
def calc_ai(self, t_train, X_all, train_actions, test_actions):
|
|
238
|
+
"""Select proposals by delegating all runs + majority vote to the LLM.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
t_train (np.ndarray): observed objective values
|
|
242
|
+
X_all (np.ndarray): all descriptor rows
|
|
243
|
+
train_actions (np.ndarray): indices of observed rows
|
|
244
|
+
test_actions (list[int]): indices of unobserved rows
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
actions (list[int]): selected action indices (length = num_proposals)
|
|
248
|
+
"""
|
|
249
|
+
user_prompt_md = self._load_prompt()
|
|
250
|
+
candidates_text, objective_headers = self._build_candidates_text(X_all, train_actions, test_actions)
|
|
251
|
+
|
|
252
|
+
print(f" [LLMEP] Sending request ({self.num_runs} runs + majority vote) ...")
|
|
253
|
+
|
|
254
|
+
messages, system = self._build_messages(user_prompt_md, candidates_text, objective_headers)
|
|
255
|
+
|
|
256
|
+
response = self.client.messages.create(
|
|
257
|
+
model=self.model,
|
|
258
|
+
max_tokens=self.max_tokens,
|
|
259
|
+
system=system,
|
|
260
|
+
messages=messages,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
response_text = response.content[0].text
|
|
264
|
+
result = self._parse_result(response_text)
|
|
265
|
+
|
|
266
|
+
# Validate: keep only indices that are actually unobserved
|
|
267
|
+
test_actions_set = set(test_actions)
|
|
268
|
+
actions = [int(i) for i in result["final_selected_indices"] if int(i) in test_actions_set]
|
|
269
|
+
reasons = result.get("reasons", {})
|
|
270
|
+
|
|
271
|
+
if len(actions) < self.num_proposals:
|
|
272
|
+
print(f" [LLMEP] Warning: only {len(actions)} valid indices returned "
|
|
273
|
+
f"(expected {self.num_proposals}). Padding with remaining candidates.")
|
|
274
|
+
used = set(actions)
|
|
275
|
+
for a in test_actions:
|
|
276
|
+
if len(actions) >= self.num_proposals:
|
|
277
|
+
break
|
|
278
|
+
if a not in used:
|
|
279
|
+
actions.append(a)
|
|
280
|
+
|
|
281
|
+
actions = actions[:self.num_proposals]
|
|
282
|
+
|
|
283
|
+
if self.log_file:
|
|
284
|
+
log_lines = [
|
|
285
|
+
"# LLMEP Selection Log\n",
|
|
286
|
+
f"Model : {self.model}",
|
|
287
|
+
f"Runs : {self.num_runs}",
|
|
288
|
+
f"Proposals: {self.num_proposals}\n",
|
|
289
|
+
"---\n",
|
|
290
|
+
"## Full LLM Response\n",
|
|
291
|
+
response_text,
|
|
292
|
+
"\n---\n",
|
|
293
|
+
"## Final Selected Indices (Python-validated)\n",
|
|
294
|
+
]
|
|
295
|
+
for rank, action in enumerate(actions, 1):
|
|
296
|
+
reason = reasons.get(str(action), "")
|
|
297
|
+
log_lines.append(f" {rank}. index={action}, descriptors={X_all[action]}, reason={reason}")
|
|
298
|
+
with open(self.log_file, 'w', encoding='utf-8') as f:
|
|
299
|
+
f.write("\n".join(log_lines))
|
|
300
|
+
print(f" [LLMEP] Log saved to {self.log_file}")
|
|
301
|
+
|
|
302
|
+
print(f" [LLMEP] Final proposals: {actions}")
|
|
303
|
+
return actions, reasons
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# ------------------------------------------------------------------
|
|
307
|
+
# Entry point (ported from RE.select)
|
|
308
|
+
# ------------------------------------------------------------------
|
|
309
|
+
|
|
310
|
+
def select(self):
|
|
311
|
+
"""Run the full selection pipeline and write proposals CSV.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
str: "True" on success
|
|
315
|
+
"""
|
|
316
|
+
print("Start selection of proposals by LLMEP!")
|
|
317
|
+
|
|
318
|
+
t_train, X_all, train_actions, test_actions = self.load_data()
|
|
319
|
+
|
|
320
|
+
actions, reasons = self.calc_ai(
|
|
321
|
+
t_train=t_train, X_all=X_all,
|
|
322
|
+
train_actions=train_actions, test_actions=test_actions,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
print('Proposals')
|
|
326
|
+
|
|
327
|
+
with open(self.input_file, 'r') as f:
|
|
328
|
+
indexes = f.readlines()[0].rstrip('\n').split(',')
|
|
329
|
+
|
|
330
|
+
indexes = ["actions"] + indexes[:-self.num_objectives]
|
|
331
|
+
proposals_all = [indexes]
|
|
332
|
+
|
|
333
|
+
for i, action in enumerate(actions):
|
|
334
|
+
row = [str(action)] + [str(v) for v in X_all[action]]
|
|
335
|
+
proposals_all.append(row)
|
|
336
|
+
print("###")
|
|
337
|
+
print("number =", i + 1)
|
|
338
|
+
print("actions =", action)
|
|
339
|
+
print("proposal =", X_all[action])
|
|
340
|
+
print("###")
|
|
341
|
+
|
|
342
|
+
with open(self.output_file, 'w', newline="") as f:
|
|
343
|
+
csv.writer(f).writerows(proposals_all)
|
|
344
|
+
|
|
345
|
+
print("Finish selection of proposals!")
|
|
346
|
+
return "True"
|
|
@@ -21,7 +21,10 @@ class selection():
|
|
|
21
21
|
combi_ranges = None, spread_elements = None,
|
|
22
22
|
sample_mode = None,
|
|
23
23
|
mode = None, max_iter = None,
|
|
24
|
-
output_res = None, training_res = None
|
|
24
|
+
output_res = None, training_res = None,
|
|
25
|
+
prompt_file = None, system_prompt_file = None,
|
|
26
|
+
llm_model = None, num_runs = None,
|
|
27
|
+
log_file = None, api_key = None, max_tokens = None):
|
|
25
28
|
|
|
26
29
|
"""Constructor
|
|
27
30
|
|
|
@@ -49,6 +52,13 @@ class selection():
|
|
|
49
52
|
max_iter (int): max iteration for DOE
|
|
50
53
|
output_res (str): True or False to output res file
|
|
51
54
|
training_res (str): True or False to training res file
|
|
55
|
+
prompt_file (str): path to the Markdown file describing the experimental context
|
|
56
|
+
system_prompt_file (str): path to a file containing the system prompt.
|
|
57
|
+
llm_model (str): Anthropic model ID to use
|
|
58
|
+
num_runs (int): number of independent LLM selection runs (for majority vote)
|
|
59
|
+
log_file (str): if provided, save the full selection log as Markdown
|
|
60
|
+
api_key (str): Anthropic API key. If None, reads from ANTHROPIC_API_KEY env variable.
|
|
61
|
+
max_tokens (int): maximum number of tokens in the LLM response (default: 8192).
|
|
52
62
|
|
|
53
63
|
"""
|
|
54
64
|
|
|
@@ -80,6 +90,16 @@ class selection():
|
|
|
80
90
|
self.output_res = output_res
|
|
81
91
|
self.training_res = training_res
|
|
82
92
|
|
|
93
|
+
|
|
94
|
+
self.prompt_file = prompt_file
|
|
95
|
+
self.system_prompt_file = system_prompt_file
|
|
96
|
+
self.llm_model = llm_model
|
|
97
|
+
self.num_runs = num_runs
|
|
98
|
+
self.log_file = log_file
|
|
99
|
+
self.api_key = api_key
|
|
100
|
+
self.max_tokens = max_tokens
|
|
101
|
+
|
|
102
|
+
|
|
83
103
|
res = self.module_selection()
|
|
84
104
|
|
|
85
105
|
|
|
@@ -153,6 +173,13 @@ class selection():
|
|
|
153
173
|
res = nimo.ai_tools.ai_tool_doe.DOE(self.input_file, self.output_file,
|
|
154
174
|
self.num_objectives, self.num_proposals, self.mode, self.max_iter).select()
|
|
155
175
|
return res
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
if self.method == "LLMEP":
|
|
179
|
+
res = nimo.ai_tools.ai_tool_llmep.LLMEP(self.input_file, self.output_file,
|
|
180
|
+
self.num_objectives, self.num_proposals,
|
|
181
|
+
self.prompt_file, self.system_prompt_file, self.llm_model,
|
|
182
|
+
self.num_runs, self.log_file, self.api_key, self.max_tokens).select()
|
|
156
183
|
|
|
157
184
|
|
|
158
185
|
class preparation_input():
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nimo
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.3
|
|
4
4
|
Summary: NIMO package
|
|
5
5
|
Author: NIMO developers
|
|
6
6
|
License-Expression: MIT
|
|
@@ -11,6 +11,7 @@ Requires-Dist: physbo>=3.1.0
|
|
|
11
11
|
Requires-Dist: scikit-learn
|
|
12
12
|
Requires-Dist: scipy
|
|
13
13
|
Requires-Dist: pyDOE3
|
|
14
|
+
Requires-Dist: anthropic
|
|
14
15
|
|
|
15
16
|
# nimo
|
|
16
17
|
|
|
@@ -33,6 +34,7 @@ NIMO is a Python library to realize a closed loop of robotic experiments and art
|
|
|
33
34
|
- scikit-learn
|
|
34
35
|
- scipy
|
|
35
36
|
- pyDOE3
|
|
37
|
+
- anthropic
|
|
36
38
|
|
|
37
39
|
# Install
|
|
38
40
|
|
|
@@ -7,7 +7,7 @@ packages = ["nimo", "nimo.ai_tools", "nimo.input_tools", "nimo.output_tools", "n
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "nimo"
|
|
10
|
-
version = "2.1.
|
|
10
|
+
version = "2.1.3"
|
|
11
11
|
description = "NIMO package"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
authors = [
|
|
@@ -20,5 +20,6 @@ dependencies = [
|
|
|
20
20
|
"physbo>=3.1.0",
|
|
21
21
|
"scikit-learn",
|
|
22
22
|
"scipy",
|
|
23
|
-
"pyDOE3"
|
|
23
|
+
"pyDOE3",
|
|
24
|
+
"anthropic"
|
|
24
25
|
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|