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.
Files changed (39) hide show
  1. {nimo-2.1.2 → nimo-2.1.3}/PKG-INFO +3 -1
  2. {nimo-2.1.2 → nimo-2.1.3}/README.md +1 -0
  3. {nimo-2.1.2 → nimo-2.1.3}/nimo/__init__.py +1 -0
  4. nimo-2.1.3/nimo/ai_tools/ai_tool_llmep.py +346 -0
  5. {nimo-2.1.2 → nimo-2.1.3}/nimo/nimo_modules.py +28 -1
  6. {nimo-2.1.2 → nimo-2.1.3}/nimo.egg-info/PKG-INFO +3 -1
  7. {nimo-2.1.2 → nimo-2.1.3}/nimo.egg-info/SOURCES.txt +1 -0
  8. {nimo-2.1.2 → nimo-2.1.3}/nimo.egg-info/requires.txt +1 -0
  9. {nimo-2.1.2 → nimo-2.1.3}/pyproject.toml +3 -2
  10. {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_blox.py +0 -0
  11. {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_bomp.py +0 -0
  12. {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_combi.py +0 -0
  13. {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_doe.py +0 -0
  14. {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_es.py +0 -0
  15. {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_nts.py +0 -0
  16. {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_pdc.py +0 -0
  17. {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_physbo.py +0 -0
  18. {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_ptr.py +0 -0
  19. {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_re.py +0 -0
  20. {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_rsvm.py +0 -0
  21. {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_slesa.py +0 -0
  22. {nimo-2.1.2 → nimo-2.1.3}/nimo/ai_tools/ai_tool_slesa_WAM.py +0 -0
  23. {nimo-2.1.2 → nimo-2.1.3}/nimo/input_tools/preparation_input_certus.py +0 -0
  24. {nimo-2.1.2 → nimo-2.1.3}/nimo/input_tools/preparation_input_certus_sc.py +0 -0
  25. {nimo-2.1.2 → nimo-2.1.3}/nimo/input_tools/preparation_input_combat.py +0 -0
  26. {nimo-2.1.2 → nimo-2.1.3}/nimo/input_tools/preparation_input_naree.py +0 -0
  27. {nimo-2.1.2 → nimo-2.1.3}/nimo/input_tools/preparation_input_standard.py +0 -0
  28. {nimo-2.1.2 → nimo-2.1.3}/nimo/output_tools/analysis_output_combat.py +0 -0
  29. {nimo-2.1.2 → nimo-2.1.3}/nimo/output_tools/analysis_output_insert.py +0 -0
  30. {nimo-2.1.2 → nimo-2.1.3}/nimo/output_tools/analysis_output_naree.py +0 -0
  31. {nimo-2.1.2 → nimo-2.1.3}/nimo/output_tools/analysis_output_sd8.py +0 -0
  32. {nimo-2.1.2 → nimo-2.1.3}/nimo/output_tools/analysis_output_standard.py +0 -0
  33. {nimo-2.1.2 → nimo-2.1.3}/nimo/output_tools/analysis_output_update.py +0 -0
  34. {nimo-2.1.2 → nimo-2.1.3}/nimo/visualization/plot_distribution.py +0 -0
  35. {nimo-2.1.2 → nimo-2.1.3}/nimo/visualization/plot_history.py +0 -0
  36. {nimo-2.1.2 → nimo-2.1.3}/nimo/visualization/plot_phase_diagram.py +0 -0
  37. {nimo-2.1.2 → nimo-2.1.3}/nimo.egg-info/dependency_links.txt +0 -0
  38. {nimo-2.1.2 → nimo-2.1.3}/nimo.egg-info/top_level.txt +0 -0
  39. {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.2
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
 
@@ -19,6 +19,7 @@ NIMO is a Python library to realize a closed loop of robotic experiments and art
19
19
  - scikit-learn
20
20
  - scipy
21
21
  - pyDOE3
22
+ - anthropic
22
23
 
23
24
  # Install
24
25
 
@@ -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.2
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
 
@@ -12,6 +12,7 @@ nimo/ai_tools/ai_tool_bomp.py
12
12
  nimo/ai_tools/ai_tool_combi.py
13
13
  nimo/ai_tools/ai_tool_doe.py
14
14
  nimo/ai_tools/ai_tool_es.py
15
+ nimo/ai_tools/ai_tool_llmep.py
15
16
  nimo/ai_tools/ai_tool_nts.py
16
17
  nimo/ai_tools/ai_tool_pdc.py
17
18
  nimo/ai_tools/ai_tool_physbo.py
@@ -4,3 +4,4 @@ physbo>=3.1.0
4
4
  scikit-learn
5
5
  scipy
6
6
  pyDOE3
7
+ anthropic
@@ -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.2"
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