llm4ad 1.0__py2.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 (112) hide show
  1. llm4ad/__init__.py +6 -0
  2. llm4ad/base/__init__.py +9 -0
  3. llm4ad/base/code.py +270 -0
  4. llm4ad/base/evaluate.py +285 -0
  5. llm4ad/base/modify_code.py +402 -0
  6. llm4ad/base/sample.py +234 -0
  7. llm4ad/gui.py +141 -0
  8. llm4ad/method/__init__.py +57 -0
  9. llm4ad/method/eoh/__init__.py +2 -0
  10. llm4ad/method/eoh/eoh.py +305 -0
  11. llm4ad/method/eoh/population.py +83 -0
  12. llm4ad/method/eoh/profiler.py +161 -0
  13. llm4ad/method/eoh/prompt.py +120 -0
  14. llm4ad/method/eoh/resume.py +104 -0
  15. llm4ad/method/eoh/sampler.py +29 -0
  16. llm4ad/method/funsearch/__init__.py +3 -0
  17. llm4ad/method/funsearch/config.py +23 -0
  18. llm4ad/method/funsearch/funsearch.py +195 -0
  19. llm4ad/method/funsearch/profiler.py +135 -0
  20. llm4ad/method/funsearch/programs_database.py +305 -0
  21. llm4ad/method/funsearch/resume.py +111 -0
  22. llm4ad/method/hillclimb/__init__.py +2 -0
  23. llm4ad/method/hillclimb/hillclimb.py +189 -0
  24. llm4ad/method/hillclimb/profiler.py +8 -0
  25. llm4ad/method/hillclimb/resume.py +83 -0
  26. llm4ad/method/randsample/__init__.py +2 -0
  27. llm4ad/method/randsample/profiler.py +8 -0
  28. llm4ad/method/randsample/randsample.py +181 -0
  29. llm4ad/method/randsample/resume.py +73 -0
  30. llm4ad/task/__init__.py +29 -0
  31. llm4ad/task/machine_learning/__init__.py +0 -0
  32. llm4ad/task/machine_learning/acrobot/__init__.py +2 -0
  33. llm4ad/task/machine_learning/acrobot/evaluation.py +65 -0
  34. llm4ad/task/machine_learning/acrobot/template.py +35 -0
  35. llm4ad/task/machine_learning/acrobot/test.py +46 -0
  36. llm4ad/task/machine_learning/car_mountain/__init__.py +2 -0
  37. llm4ad/task/machine_learning/car_mountain/evaluation.py +57 -0
  38. llm4ad/task/machine_learning/car_mountain/q-learning.py +123 -0
  39. llm4ad/task/machine_learning/car_mountain/template.py +22 -0
  40. llm4ad/task/machine_learning/car_mountain/test.py +163 -0
  41. llm4ad/task/machine_learning/moon_lander/__init__.py +2 -0
  42. llm4ad/task/machine_learning/moon_lander/evaluation.py +84 -0
  43. llm4ad/task/machine_learning/moon_lander/template.py +32 -0
  44. llm4ad/task/machine_learning/moon_lander/test.py +51 -0
  45. llm4ad/task/optimization/__init__.py +30 -0
  46. llm4ad/task/optimization/admissible_set/__init__.py +2 -0
  47. llm4ad/task/optimization/admissible_set/evaluation.py +155 -0
  48. llm4ad/task/optimization/admissible_set/template.py +22 -0
  49. llm4ad/task/optimization/cvrp_construct/__init__.py +2 -0
  50. llm4ad/task/optimization/cvrp_construct/evaluation.py +152 -0
  51. llm4ad/task/optimization/cvrp_construct/get_instance.py +50 -0
  52. llm4ad/task/optimization/cvrp_construct/template.py +22 -0
  53. llm4ad/task/optimization/online_bin_packing/__init__.py +2 -0
  54. llm4ad/task/optimization/online_bin_packing/evaluation.py +91 -0
  55. llm4ad/task/optimization/online_bin_packing/generate_weibull_instances.py +36 -0
  56. llm4ad/task/optimization/online_bin_packing/template.py +15 -0
  57. llm4ad/task/optimization/ovrp_construct/__init__.py +2 -0
  58. llm4ad/task/optimization/ovrp_construct/evaluation.py +124 -0
  59. llm4ad/task/optimization/ovrp_construct/get_instance.py +50 -0
  60. llm4ad/task/optimization/ovrp_construct/template.py +22 -0
  61. llm4ad/task/optimization/tsp_construct/__init__.py +2 -0
  62. llm4ad/task/optimization/tsp_construct/evaluation.py +148 -0
  63. llm4ad/task/optimization/tsp_construct/get_instance.py +15 -0
  64. llm4ad/task/optimization/tsp_construct/template.py +24 -0
  65. llm4ad/task/optimization/vrptw_construct/__init__.py +2 -0
  66. llm4ad/task/optimization/vrptw_construct/evaluation.py +174 -0
  67. llm4ad/task/optimization/vrptw_construct/get_instance.py +65 -0
  68. llm4ad/task/optimization/vrptw_construct/template.py +23 -0
  69. llm4ad/task/science_discovery/__init__.py +0 -0
  70. llm4ad/task/science_discovery/bactgrow/__init__.py +2 -0
  71. llm4ad/task/science_discovery/bactgrow/evaluation.py +87 -0
  72. llm4ad/task/science_discovery/bactgrow/template.py +20 -0
  73. llm4ad/task/science_discovery/bactgrow/test_id.py +17502 -0
  74. llm4ad/task/science_discovery/bactgrow/test_odd.py +105002 -0
  75. llm4ad/task/science_discovery/bactgrow/train.py +52502 -0
  76. llm4ad/task/science_discovery/ode_1d/__init__.py +2 -0
  77. llm4ad/task/science_discovery/ode_1d/evaluation.py +152 -0
  78. llm4ad/task/science_discovery/ode_1d/strogatz_equations.py +224 -0
  79. llm4ad/task/science_discovery/ode_1d/strogatz_extended.py +34001 -0
  80. llm4ad/task/science_discovery/ode_1d/template.py +22 -0
  81. llm4ad/task/science_discovery/oscillator1/__init__.py +2 -0
  82. llm4ad/task/science_discovery/oscillator1/evaluation.py +85 -0
  83. llm4ad/task/science_discovery/oscillator1/template.py +19 -0
  84. llm4ad/task/science_discovery/oscillator1/test_id.py +50002 -0
  85. llm4ad/task/science_discovery/oscillator1/test_odd.py +50002 -0
  86. llm4ad/task/science_discovery/oscillator1/train.py +50002 -0
  87. llm4ad/task/science_discovery/oscillator2/__init__.py +2 -0
  88. llm4ad/task/science_discovery/oscillator2/evaluation.py +87 -0
  89. llm4ad/task/science_discovery/oscillator2/template.py +20 -0
  90. llm4ad/task/science_discovery/oscillator2/test_id.py +60002 -0
  91. llm4ad/task/science_discovery/oscillator2/test_odd.py +60002 -0
  92. llm4ad/task/science_discovery/oscillator2/train.py +60002 -0
  93. llm4ad/task/science_discovery/stresstrain/__init__.py +2 -0
  94. llm4ad/task/science_discovery/stresstrain/evaluation.py +85 -0
  95. llm4ad/task/science_discovery/stresstrain/template.py +19 -0
  96. llm4ad/task/science_discovery/stresstrain/test_id.py +7212 -0
  97. llm4ad/task/science_discovery/stresstrain/test_odd.py +3692 -0
  98. llm4ad/task/science_discovery/stresstrain/train.py +10807 -0
  99. llm4ad/tools/__init__.py +3 -0
  100. llm4ad/tools/llm/__init__.py +32 -0
  101. llm4ad/tools/llm/llm_api_https.py +73 -0
  102. llm4ad/tools/llm/llm_api_openai.py +21 -0
  103. llm4ad/tools/log_utils.py +730 -0
  104. llm4ad/tools/profiler/__init__.py +40 -0
  105. llm4ad/tools/profiler/profile.py +208 -0
  106. llm4ad/tools/profiler/tensorboard_profiler.py +85 -0
  107. llm4ad/tools/profiler/wandb_profiler.py +100 -0
  108. llm4ad-1.0.dist-info/LICENSE +21 -0
  109. llm4ad-1.0.dist-info/METADATA +19 -0
  110. llm4ad-1.0.dist-info/RECORD +112 -0
  111. llm4ad-1.0.dist-info/WHEEL +6 -0
  112. llm4ad-1.0.dist-info/top_level.txt +1 -0
