functioneer 0.0.1__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.
@@ -0,0 +1,26 @@
1
+ Proprietary Software License
2
+ ----------------------------
3
+
4
+ Software: functioneer (Functioneer)
5
+ Author: Quinn Marsh
6
+ Copyright: © [2024] Quinn Marsh
7
+
8
+ All rights reserved.
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, and distribute copies of the Software, subject to the following conditions:
11
+
12
+ 1. Attribution: All copies, substantial portions of the Software, and derivative works must include this original copyright notice, the name of the author(s), and a link to the original repository.
13
+
14
+ 2. Non-commercial Use: The Software may not be used, in whole or in part, for commercial purposes without explicit permission from the copyright holder.
15
+
16
+ 3. No Warranty: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+
18
+ 4. No Misrepresentation: Users of the Software shall not misrepresent the origin of the Software and should clearly indicate any modifications made.
19
+
20
+ By using, copying, modifying, or distributing this Software, you agree to the terms and conditions outlined above.
21
+
22
+
23
+ For details on using the Software within the terms of this license, please contact Quinn Marsh.
24
+
25
+ Quinn Marsh
26
+ [quinnmarsh@hotmail.com, X @qthedoc]
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.1
2
+ Name: functioneer
3
+ Version: 0.0.1
4
+ Summary: Functioneer is a Python package that simplifies the setup of automated analyses for your functions.
5
+ Author-email: Quinn Marsh <quinnmarsh@hotmail.com>
6
+ Maintainer-email: Quinn Marsh <quinnmarsh@hotmail.com>
7
+ License: Proprietary Software License
8
+ ----------------------------
9
+
10
+ Software: functioneer (Functioneer)
11
+ Author: Quinn Marsh
12
+ Copyright: © [2024] Quinn Marsh
13
+
14
+ All rights reserved.
15
+
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, and distribute copies of the Software, subject to the following conditions:
17
+
18
+ 1. Attribution: All copies, substantial portions of the Software, and derivative works must include this original copyright notice, the name of the author(s), and a link to the original repository.
19
+
20
+ 2. Non-commercial Use: The Software may not be used, in whole or in part, for commercial purposes without explicit permission from the copyright holder.
21
+
22
+ 3. No Warranty: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ 4. No Misrepresentation: Users of the Software shall not misrepresent the origin of the Software and should clearly indicate any modifications made.
25
+
26
+ By using, copying, modifying, or distributing this Software, you agree to the terms and conditions outlined above.
27
+
28
+
29
+ For details on using the Software within the terms of this license, please contact Quinn Marsh.
30
+
31
+ Quinn Marsh
32
+ [quinnmarsh@hotmail.com, X @qthedoc]
33
+
34
+ Project-URL: Homepage, https://github.com/qthedoc/functioneer
35
+ Project-URL: Issues, https://github.com/qthedoc/functioneer/issues
36
+ Project-URL: Funding, https://donate.pypi.org
37
+ Project-URL: Say Thanks!, http://quinnmarsh.com
38
+ Keywords: functioneer,analysis,automation,autorun
39
+ Classifier: Development Status :: 2 - Pre-Alpha
40
+ Classifier: Intended Audience :: Science/Research
41
+ Classifier: Topic :: Scientific/Engineering
42
+ Classifier: Programming Language :: Python :: 3.6
43
+ Classifier: Programming Language :: Python :: 3.7
44
+ Classifier: Programming Language :: Python :: 3.8
45
+ Classifier: Programming Language :: Python :: 3.9
46
+ Classifier: Programming Language :: Python :: 3.10
47
+ Classifier: Programming Language :: Python :: 3.11
48
+ Classifier: Programming Language :: Python :: 3.12
49
+ Classifier: Programming Language :: Python :: 3.13
50
+ Requires-Python: >=3.10
51
+ Description-Content-Type: text/markdown
52
+ License-File: LICENSE
53
+ Requires-Dist: numpy>=1.18.5
54
+ Requires-Dist: scipy>=1.5.2
55
+ Requires-Dist: pandas>=1.0.5
56
+
57
+ # Functioneer
58
+
59
+ **Author**: Quinn Marsh
60
+ **Date**: October 21, 2024
61
+
62
+ Functioneer is a powerful Python package that simplifies the setup of automated analyses for your functions. With Functioneer, you can configure and execute functions with unlimited combinations of inputs, making it perfect for optimizations, parameter sweeps, testing, and any scenario where you need to test a function(s) over a wide range of inputs.
63
+
64
+ ## Features
65
+
66
+ - **Fast Setup**: Define inputs, configurations, and combinations in seconds.
67
+ - **Automated Execution**: Run functions with diverse parameter combinations, reducing repetitive setup.
68
+ - **Flexible Input Handling**: Supports a variety of input types for complex analyses.
69
+ - **Combinatorial Efficiency**: Specify ranges and conditions to create the full space of function possibilities.
70
+ - **Output Management**: Easily retrieve and analyze the output of all function executions.
71
+
72
+ ## Installation
73
+
74
+ Install Functioneer directly from PyPI:
75
+
76
+ ```
77
+ pip install functioneer
78
+ ```
79
+
80
+ ## Documentation
81
+ For full documentation on setting up Functioneer, customizing parameters, and handling outputs, visit the documentation site.
82
+
83
+ ## License
84
+ Functioneer is released as open-source software with a custom license. Please see the LICENSE file for usage guidelines.
@@ -0,0 +1,28 @@
1
+ # Functioneer
2
+
3
+ **Author**: Quinn Marsh
4
+ **Date**: October 21, 2024
5
+
6
+ Functioneer is a powerful Python package that simplifies the setup of automated analyses for your functions. With Functioneer, you can configure and execute functions with unlimited combinations of inputs, making it perfect for optimizations, parameter sweeps, testing, and any scenario where you need to test a function(s) over a wide range of inputs.
7
+
8
+ ## Features
9
+
10
+ - **Fast Setup**: Define inputs, configurations, and combinations in seconds.
11
+ - **Automated Execution**: Run functions with diverse parameter combinations, reducing repetitive setup.
12
+ - **Flexible Input Handling**: Supports a variety of input types for complex analyses.
13
+ - **Combinatorial Efficiency**: Specify ranges and conditions to create the full space of function possibilities.
14
+ - **Output Management**: Easily retrieve and analyze the output of all function executions.
15
+
16
+ ## Installation
17
+
18
+ Install Functioneer directly from PyPI:
19
+
20
+ ```
21
+ pip install functioneer
22
+ ```
23
+
24
+ ## Documentation
25
+ For full documentation on setting up Functioneer, customizing parameters, and handling outputs, visit the documentation site.
26
+
27
+ ## License
28
+ Functioneer is released as open-source software with a custom license. Please see the LICENSE file for usage guidelines.
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "functioneer"
7
+ version = "0.0.1"
8
+ authors = [{ name = "Quinn Marsh", email = "quinnmarsh@hotmail.com" }]
9
+ maintainers = [{ name = "Quinn Marsh", email = "quinnmarsh@hotmail.com" }]
10
+ description = "Functioneer is a Python package that simplifies the setup of automated analyses for your functions."
11
+ readme = "README.md"
12
+ license = { file = "LICENSE" }
13
+ keywords = ["functioneer", "analysis", "automation", "autorun"]
14
+ requires-python = ">=3.10"
15
+ dependencies = [
16
+ "numpy>=1.18.5",
17
+ "scipy>=1.5.2",
18
+ "pandas>=1.0.5"
19
+ ]
20
+
21
+ classifiers = [
22
+ "Development Status :: 2 - Pre-Alpha",
23
+ "Intended Audience :: Science/Research",
24
+ "Topic :: Scientific/Engineering",
25
+ "Programming Language :: Python :: 3.6",
26
+ "Programming Language :: Python :: 3.7",
27
+ "Programming Language :: Python :: 3.8",
28
+ "Programming Language :: Python :: 3.9",
29
+ "Programming Language :: Python :: 3.10",
30
+ "Programming Language :: Python :: 3.11",
31
+ "Programming Language :: Python :: 3.12",
32
+ "Programming Language :: Python :: 3.13"
33
+ ]
34
+
35
+ [project.urls]
36
+ "Homepage" = "https://github.com/qthedoc/functioneer"
37
+ "Issues" = "https://github.com/qthedoc/functioneer/issues"
38
+ "Funding" = "https://donate.pypi.org"
39
+ "Say Thanks!" = "http://quinnmarsh.com"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,6 @@
1
+ # functioneer/__init__.py
2
+
3
+ # Import specific functions and classes from your modules
4
+
5
+ from functioneer.analysis import AnalysisModule, AnalysisStep, Define, Fork, Execute, Optimize
6
+ from functioneer.parameter import Parameter
@@ -0,0 +1,388 @@
1
+ from typing import Callable
2
+ import logging
3
+ import copy
4
+ import pandas as pd
5
+ from datetime import datetime
6
+ from time import time
7
+ import numpy as np
8
+ from scipy.optimize import minimize
9
+
10
+
11
+ from functioneer.parameter import ParameterSet, Parameter
12
+ from functioneer.util import call_with_matched_kwargs
13
+
14
+ class AnalysisStep():
15
+ """
16
+ Template AnalysisStep object containing basic info
17
+ """
18
+ def __init__(self, condition: Callable[..., bool] | None = None) -> None:
19
+ if condition is not None and not isinstance(condition, Callable):
20
+ raise ValueError(f"AnalysisStep condition must be a function that returns a bool")
21
+
22
+ self.condition = condition
23
+
24
+ # self.branch_cnt = 1
25
+
26
+ def run(self, paramset: ParameterSet):
27
+ """
28
+ template method for executing the AnalysisStep
29
+ returns a list of one or more ParameterSets that have been altered according to the AnalysisStep
30
+ returns a tuple of one or more parametersets
31
+
32
+ TODO decide if Default behavior is to COPY or PASS the paramset
33
+
34
+ validates paramset
35
+ """
36
+ if not isinstance(paramset, ParameterSet):
37
+ # logging.error(f"paramset is not of type ParameterSet")
38
+ raise ValueError(f"paramset is not of type ParameterSet")
39
+
40
+ return (paramset,)
41
+
42
+ class Define(AnalysisStep):
43
+ """
44
+ Define AnalysisStep: Adds parameter to parameterset
45
+ Will create new Parameter in ParameterSet if one does not already exist.
46
+ """
47
+ def __init__(self,
48
+ name: str,
49
+ value = None,
50
+ # func = None,
51
+ # func_output_mode = 'single',
52
+ condition = None
53
+ ):
54
+ super().__init__(condition)
55
+
56
+ if isinstance(name, str):
57
+ self.parameter = Parameter(name, value)
58
+
59
+ # elif isinstance(param, Parameter):
60
+ # pass
61
+
62
+ else:
63
+ logging.error(f"param must be of type Parameter")
64
+
65
+ def run(self, paramset):
66
+ # Validate
67
+ super().run(paramset)
68
+
69
+ # add new Parameter to Paramset
70
+ paramset.add_param(self.parameter)
71
+
72
+ return (paramset,)
73
+
74
+ class Fork(AnalysisStep):
75
+ """
76
+ Fork AnalysisStep: Splits analysis into several parallel analysis based on the provided parameters and values.
77
+ Will create new Parameter in ParameterSet if one does not already exist.
78
+ """
79
+ def __init__(self,
80
+ param_ids,
81
+ value_sets, # value_sets
82
+ condition = None
83
+ ):
84
+ super().__init__(condition)
85
+
86
+ # Handle single parameter id
87
+ if Parameter.is_valid_id(param_ids):
88
+ self.param_ids = (param_ids,)
89
+ value_sets = (value_sets,)
90
+
91
+ # Handle multiple param ids
92
+ elif isinstance(param_ids, (tuple, list)) and all([Parameter.is_valid_id(id) for id in param_ids]):
93
+ self.param_ids = param_ids
94
+
95
+ else:
96
+ raise ValueError(f"Fork param_ids must be a valid param id or a tuple of valid param ids")
97
+
98
+ # Identify number of parameters
99
+ self.param_cnt = len(self.param_ids)
100
+
101
+ # Validate same number of params and value sets
102
+ if len(self.param_ids) != len(value_sets):
103
+ raise ValueError(f"Invalid Fork (param_ids='{self.param_ids}'): Mismatch between number of parameters and number or value sets.")
104
+
105
+ self.value_cnt = len(value_sets[0])
106
+ for vs in value_sets:
107
+ if len(vs) != self.value_cnt:
108
+ raise ValueError(f"Invalid Fork (param_ids='{self.param_ids}'): Each parameter's value set must be the same length")
109
+
110
+ self.value_sets = value_sets
111
+
112
+ # TODO Validate value types
113
+ # this will be optional but left alone and values are tuples then it might be good to throw a warning in case user meant
114
+ # raise ValueError(f"Warning: values of paramfork are of type 'tuple'. to silence this error set Parameter.value_type tot tuple")
115
+
116
+ def run(self, paramset: dict[str, Parameter]):
117
+ super().run(paramset)
118
+
119
+ # Do forky stuff
120
+
121
+ next_paramsets = []
122
+ for branch_idx in range(self.value_cnt):
123
+ ps: ParameterSet = copy.deepcopy(paramset)
124
+ for id, vs in zip(self.param_ids, self.value_sets):
125
+ val = vs[branch_idx]
126
+ ps.update_param(id, val)
127
+
128
+ next_paramsets.append(ps)
129
+
130
+ return next_paramsets
131
+
132
+ class Execute(AnalysisStep):
133
+ """
134
+ Execute AnalysisStep: Executes provided function using matched kwargs and sets parameters based on returned dict
135
+ """
136
+ def __init__(self,
137
+ func: Callable,
138
+ output_param_ids: str = None,
139
+ input_param_ids: str = None,
140
+ condition = None
141
+ ):
142
+ super().__init__(condition)
143
+
144
+ self.func = func
145
+
146
+ if output_param_ids is None:
147
+ self.output_param_ids = output_param_ids
148
+ elif Parameter.is_valid_id(output_param_ids):
149
+ self.output_param_ids = output_param_ids
150
+ elif Parameter.is_valid_id_iterable(output_param_ids):
151
+ self.output_param_ids = output_param_ids
152
+ else:
153
+ raise ValueError(f"Execute Step's output_param_ids must be a str or tuple of str")
154
+
155
+ self.input_param_ids = input_param_ids
156
+
157
+ def run(self, paramset):
158
+ """
159
+ Executes funciton and modifies paramset
160
+
161
+ TODO future args: auto_add_new_args=True
162
+ """
163
+ super().run(paramset)
164
+
165
+ # TODO might need to deepcopy params is preservation of the upper level is needed
166
+ next_paramset = copy.deepcopy(paramset)
167
+
168
+ # Execute function: Positional argument input mode
169
+ if self.input_param_ids:
170
+ output = next_paramset.call_with_positional_args(func=self.func, param_ids=self.input_param_ids)
171
+
172
+ # Execute function: Keyword argument input mode
173
+ else:
174
+ output = next_paramset.call_with_matched_kwargs(func=self.func)
175
+
176
+ # Process output: Direct output mode
177
+ if self.output_param_ids and isinstance(self.output_param_ids, str):
178
+ next_paramset.update_param(id=self.output_param_ids, value=output)
179
+
180
+ # Process output: Positional output mode
181
+ elif self.output_param_ids:
182
+ if len(self.output_param_ids) != len(output):
183
+ raise ValueError(f"Number of function outputs ({len(output)}) does not match the specified output_param_ids ({len(self.output_param_ids)})")
184
+
185
+ for i, id in enumerate(self.output_param_ids):
186
+ next_paramset.update_param(id=id, value=output[i])
187
+
188
+ # Process output: Keyword output mode
189
+ else:
190
+ if not isinstance(output, dict):
191
+ raise ValueError(f"function in step {69} does not return valid dict of param names and values")
192
+
193
+ # next_paramsets.update_values(results)
194
+
195
+ # TODO this should be funcitonalized
196
+ # paramset.update_param_values(results)
197
+ for id, val in output.items():
198
+ next_paramset.update_param(id, value=val)
199
+
200
+ return (next_paramset,)
201
+
202
+ class Optimize(AnalysisStep):
203
+ """
204
+ Optimize AnalysisStep: Minimizes (or maximizes) objective by optimizing opt vars
205
+ """
206
+ def __init__(self,
207
+ func,
208
+ obj_param_id = None,
209
+ opt_param_ids = None,
210
+ method = 'SLSQP',
211
+ ftol = None,
212
+ xtol = None,
213
+ func_output_mode = 'single',
214
+ condition = None
215
+ ):
216
+ super().__init__(condition)
217
+
218
+ # Validate stuff
219
+ # if not is_valid_kwarg_func(func): TODO make this funciton validator a thing
220
+ # raise ValueError(f"Invalid Optimize: 'func' is not a valid objective function: {obj_param_id}")
221
+ if not isinstance(func, Callable):
222
+ raise ValueError(f"Invalid Optimize: 'func' is not a valid objective function: {obj_param_id}")
223
+ self.func = func
224
+
225
+ if not Parameter.is_valid_id(obj_param_id):
226
+ raise ValueError(f"Invalid Optimize: 'obj_param_id' is not a valid param id: {obj_param_id}")
227
+ self.obj_param_id = obj_param_id
228
+
229
+ # TODO funcitonalize these checks so they can be onelinesrs
230
+ if not Parameter.is_valid_id_iterable(opt_param_ids):
231
+ raise ValueError(f"Invalid Optimize: 'obj_param_id' is not a valid param id tuple: {opt_param_ids}")
232
+ self.opt_param_ids = opt_param_ids
233
+
234
+ self.method = method
235
+ self.ftol = ftol
236
+ self.xtol = xtol
237
+ self.func_output_mode = func_output_mode
238
+
239
+ def run(self, paramset):
240
+ # Validate
241
+ super().run(paramset)
242
+
243
+ # Copy
244
+ next_paramset = copy.deepcopy(paramset)
245
+
246
+ # create objective wrapper where x is a list of opt vars
247
+ def objective_wrapper(x): # (func, next_paramset, opt_param_ids)
248
+ '''
249
+ evaluates the user supplied objective function using fresh optimizer values from x.
250
+ uses x to modify parameter set and then run user objective function
251
+ '''
252
+ # Create test parameter set with values from optimizer
253
+ test_paramset = copy.deepcopy(next_paramset)
254
+ test_paramset.update_param_values(dict(zip(self.opt_param_ids, x)))
255
+
256
+ # evaluate objective parameter
257
+ obj_val = test_paramset.call_with_matched_kwargs(self.func)
258
+
259
+ # TODO add code for different output methods
260
+
261
+ return obj_val
262
+
263
+ # Create x0 array
264
+ x0 = [next_paramset[id].value for id in self.opt_param_ids]
265
+
266
+ # run optimization
267
+ results = minimize(objective_wrapper, x0, method=self.method)
268
+
269
+ # Check if the number of optimization parameters matches the length of x
270
+ if len(self.opt_param_ids) != len(results.x):
271
+ raise ValueError("Number of optimization parameters does not match the length of the optimization result")
272
+
273
+ # Set optimization parameters
274
+ next_paramset.update_param_values(dict(zip(self.opt_param_ids, results.x)))
275
+
276
+ # Set objective parameter
277
+ next_paramset.update_param(self.obj_param_id, value=results.fun)
278
+
279
+ return (next_paramset,)
280
+
281
+ class End(AnalysisStep):
282
+ def __init__(self, condition = None):
283
+ super().__init__(condition)
284
+
285
+ def run(self, paramset):
286
+ if self.condition:
287
+ return None
288
+ else:
289
+ return super().run(paramset)
290
+
291
+ class AnalysisModule():
292
+ def __init__(self, name='') -> None:
293
+
294
+ self.sequence = []
295
+ self.finished_leaves:int = 0
296
+
297
+ self.name = name
298
+
299
+ self.df: pd.DataFrame = pd.DataFrame()
300
+
301
+ self.t0 = None
302
+
303
+ pass
304
+
305
+ def add(self, analysis_object: AnalysisStep) -> None:
306
+ '''
307
+ appends AnalysisStep to the Analysis Sequence
308
+ e.g.: define, fork, evaluation or optimization
309
+ '''
310
+ # TODO Validate AnalysisStep
311
+
312
+ # TODO analysis_object.initialize(self.sequence)
313
+
314
+ self.sequence.append(analysis_object)
315
+
316
+ def run(self,
317
+ create_pandas = True,
318
+ verbose = True
319
+ ):
320
+ # self.sequence = tuple(self.sequence)
321
+
322
+ self.t0 = time()
323
+ self.finished_leaves = 0
324
+ self.execute_step(ParameterSet(), step_idx=0) # start on step 0
325
+
326
+ print('done with analysis!')
327
+
328
+ results = dict(
329
+ df = self.df,
330
+ runtime = self.runtime,
331
+ finished_leaves = self.finished_leaves,
332
+ )
333
+
334
+ return results
335
+
336
+ def execute_step(self, paramset: ParameterSet, step_idx:int):
337
+ """
338
+ Runs a single step of the analysis sequence, then recursivly calls the next.
339
+ """
340
+
341
+ # End analysis branch if no more steps
342
+ if step_idx >= len(self.sequence):
343
+ self.end_sequence(paramset)
344
+ return
345
+
346
+ # Extract step object
347
+ analysis_step: AnalysisStep = self.sequence[step_idx]
348
+
349
+ # Check if step has a conditional
350
+ run_step = True
351
+ if analysis_step.condition is not None:
352
+ run_step = paramset.call_with_matched_kwargs(analysis_step.condition)
353
+
354
+ t0 = time()
355
+ if run_step:
356
+ # Run the next step
357
+ new_paramsets = analysis_step.run(paramset)
358
+ else: # pass step
359
+ new_paramsets = (copy.deepcopy(paramset),)
360
+ step_runtime = time() - t0
361
+
362
+ # Process Step and Recursivly call next step
363
+ for ps in new_paramsets:
364
+ # Add this steps runtime to total runtime
365
+ ps['runtime'].value += step_runtime
366
+
367
+ # Recursivly call next step
368
+ self.execute_step(ps, step_idx+1)
369
+
370
+ # End analysis branch if no paramset returned
371
+ # TODO decide how to handle End: (paramset is None) OR (type(analysis_step)==End)
372
+ if new_paramsets is None:
373
+ self.end_sequence(paramset)
374
+
375
+ return
376
+
377
+ def end_sequence(self, paramset: ParameterSet):
378
+ # increment leaf count
379
+ self.finished_leaves += 1
380
+
381
+ # Add a new row to DataFrame
382
+ new_row = paramset.values_dict.copy() # Copy the values_dict
383
+ new_row['datetime'] = datetime.now() # Add the current datetime
384
+
385
+ # Append the new row
386
+ self.df = pd.concat([self.df, pd.DataFrame([new_row])], ignore_index=True)
387
+
388
+ self.runtime = time() - self.t0
@@ -0,0 +1,122 @@
1
+ import logging
2
+ import inspect
3
+
4
+ class Parameter():
5
+ def __init__(self,
6
+ id: str,
7
+ value = None,
8
+ value_set = None,
9
+ # fun = None,
10
+ # bounds = (None, None),
11
+ ):
12
+
13
+ if isinstance(id, str) and id != '':
14
+ self.id = id
15
+ else:
16
+ logging.error(f"Parameter name must be a string with at least one character")
17
+ self.value = value
18
+ # self.values = values
19
+ # self.function = fun
20
+ # self.bounds = bounds
21
+
22
+ def set(self, **kwargs):
23
+ '''
24
+ set any attribute of a parameter
25
+ '''
26
+ for attr, value in kwargs.items():
27
+ if hasattr(self, attr):
28
+ setattr(self, attr, value)
29
+ else:
30
+ raise AttributeError(f"Attribute '{attr}' does not exist in the parameter.")
31
+
32
+ @staticmethod
33
+ def is_valid_id(id):
34
+ # TODO add this to other places
35
+ return isinstance(id, str) and id != ''
36
+
37
+ @staticmethod
38
+ def is_valid_id_iterable(id_iterable):
39
+ return isinstance(id_iterable, (list, tuple)) and all([Parameter.is_valid_id(id) for id in id_iterable])
40
+
41
+
42
+ class ParameterSet(dict):
43
+ def __init__(self):
44
+
45
+ self.add_param(Parameter('runtime', 0))
46
+
47
+ def add_param(self, parameter):
48
+ if not isinstance(parameter, Parameter):
49
+ raise ValueError("parameter must be an instance of Parameter")
50
+
51
+ id = parameter.id
52
+
53
+ if id in self:
54
+ logging.info(f"Parameter '{id}' overwritten")
55
+
56
+ self[id] = parameter
57
+
58
+ @property
59
+ def values_dict(self):
60
+ return {key: param.value for key, param in self.items()}
61
+
62
+ def update_param(self, id, value=None):
63
+ """
64
+ Updates attributes of parameter matching 'id' or creates a new parameter if none match
65
+
66
+ TODO add other attributes here if needed
67
+ """
68
+ if value is not None:
69
+ if id in self:
70
+ self[id].value = value
71
+ else:
72
+ self.add_param(Parameter(id, value))
73
+
74
+ def update_param_values(self, values_dict: dict):
75
+ for id, val in values_dict.items():
76
+ self.update_param(id, val)
77
+
78
+ # def get_value_list(self, param_ids):
79
+ # # TODO Validate param_ids are in paramset
80
+ # return [self[id].value for id in param_ids]
81
+
82
+ def call_with_matched_kwargs(self, func):
83
+ """
84
+ Calls function with parameter values from the ParameterSet with matching keyword args.
85
+ Throws error if required arg is missing in paramset.
86
+ """
87
+ kwargs = self.values_dict
88
+
89
+ # Get the function signature
90
+ signature = inspect.signature(func)
91
+ arguments = signature.parameters
92
+
93
+ # Match provided kwargs with function arguments
94
+ matched_kwargs = {}
95
+ missing_args = []
96
+ for name, arg in arguments.items():
97
+ if name in kwargs:
98
+ matched_kwargs[name] = kwargs[name]
99
+ elif arg.default == inspect.Parameter.empty:
100
+ missing_args.append(name)
101
+
102
+ # Raise a warning if required arguments are missing
103
+ if missing_args:
104
+ missing_args = [f"'{arg}'" for arg in missing_args]
105
+ # TODO: catch this error higher up so we can provide info about WHAT param or funciton was being evaluated
106
+ raise ValueError(f"Missing the following required params while evaluating function: {', '.join(missing_args)}")
107
+
108
+ # Call the function with matched kwargs
109
+ return func(**matched_kwargs)
110
+
111
+ def call_with_positional_args(self, func, param_ids):
112
+ """
113
+ Calls function with parameter values as positional args.
114
+ The order of the args is the same as given in 'param_ids'.
115
+ """
116
+ # arg_list = self.get_value_list(self.input_params)
117
+
118
+ # TODO Validate param_ids are in paramset
119
+ arg_list = [self[id].value for id in param_ids]
120
+ return func(*arg_list)
121
+
122
+
@@ -0,0 +1,29 @@
1
+ import inspect
2
+
3
+
4
+ def call_with_matched_kwargs(func, kwargs):
5
+ '''
6
+ calls funciton with matching kwargs, throws error if required arg is missing.
7
+ TODO: could add a special case for a kwarg called kwargs where all kwargs are just fed in there. this allows user to have **kwargs on their functions
8
+ '''
9
+ # Get the function signature
10
+ signature = inspect.signature(func)
11
+ arguments = signature.parameters
12
+
13
+ # Match provided kwargs with function arguments
14
+ matched_kwargs = {}
15
+ missing_args = []
16
+ for name, arg in arguments.items():
17
+ if name in kwargs:
18
+ matched_kwargs[name] = kwargs[name]
19
+ elif arg.default == inspect.Parameter.empty:
20
+ missing_args.append(name)
21
+
22
+ # Raise a warning if required arguments are missing
23
+ if missing_args:
24
+ missing_args = [f"'{arg}'" for arg in missing_args]
25
+ # TODO: catch this error higher up so we can provide info about WHAT param or funciton was being evaluated
26
+ raise ValueError(f"Missing the following required params while evaluating function: {', '.join(missing_args)}")
27
+
28
+ # Call the function with matched kwargs
29
+ return func(**matched_kwargs)
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.1
2
+ Name: functioneer
3
+ Version: 0.0.1
4
+ Summary: Functioneer is a Python package that simplifies the setup of automated analyses for your functions.
5
+ Author-email: Quinn Marsh <quinnmarsh@hotmail.com>
6
+ Maintainer-email: Quinn Marsh <quinnmarsh@hotmail.com>
7
+ License: Proprietary Software License
8
+ ----------------------------
9
+
10
+ Software: functioneer (Functioneer)
11
+ Author: Quinn Marsh
12
+ Copyright: © [2024] Quinn Marsh
13
+
14
+ All rights reserved.
15
+
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, and distribute copies of the Software, subject to the following conditions:
17
+
18
+ 1. Attribution: All copies, substantial portions of the Software, and derivative works must include this original copyright notice, the name of the author(s), and a link to the original repository.
19
+
20
+ 2. Non-commercial Use: The Software may not be used, in whole or in part, for commercial purposes without explicit permission from the copyright holder.
21
+
22
+ 3. No Warranty: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ 4. No Misrepresentation: Users of the Software shall not misrepresent the origin of the Software and should clearly indicate any modifications made.
25
+
26
+ By using, copying, modifying, or distributing this Software, you agree to the terms and conditions outlined above.
27
+
28
+
29
+ For details on using the Software within the terms of this license, please contact Quinn Marsh.
30
+
31
+ Quinn Marsh
32
+ [quinnmarsh@hotmail.com, X @qthedoc]
33
+
34
+ Project-URL: Homepage, https://github.com/qthedoc/functioneer
35
+ Project-URL: Issues, https://github.com/qthedoc/functioneer/issues
36
+ Project-URL: Funding, https://donate.pypi.org
37
+ Project-URL: Say Thanks!, http://quinnmarsh.com
38
+ Keywords: functioneer,analysis,automation,autorun
39
+ Classifier: Development Status :: 2 - Pre-Alpha
40
+ Classifier: Intended Audience :: Science/Research
41
+ Classifier: Topic :: Scientific/Engineering
42
+ Classifier: Programming Language :: Python :: 3.6
43
+ Classifier: Programming Language :: Python :: 3.7
44
+ Classifier: Programming Language :: Python :: 3.8
45
+ Classifier: Programming Language :: Python :: 3.9
46
+ Classifier: Programming Language :: Python :: 3.10
47
+ Classifier: Programming Language :: Python :: 3.11
48
+ Classifier: Programming Language :: Python :: 3.12
49
+ Classifier: Programming Language :: Python :: 3.13
50
+ Requires-Python: >=3.10
51
+ Description-Content-Type: text/markdown
52
+ License-File: LICENSE
53
+ Requires-Dist: numpy>=1.18.5
54
+ Requires-Dist: scipy>=1.5.2
55
+ Requires-Dist: pandas>=1.0.5
56
+
57
+ # Functioneer
58
+
59
+ **Author**: Quinn Marsh
60
+ **Date**: October 21, 2024
61
+
62
+ Functioneer is a powerful Python package that simplifies the setup of automated analyses for your functions. With Functioneer, you can configure and execute functions with unlimited combinations of inputs, making it perfect for optimizations, parameter sweeps, testing, and any scenario where you need to test a function(s) over a wide range of inputs.
63
+
64
+ ## Features
65
+
66
+ - **Fast Setup**: Define inputs, configurations, and combinations in seconds.
67
+ - **Automated Execution**: Run functions with diverse parameter combinations, reducing repetitive setup.
68
+ - **Flexible Input Handling**: Supports a variety of input types for complex analyses.
69
+ - **Combinatorial Efficiency**: Specify ranges and conditions to create the full space of function possibilities.
70
+ - **Output Management**: Easily retrieve and analyze the output of all function executions.
71
+
72
+ ## Installation
73
+
74
+ Install Functioneer directly from PyPI:
75
+
76
+ ```
77
+ pip install functioneer
78
+ ```
79
+
80
+ ## Documentation
81
+ For full documentation on setting up Functioneer, customizing parameters, and handling outputs, visit the documentation site.
82
+
83
+ ## License
84
+ Functioneer is released as open-source software with a custom license. Please see the LICENSE file for usage guidelines.
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/functioneer/__init__.py
5
+ src/functioneer/analysis.py
6
+ src/functioneer/parameter.py
7
+ src/functioneer/util.py
8
+ src/functioneer.egg-info/PKG-INFO
9
+ src/functioneer.egg-info/SOURCES.txt
10
+ src/functioneer.egg-info/dependency_links.txt
11
+ src/functioneer.egg-info/requires.txt
12
+ src/functioneer.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ numpy>=1.18.5
2
+ scipy>=1.5.2
3
+ pandas>=1.0.5
@@ -0,0 +1 @@
1
+ functioneer