llm4ad/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ from . import base
2
+ from . import method
3
+ from . import task
4
+ from .tools import profiler
5
+ from .tools import llm
6
+
@@ -0,0 +1,9 @@
1
+ from . import code, evaluate, sample, modify_code
2
+ from .code import (
3
+ Function,
4
+ Program,
5
+ TextFunctionProgramConverter
6
+ )
7
+ from .evaluate import Evaluation, SecureEvaluator
8
+ from .modify_code import ModifyCode
9
+ from .sample import LLM, SampleTrimmer
llm4ad/base/code.py ADDED
@@ -0,0 +1,270 @@
1
+ """
2
+ This file implements 2 classes representing unities of code:
3
+
4
+ - Function, containing all the information we need about functions: name, args,
5
+ body and optionally a return type and a docstring.
6
+
7
+ - Program, which contains a code preface (which could be imports, global
8
+ variables and classes, ...) and a list of Functions.
9
+
10
+ - For example, a function is shown below,
11
+ which is an un-executable program because 'np' is not defined, and 'WEIGHT' is not defined.
12
+ --------------------------------------------------------------------------------------------
13
+ def func(a: np.ndarray, b: np.ndarray) -> np.ndarray:
14
+ b = b + WEIGHT
15
+ return a + b
16
+ --------------------------------------------------------------------------------------------
17
+
18
+ - A program is an executable object as shown below.
19
+ --------------------------------------------------------------------------------------------
20
+ import numpy as np
21
+ WEIGHT = 10
22
+
23
+ def func(a: np.ndarray, b: np.ndarray) -> np.ndarray:
24
+ b = b + WEIGHT
25
+ return a + b
26
+ --------------------------------------------------------------------------------------------
27
+ """
28
+ from __future__ import annotations
29
+
30
+ import ast
31
+ import copy
32
+ import dataclasses
33
+ from typing import Any
34
+
35
+
36
+ @dataclasses.dataclass
37
+ class Function:
38
+ """A parsed Python function."""
39
+
40
+ name: str
41
+ args: str
42
+ body: str
43
+ return_type: str | None = None
44
+ docstring: str | None = None
45
+ score: Any | None = None
46
+ evaluate_time: float | None = None
47
+ sample_time: float | None = None
48
+
49
+ def __str__(self) -> str:
50
+ return_type = f' -> {self.return_type}' if self.return_type else ''
51
+
52
+ function = f'def {self.name}({self.args}){return_type}:\n'
53
+ if self.docstring:
54
+ # self.docstring is already indented on every line except the first one.
55
+ # Here, we assume the indentation is always four spaces.
56
+ new_line = '\n' if self.body else ''
57
+ function += f' """{self.docstring}"""{new_line}'
58
+ # self.body is already indented.
59
+ function += self.body + '\n\n'
60
+ return function
61
+
62
+ def __setattr__(self, name: str, value: str) -> None:
63
+ # Ensure there aren't leading & trailing new lines in `body`.
64
+ if name == 'body':
65
+ value = value.strip('\n')
66
+ # Ensure there aren't leading & trailing quotes in `docstring``.
67
+ if name == 'docstring' and value is not None:
68
+ if '"""' in value:
69
+ value = value.strip()
70
+ value = value.replace('"""', '')
71
+ super().__setattr__(name, value)
72
+
73
+ def __eq__(self, other: Function):
74
+ assert isinstance(other, Function)
75
+ return (self.name == other.name and
76
+ self.args == other.args and
77
+ self.return_type == other.return_type and
78
+ self.body == other.body)
79
+
80
+
81
+ @dataclasses.dataclass(frozen=True)
82
+ class Program:
83
+ """A parsed Python program."""
84
+
85
+ # `preface` is everything from the beginning of the code till the first
86
+ # function is found.
87
+ preface: str
88
+ functions: list[Function]
89
+
90
+ def __str__(self) -> str:
91
+ program = f'{self.preface}\n' if self.preface else ''
92
+ program += '\n'.join([str(f) for f in self.functions])
93
+ return program
94
+
95
+ def find_function_index(self, function_name: str) -> int:
96
+ """Returns the index of input function name."""
97
+ function_names = [f.name for f in self.functions]
98
+ count = function_names.count(function_name)
99
+ if count == 0:
100
+ raise ValueError(
101
+ f'function {function_name} does not exist in program:\n{str(self)}'
102
+ )
103
+ if count > 1:
104
+ raise ValueError(
105
+ f'function {function_name} exists more than once in program:\n'
106
+ f'{str(self)}'
107
+ )
108
+ index = function_names.index(function_name)
109
+ return index
110
+
111
+ def get_function(self, function_name: str) -> Function:
112
+ index = self.find_function_index(function_name)
113
+ return self.functions[index]
114
+
115
+
116
+ class _ProgramVisitor(ast.NodeVisitor):
117
+ """Parses code to collect all required information to produce a `Program`.
118
+ Note that we do not store function decorators.
119
+ """
120
+
121
+ def __init__(self, sourcecode: str):
122
+ self._codelines: list[str] = sourcecode.splitlines()
123
+ self._preface: str = ''
124
+ self._functions: list[Function] = []
125
+ self._current_function: str | None = None
126
+
127
+ def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
128
+ """Collects all information about the function being parsed.
129
+ """
130
+ # We only care about first level functions.
131
+ if node.col_offset == 0:
132
+ self._current_function = node.name
133
+ # --------------------------------------------------------------------------------------------
134
+ # RZ: Here is an issue: some cases were not taken into consideration.
135
+ # For instance, if the first parsed function has decorators,
136
+ # these decorators will also be retained in the preceding code snippet.
137
+ # However, in the algorithm's flow, these decorators are invalid,
138
+ # leading to subsequent evaluations reporting errors.
139
+ # --------------------------------------------------------------------------------------------
140
+ # if not self._functions:
141
+ # self._preface = '\n'.join(self._codelines[:node.lineno - 1])
142
+ # --------------------------------------------------------------------------------------------
143
+ # Fix bugs: if the first function is decorated, find the smallest line_no of the decorator,
144
+ # FIX bugs: and retrain the code above it as 'preface'.
145
+ # --------------------------------------------------------------------------------------------
146
+ if not self._functions:
147
+ has_decorators = bool(node.decorator_list)
148
+ if has_decorators:
149
+ # find the minimum line number and retain the code above
150
+ decorator_start_line = min(decorator.lineno for decorator in node.decorator_list)
151
+ self._preface = '\n'.join(self._codelines[:decorator_start_line - 1])
152
+ else:
153
+ self._preface = '\n'.join(self._codelines[:node.lineno - 1])
154
+ # --------------------------------------------------------------------------------------------
155
+ function_end_line = node.end_lineno
156
+ body_start_line = node.body[0].lineno - 1
157
+ # Extract the docstring.
158
+ docstring = None
159
+ if isinstance(node.body[0], ast.Expr) and isinstance(node.body[0].value, ast.Str):
160
+ docstring = f' """{ast.literal_eval(ast.unparse(node.body[0]))}"""'
161
+ if len(node.body) > 1:
162
+ # ----------------------------------------------------------------------------
163
+ # RZ: If the next line of the docstring is also comment that starts with "#"
164
+ # We should preserve it.
165
+ # ----------------------------------------------------------------------------
166
+ # body_start_line = node.body[1].lineno - 1
167
+ # ----------------------------------------------------------------------------
168
+ # Fix bugs: If we find the docstring, the function body start with
169
+ # Fix bugs: body[0].end_lineno, where body[0] refers to the docstring node.
170
+ # ----------------------------------------------------------------------------
171
+ body_start_line = node.body[0].end_lineno
172
+ # ----------------------------------------------------------------------------
173
+ else:
174
+ body_start_line = function_end_line
175
+
176
+ self._functions.append(Function(
177
+ name=node.name,
178
+ args=ast.unparse(node.args),
179
+ return_type=ast.unparse(node.returns) if node.returns else None,
180
+ docstring=docstring,
181
+ body='\n'.join(self._codelines[body_start_line:function_end_line]),
182
+ ))
183
+ self.generic_visit(node)
184
+
185
+ def return_program(self) -> Program:
186
+ return Program(preface=self._preface, functions=self._functions)
187
+
188
+
189
+ class TextFunctionProgramConverter:
190
+ """Convert text to Program instance and Function instance,
191
+ Convert Program instance to Function instance, and Function instance to Program instance.
192
+ """
193
+
194
+ @classmethod
195
+ def text_to_program(cls, program_str: str) -> Program | None:
196
+ """Returns Program object by parsing input text using Python AST.
197
+ """
198
+ try:
199
+ # We assume that the program is composed of some preface (e.g. imports,
200
+ # classes, assignments, ...) followed by a sequence of functions.
201
+ tree = ast.parse(program_str)
202
+ visitor = _ProgramVisitor(program_str)
203
+ visitor.visit(tree)
204
+ return visitor.return_program()
205
+ except:
206
+ return None
207
+
208
+ @classmethod
209
+ def text_to_function(cls, program_str: str) -> Function | None:
210
+ """Returns Function object by parsing input text using Python AST.
211
+ """
212
+ try:
213
+ program = cls.text_to_program(program_str)
214
+ if len(program.functions) != 1:
215
+ raise ValueError(f'Only one function expected, got {len(program.functions)}'
216
+ f':\n{program.functions}')
217
+ return program.functions[0]
218
+ except ValueError as value_err:
219
+ raise value_err
220
+ except:
221
+ return None
222
+
223
+ @classmethod
224
+ def function_to_program(cls, function: str | Function, template_program: str | Program) -> Program | None:
225
+ try:
226
+ # convert function to Function instance
227
+ if isinstance(function, str):
228
+ function = cls.text_to_function(function)
229
+ else:
230
+ function = copy.deepcopy(function)
231
+
232
+ # convert template_program to Program instance
233
+ if isinstance(template_program, str):
234
+ template_program = cls.text_to_program(template_program)
235
+ else:
236
+ template_program = copy.deepcopy(template_program)
237
+
238
+ # assert that a program have one function
239
+ if len(template_program.functions) != 1:
240
+ raise ValueError(f'Only one function expected, got {len(template_program.functions)}'
241
+ f':\n{template_program.functions}')
242
+
243
+ # replace the function body with the new function body
244
+ template_program.functions[0].body = function.body
245
+ return template_program
246
+ except ValueError as value_err:
247
+ raise value_err
248
+ except:
249
+ return None
250
+
251
+ @classmethod
252
+ def program_to_function(cls, program: str | Program) -> Function | None:
253
+ try:
254
+ # convert program to Program instance
255
+ if isinstance(program, str):
256
+ program = cls.text_to_program(program)
257
+ else:
258
+ program = copy.deepcopy(program)
259
+
260
+ # assert that a program have one function
261
+ if len(program.functions) != 1:
262
+ raise ValueError(f'Only one function expected, got {len(program.functions)}'
263
+ f':\n{program.functions}')
264
+
265
+ # return the function
266
+ return program.functions[0]
267
+ except ValueError as value_err:
268
+ raise value_err
269
+ except:
270
+ return None
@@ -0,0 +1,285 @@
1
+ from __future__ import annotations
2
+
3
+ import multiprocessing
4
+ import sys
5
+ import time
6
+ from abc import ABC, abstractmethod
7
+ from typing import Any
8
+
9
+ from .code import TextFunctionProgramConverter, Program
10
+ from .modify_code import ModifyCode
11
+
12
+
13
+ class Evaluation(ABC):
14
+ def __init__(
15
+ self,
16
+ template_program: str,
17
+ task_description: str = '',
18
+ use_numba_accelerate: bool = False,
19
+ use_protected_div: bool = False,
20
+ protected_div_delta: float = 1e-5,
21
+ random_seed: int | None = None,
22
+ timeout_seconds: int | float = None,
23
+ *,
24
+ exec_code: bool = True,
25
+ safe_evaluate: bool = True,
26
+ daemon_eval_process: bool = False,
27
+ **kwargs
28
+ ):
29
+ """Evaluator for executing generated code.
30
+ Args:
31
+ task_description: a brief description of the algorithm design task and specification.
32
+ template_program: the seed program (in str) as the initial function of the run.
33
+ the template_program should be executable, i.e., incorporating package import, and function definition, and function body.
34
+ The function body can be 'pass' in EoH as EoH does not request the template_program tobe valid.
35
+ use_numba_accelerate: Wrap the function with '@numba.jit(nopython=True)'.
36
+ use_protected_div : Modify 'a / b' => 'a / (b + delta)'.
37
+ protected_div_delta : Delta value in protected div.
38
+ random_seed : If is not None, set random seed in the first line of the function body.
39
+ timeout_seconds : Terminate the evaluation after timeout seconds.
40
+ exec_code : Using 'exec()' to compile the code and provide the callable function.
41
+ If is set to 'False', the 'callable_func' argument in 'self.evaluate_program' is always 'None'.
42
+ If is set to 'True', the user should provide the score of the program based on 'program_str' argument in 'self.evaluate_program'.
43
+ safe_evaluate : Evaluate in safe mode using a new process. If is set to False,
44
+ the evaluation will not be terminated after timeout seconds. The user should consider how to
45
+ terminate evaluating in time.
46
+ daemon_eval_process : Set the evaluate process as a daemon process. If set to True,
47
+ you can not set new processes in the evaluator. Which means in self.evaluate_program(),
48
+ you can not create new processes.
49
+
50
+ -Assume that: use_numba_accelerate=True, self.use_protected_div=True, and self.random_seed=2024.
51
+ -The original function:
52
+ --------------------------------------------------------------------------------
53
+ import numpy as np
54
+
55
+ def f(a, b):
56
+ a = np.random.random()
57
+ return a / b
58
+ --------------------------------------------------------------------------------
59
+ -The modified function will be:
60
+ --------------------------------------------------------------------------------
61
+ import numpy as np
62
+ import numba
63
+
64
+ @numba.jit(nopython=True)
65
+ def f():
66
+ np.random.seed(2024)
67
+ a = np.random.random()
68
+ return _protected_div(a, b)
69
+
70
+ def _protected_div(a, b, delta=1e-5):
71
+ return a / (b + delta)
72
+ --------------------------------------------------------------------------------
73
+ As shown above, the 'import numba', 'numba.jit()' decorator, and '_protected_dev' will be added by this function.
74
+ """
75
+
76
+ self.template_program = template_program
77
+ self.task_description = task_description
78
+ self.use_numba_accelerate = use_numba_accelerate
79
+ self.use_protected_div = use_protected_div
80
+ self.protected_div_delta = protected_div_delta
81
+ self.random_seed = random_seed
82
+ self.timeout_seconds = timeout_seconds
83
+ self.exec_code = exec_code
84
+ self.safe_evaluate = safe_evaluate
85
+ self.daemon_eval_process = daemon_eval_process
86
+
87
+ @abstractmethod
88
+ def evaluate_program(self, program_str: str, callable_func: callable) -> Any | None:
89
+ """Evaluate a given function. You can use compiled function (function_callable),
90
+ as well as the original function strings for evaluation.
91
+ Args:
92
+ program_str: The function in string. You can ignore this argument when implementation. (See below).
93
+ callable_func: The callable heuristic function. You can call it using `callable_func(args, kwargs)`.
94
+ Return:
95
+ Returns the fitness value.
96
+
97
+ Assume that: self.use_numba_accelerate = True, self.use_protected_div = True,
98
+ and self.random_seed = 2024, the argument 'function_str' will be something like below:
99
+ --------------------------------------------------------------------------------
100
+ import numpy as np
101
+ import numba
102
+
103
+ @numba.jit(nopython=True)
104
+ def f(a, b):
105
+ np.random.seed(2024)
106
+ a = a + np.random.random()
107
+ return _protected_div(a, b)
108
+
109
+ def _protected_div(a, b, delta=1e-5):
110
+ return a / (b + delta)
111
+ --------------------------------------------------------------------------------
112
+ As shown above, the 'import numba', 'numba.jit()' decorator,
113
+ and '_protected_dev' will be added by this function.
114
+ """
115
+ raise NotImplementedError('Must provide a evaluator for a function.')
116
+
117
+
118
+ class SecureEvaluator:
119
+ def __init__(self, evaluator: Evaluation, debug_mode=False, *, fork_proc: str | bool = 'auto', **kwargs):
120
+ assert fork_proc in [True, False, 'auto', 'default']
121
+ self._evaluator = evaluator
122
+ self._debug_mode = debug_mode
123
+
124
+ if self._evaluator.safe_evaluate:
125
+ if fork_proc == 'auto':
126
+ # force MacOS and Linux use 'fork' to generate new process
127
+ if sys.platform.startswith('darwin') or sys.platform.startswith('linux'):
128
+ multiprocessing.set_start_method('fork', force=True)
129
+ elif fork_proc is True:
130
+ multiprocessing.set_start_method('fork', force=True)
131
+ elif fork_proc is False:
132
+ multiprocessing.set_start_method('spawn', force=True)
133
+
134
+ def _modify_program_code(self, program_str: str) -> str:
135
+ function_name = TextFunctionProgramConverter.text_to_function(program_str).name
136
+ if self._evaluator.use_numba_accelerate:
137
+ program_str = ModifyCode.add_numba_decorator(
138
+ program_str, function_name=function_name
139
+ )
140
+ if self._evaluator.use_protected_div:
141
+ program_str = ModifyCode.replace_div_with_protected_div(
142
+ program_str, self._evaluator.protected_div_delta, self._evaluator.use_numba_accelerate
143
+ )
144
+ if self._evaluator.random_seed is not None:
145
+ program_str = ModifyCode.add_numpy_random_seed_to_func(
146
+ program_str, function_name, self._evaluator.random_seed
147
+ )
148
+ return program_str
149
+
150
+ def evaluate_program(self, program: str | Program):
151
+ try:
152
+ program_str = str(program)
153
+ # record function name BEFORE modifying program code
154
+ function_name = TextFunctionProgramConverter.text_to_function(program_str).name
155
+
156
+ program_str = self._modify_program_code(program_str)
157
+ if self._debug_mode:
158
+ print(f'DEBUG: evaluated program:\n{program_str}\n')
159
+
160
+ # safe evaluate
161
+ if self._evaluator.safe_evaluate:
162
+ result_queue = multiprocessing.Queue()
163
+ process = multiprocessing.Process(
164
+ target=self._evaluate_in_safe_process,
165
+ args=(program_str, function_name, result_queue),
166
+ daemon=self._evaluator.daemon_eval_process
167
+ )
168
+ process.start()
169
+
170
+ if self._evaluator.timeout_seconds is not None:
171
+ try:
172
+ # get the result in timeout seconds
173
+ result = result_queue.get(timeout=self._evaluator.timeout_seconds)
174
+ # after getting the result, terminate/kill the process
175
+ process.terminate()
176
+ process.join()
177
+ except:
178
+ # timeout
179
+ if self._debug_mode:
180
+ print(f'DEBUG: the evaluation time exceeds {self._evaluator.timeout_seconds}s.')
181
+ process.terminate()
182
+ process.join()
183
+ result = None
184
+ else:
185
+ result = result_queue.get()
186
+ process.terminate()
187
+ process.join()
188
+ return result
189
+ else:
190
+ return self._evaluate(program_str, function_name)
191
+ except Exception as e:
192
+ if self._debug_mode:
193
+ print(e)
194
+ return None
195
+
196
+ def evaluate_program_record_time(self, program: str | Program):
197
+ evaluate_start = time.time()
198
+ try:
199
+ program_str = str(program)
200
+ # record function name BEFORE modifying program code
201
+ function_name = TextFunctionProgramConverter.text_to_function(program_str).name
202
+
203
+ program_str = self._modify_program_code(program_str)
204
+ if self._debug_mode:
205
+ print(f'DEBUG: evaluated program:\n{program_str}\n')
206
+
207
+ # safe evaluate
208
+ if self._evaluator.safe_evaluate:
209
+ result_queue = multiprocessing.Queue()
210
+ process = multiprocessing.Process(
211
+ target=self._evaluate_in_safe_process,
212
+ args=(program_str, function_name, result_queue),
213
+ daemon=self._evaluator.daemon_eval_process
214
+ )
215
+ process.start()
216
+
217
+ if self._evaluator.timeout_seconds is not None:
218
+ try:
219
+ # get the result in timeout seconds
220
+ result = result_queue.get(timeout=self._evaluator.timeout_seconds)
221
+ # after getting the result, terminate/kill the process
222
+ process.terminate()
223
+ process.join()
224
+ except:
225
+ # timeout
226
+ if self._debug_mode:
227
+ print(f'DEBUG: the evaluation time exceeds {self._evaluator.timeout_seconds}s.')
228
+ process.terminate()
229
+ process.join()
230
+ result = None
231
+ else:
232
+ result = result_queue.get()
233
+ process.terminate()
234
+ process.join()
235
+ # return evaluate result and evaluate time
236
+ return result, time.time() - evaluate_start
237
+ else:
238
+ start = time.time()
239
+ score = self._evaluate(program_str, function_name)
240
+ return score, time.time() - start
241
+ except Exception as e:
242
+ if self._debug_mode:
243
+ print(e)
244
+ # return None and evaluate time
245
+ return None, time.time() - evaluate_start
246
+
247
+ def _evaluate_in_safe_process(self, program_str: str, function_name, result_queue: multiprocessing.Queue):
248
+ try:
249
+ if self._evaluator.exec_code:
250
+ # compile the program, and maps the global func/var/class name to its address
251
+ all_globals_namespace = {}
252
+ # execute the program, map func/var/class to global namespace
253
+ exec(program_str, all_globals_namespace)
254
+ # get the pointer of 'function_to_run'
255
+ program_callable = all_globals_namespace[function_name]
256
+ else:
257
+ program_callable = None
258
+
259
+ # get evaluate result
260
+ res = self._evaluator.evaluate_program(program_str, program_callable)
261
+ result_queue.put(res)
262
+ except Exception as e:
263
+ if self._debug_mode:
264
+ print(e)
265
+ result_queue.put(None)
266
+
267
+ def _evaluate(self, program_str: str, function_name):
268
+ try:
269
+ if self._evaluator.exec_code:
270
+ # compile the program, and maps the global func/var/class name to its address
271
+ all_globals_namespace = {}
272
+ # execute the program, map func/var/class to global namespace
273
+ exec(program_str, all_globals_namespace)
274
+ # get the pointer of 'function_to_run'
275
+ program_callable = all_globals_namespace[function_name]
276
+ else:
277
+ program_callable = None
278
+
279
+ # get evaluate result
280
+ res = self._evaluator.evaluate_program(program_str, program_callable)
281
+ return res
282
+ except Exception as e:
283
+ if self._debug_mode:
284
+ print(e)
285
+ return None