Function
Defines the functions used along with ceed.shape
to create time-varying
intensities for the shapes during an experiment. CeedStage
combines functions with shapes and displays them during an experiment according
to the stage’s function.
Although the functions’ range is (-infinity, +infinity)
and this module
places no restriction on the function output so that it may return any
scalar value, the graphics system can only accept values in the [0, 1]
range for each red, green, or blue channel. Consequently, the graphics system
(at ceed.stage.StageFactoryBase.set_shape_gl_color_values()
) will clip
the function output to that range.
Function factory and plugins
The FunctionFactoryBase
is a store of the defined FuncBase
sub-classes and customized function instances. Function classes/instances
registered with the FunctionFactoryBase
instance used by the
the GUI are available to the user in the GUI. During analysis, functions are
similarly registered with the FunctionFactoryBase
instance used in the
analysis and can then be used to get these functions. E.g.:
>>> # create the function store
>>> function_factory = FunctionFactoryBase()
>>> register_all_functions(function_factory) # register plugins
>>> LinearFunc = function_factory.get('LinearFunc') # get the class
>>> LinearFunc
ceed.function.plugin.LinearFunc
Classes can be registered manually with
FunctionFactoryBase.register()
or they can be registered automatically
with register_all_functions()
if they are an internal plugin or
register_external_functions()
for an external plugin. The GUI calls
register_all_functions()
when started as well as
register_external_functions()
if the
external_function_plugin_package
configuration
variable contains a package name.
See ceed.function.plugin
for details on writing plugins. For details on
how to customize functions, see below for important steps and methods during a
function’s lifecycle.
To get a function class registered with FunctionFactoryBase
, e.g.
ceed.function.plugin.CosFunc
:
>>> CosFunc = function_factory.get('CosFunc') # using class name
>>> CosFunc
ceed.function.plugin.CosFunc
Default function instances
When a function class (e.g. a linear function or a cosine function) is
registered, we create a default instance of the class,
and that instance is accessible at FunctionFactoryBase.funcs_inst
as well as from the GUI. The GUI makes available function names listed in
FunctionFactoryBase.funcs_inst
:
>>> function_factory.funcs_inst
{'Group': <ceed.function.FuncGroup at 0x2a351743c88>,
'Const': <ceed.function.plugin.ConstFunc at 0x2a351743e48>,
'Linear': <ceed.function.plugin.LinearFunc at 0x2a351743c18>,
'Exp': <ceed.function.plugin.ExponentialFunc at 0x2a351743f98>,
'Cos': <ceed.function.plugin.CosFunc at 0x2a351743eb8>}
We can also add customized function instances to be available to the user
with FunctionFactoryBase.add_func()
.
They have unique names, by which we access them from the GUI or from
FunctionFactoryBase.funcs_inst
. E.g.:
>>> f = LinearFunc(function_factory=function_factory, duration=2, m=2, name='line')
>>> function_factory.add_func(f)
>>> function_factory.funcs_inst
{...
'Cos': <ceed.function.plugin.CosFunc at 0x1da866f0ac8>,
'line': <ceed.function.plugin.LinearFunc at 0x1da866f0278>}
The name will be automatically changed if a function with the given name already exists when it is registered. E.g.
>>> f = LinearFunc(function_factory=function_factory, name='line')
>>> function_factory.add_func(f)
>>> f2 = LinearFunc(function_factory=function_factory, name='line')
>>> function_factory.add_func(f2)
>>> f.name
'line'
>>> f2.name
'line-2'
To use a registered function instance, it needs to be copied before it can be used. e.g.:
>>> from copy import deepcopy
>>> f = deepcopy(function_factory.funcs_inst['line'])
>>> f.init_func_tree()
>>> ...
Function basics
All functions inherit from FuncBase
. FuncBase
defines the
(stateful) interface that returns a number when called with a time argument.
Ceed functions are not like typical functions that can be called with any value, rather, they keep some internal state, which determines what its current domain is. Specifically, functions may only be called with monotonically increasing time values, like we do in an experiment where time always monotonically increases in multiples of the frame rate period. Here’s some example basic function usage:
>>> # function factory stores all the available function types (classes)
>>> function_factory = FunctionFactoryBase()
>>> # register all plugin functions
>>> register_all_functions(function_factory)
>>> # get cosine function class from internal plugin
>>> CosFunc = function_factory.get('CosFunc')
>>> # cos will have amplitude of 10, frequency of 1Hz and 10s duration
>>> f = CosFunc(function_factory=function_factory, duration=10, A=10, f=1)
>>> f
<ceed.function.plugin.CosFunc at 0x4eadba8>
>>> # we must initialize the function tree before use
>>> f.init_func_tree()
>>> # then we initialize specifically this function
>>> # initialize function base time at 3. I.e. 3 will be subtracted from
>>> # future times to convert from global to function-local time
>>> f.init_func(3)
>>> # evaluate the function at time 3 - 3 = 0 seconds
>>> f(3)
10.0
>>> f(3.25) # now at 3.25 - 3 = 0.25 seconds
6.123233995736766e-16
>>> f(3.5)
-10.0
>>> f(15) # calling outside the domain raises an exception
Traceback (most recent call last):
File "g:\python\libs\ceed\ceed\function\__init__.py", line 1042, in __call__
raise FuncDoneException
ceed.function.FuncDoneException
Initialization
Before a function can be used, it must be initialized with
FuncBase.init_func_tree()
and FuncBase.init_func()
.
FuncBase.init_func_tree()
intializes all functions and sub-functions in
the function tree recursively. FuncBase.init_func()
on the other hand
intitializes each function in the tree just before it is going to be used.
It takes a time value in seconds (in global time) where the domain
of the function starts. This is how we can evaluate the function independant of
the baseline global time. E.g. if a function computes f(t) = m * t + b
,
it’ll actually always be internally computed as
f(t) = m * (t - t_start) + b
, where FuncBase.t_start
is the value
passed to FuncBase.init_func()
(and FuncBase.init_loop_iteration()
at each loop iteration, but FuncBase.init_loop_iteration()
is called
internally, not by external code).
To evaluate the function, just call it with a time value as in the example above.
Domain and monotonicity
Functions have a domain, as determined by FuncBase.get_domain()
.
By default the domain for a just initialized function is
[FuncBase.t_start
, FuncBase.t_start
+
FuncBase.duration
* FuncBase.loop
*
FuncBase.get_timebase()
).
FuncBase.duration
can -1
, indicating the domain extends to
+infinity. FuncBase.get_timebase()
allows one to state the
FuncBase.duration
in FuncBase.timebase
units, for better
accuracy, rather than in seconds. If a function
loops (FuncBase.loop
), the domain extends until the end of the loops,
but the domain obviously shrinks with each loop iteration.
The domain always starts at FuncBase.t_start
, but
FuncBase.t_start
is updated internally for each loop to the time at the
start of the current loop (or more accurately the time the last loop ended,
FuncBase.t_end
if it’s before the current time). So the domain gets
smaller as we iterate the loops. E.g.:
>>> LinearFunc = function_factory.get('LinearFunc')
>>> # slope of 2, with 2 loop iterations
>>> f = LinearFunc(function_factory=function_factory, duration=10, loop=2, m=2)
>>> f.loop
2
>>> f.init_func_tree()
>>> f.init_func(2) # the valid domain starts at 2 seconds
>>> f(0) # zero seconds is outside the domain
Traceback (most recent call last):
File "g:\python\libs\ceed\ceed\function\__init__.py", line 1032, in __call__
ValueError: Cannot call function <ceed.function.plugin.LinearFunc object at 0x000001B66B022DD8> with time less than the function start 2
>>> f(2) # intercept at 2 - 2 = 0 seconds is zero
0.0
>>> f.t_start
2
>>> f.loop_count # it's in the first loop iteration
0
>>> f.get_domain(current_iteration=False)
(2, Fraction(22, 1))
>>> f(10) # value of time at 10 - 2 = 8 seconds
16.0
>>> # now we called it with a time value at the end of the first loop
>>> # i.e. 12 seconds is 2 + 10, which is the duration of the function,
>>> # so it automatically increments the loop and updates t_start
>>> f(12)
0.0
>>> f.t_start
Fraction(12, 1)
>>> f.loop_count
1
>>> f.get_domain(current_iteration=False) # the valid domain has updated
(Fraction(12, 1), Fraction(22, 1))
>>> f(20)
16.0
>>> f(22) # finally this is the end of the second loop end of domain
Traceback (most recent call last):
File "g:\python\libs\ceed\ceed\function\__init__.py", line 1042, in __call__
ceed.function.FuncDoneException
>>> f.loop_count
2
>>> # there's no more valid domain left
>>> f.get_domain()
(-1, -1)
>>> f(20) # function may only be called monotonically, it won't always
>>> # raise an error if called non-monotonically, but generally it will
Traceback (most recent call last):
File "g:\python\libs\ceed\ceed\function\__init__.py", line 1034, in __call__
ceed.function.FuncDoneException
As seen above, the domain of a function changes as it’s called with time values. Consequently, functions may only be called with monotonically increasing time arguments. If violated, it may raise an error, but it doesn’t always.
This rule is required to support functions that perform some IO and therefore may change some state, so calling with the same time input multiple times may not make sense. Similarly, we combine functions in a group (e.g. function A, then B etc), so once we finish function A of the group and moved to B, we don’t support moving to function A again, unless we are in the next loop. E.g.:
>>> ConstFunc = function_factory.get('ConstFunc')
>>> FuncGroup = function_factory.get('FuncGroup')
>>> # two constant functions that output a, and 10 respectively
>>> f1 = ConstFunc(function_factory=function_factory, duration=2, a=2)
>>> f2 = ConstFunc(function_factory=function_factory, duration=2, a=10)
>>> # create a function group of two sequential constant functions
>>> f = FuncGroup(function_factory=function_factory, loop=2)
>>> f.add_func(f1)
>>> f.add_func(f2)
>>> f.init_func_tree()
>>> f.init_func(1)
>>> f.get_domain(current_iteration=False)
(1, Fraction(9, 1))
>>> f(1) # this is internally calling f1
2
>>> f(3) # now we're calling f2 internally since we past the 2s duration
10
>>> # even though the domain is still the same, we cannot call it now with
>>> # a value less than 3 (because it'd have to jump back to f1 and
>>> # we don't need or support that)
>>> f.get_domain(current_iteration=False)
(1, Fraction(9, 1))
>>> f.loop_count
0
>>> f(5)
2
>>> f.loop_count
1
>>> f(7)
10
>>> f(9)
Traceback (most recent call last):
File "g:\python\libs\ceed\ceed\function\__init__.py", line 1341, in __call__
ceed.function.FuncDoneException
Timebase
As alluded to above, functions have a optional timebase to help be more
precise with the function duration. Normally, the FuncBase.duration
is set in seconds to the duration the function should take. But if the
projector is going at say 120 frames per second (fps), we may want to e.g.
set the shape to be intensity 1 for frame 0 (t = 0 / 120
) and intensity
0.5 for frame 1 (t = 1 / 120
). Expressing
duration = 1 / 120 = 0.008333333333333333
is not possible with decimal
math. Consequently, we allow setting the FuncBase.timebase
.
FuncBase.timebase
determines the units of the
FuncBase.duration
. If it’s zero, then the units is in seconds, like
normal. If it’s non-zero, e.g. the fraction Fraction(1, 120)
, then
duration is multiplied by it to get the duration in seconds. So e.g. with
Fraction(1, 120)
, if duration is 12
, then the stage duration is
12/120
seconds, or 12 frames.
During an experimental stage when functions are called, we pass time to the
functions represented as fractions rather than decimel, where the denominator
represents the true framerate of the projector, the numerator is the number of
frames elpased, so the time value is the elapsed time since the start. So
e.g. we’d call it with f(Fraction(180, 120))
if we are 1.5 seconds into
the stage and the projector frame rate is 120
. This allows us to do more
precise duration math. E.g.:
>>> from fractions import Fraction
>>> # duration will be 2 frames at 120 fps (2/120 seconds)
>>> f = LinearFunc(function_factory=function_factory, duration=2, timebase_numerator=1, timebase_denominator=120, m=2)
>>> f.init_func_tree()
>>> f.init_func(1) # start at 1 sec
>>> f.get_domain(current_iteration=False)
(1, Fraction(61, 60))
>>> f.get_timebase() # this is what we need to use to get the timebase
Fraction(1, 120)
>>> f.timebase
Fraction(1, 120)
>>> f(Fraction(120, 120)) # calling it t=frame 0 at 120fps
0.0
>>> f(Fraction(121, 120)) # calling it t=frame 1 at 120fps
0.016666666666666666
>>> f(Fraction(122, 120)) # calling it t=frame 2 at 120fps
Traceback (most recent call last):
File "g:\python\libs\ceed\ceed\function\__init__.py", line 1150, in __call__
ceed.function.FuncDoneException
Inheriting timebase
Functions can be grouped, e.g. in the example above. We don’t want to have to
specify the FuncBase.timebase
for each function. Consequently, if the
FuncBase.timebase
is unspecified (i.e. zero), a
function will inherit the timebase from the FuncBase.parent_func
they
belong to all the way to the root where FuncBase.parent_func
is None.
E.g. we want the function to alternate between 2 and 10 for each frame at 120fps:
>>> # each sub-function is exactly one frame long
>>> f1 = ConstFunc(function_factory=function_factory, duration=1, a=2)
>>> f2 = ConstFunc(function_factory=function_factory, duration=1, a=10)
>>> # so far the timbease are the defaults
>>> f1.timebase
Fraction(0, 1)
>>> f1.get_timebase() # so it's in seconds
1
>>> # because we specify a timebase for the group, all the sub-functions
>>> # will share the same timebase. Unless a sub function specifically sets
>>> # its own timebase
>>> f = FuncGroup(function_factory=function_factory, timebase_numerator=1, timebase_denominator=120, loop=2)
>>> f.add_func(f1)
>>> f.add_func(f2)
>>> # now, we inherit the timebase from out parent because we were added
>>> # to a parent and we didn't set our own timebase
>>> f1.timebase
Fraction(0, 1)
>>> f1.get_timebase() # our parents timebase
Fraction(1, 120)
>>> f.init_func(1) # i.e. 120 / 120
>>> f.get_domain(current_iteration=False)
(1, Fraction(31, 30))
>>> f(Fraction(120, 120)) # loop 0, f1
2
>>> f(Fraction(121, 120)) # loop 0, f2
10
>>> f(Fraction(122, 120)) # loop 1, f1
2
>>> f(Fraction(123, 120)) # loop 1, f2
10
>>> f(Fraction(124, 120)) # done
Traceback (most recent call last):
File "g:\python\libs\ceed\ceed\function\__init__.py", line 1459, in __call__
ceed.function.FuncDoneException
As we just saw, unless overwritten by a function, the timebase is inherited
from parent functions if they set it. Therefore, to get the actual timebase use
FuncBase.get_timebase()
, instead of FuncBase.timebase
. The latter
is only used to directly set the function’s timebase. Functions that need to
use the timebase should design all the duration values with that in mind.
The downside to setting a timebase is that it’s specific to that timebase, so to have a function in different timebases, it would need to be replicated for each timebase.
GUI customization
Function properties are customizable in the GUI by the user according to the
values returned by FuncBase.get_gui_props()
,
FuncBase.get_prop_pretty_name()
, and FuncBase.get_gui_elements()
.
These methods control what properties are editable by the user and the values they may potentially take.
Randomizing parameters
A FuncBase
subclass typically contains parameters. E.g.
ceed.function.plugin.LinearFunc
has a offset and slope (
ceed.function.plugin.LinearFunc.b
and
ceed.function.plugin.LinearFunc.m
). Sometimes it is desireable for the
parameters to be randomly re-sampled before each experiment, or even for each
loop iteration (FuncBase.loop_tree_count
).
All parameters that support randomization must be returned by
FuncBase.get_noise_supported_parameters()
and the specific distribution
used to randomize each parameter (based on param_noise
)
is stored in FuncBase.noisy_parameters
. The GUI manages
FuncBase.noisy_parameters
from user configuration based on
FuncBase.get_noise_supported_parameters()
.
Then, when the function is prepared by Ceed it calls
FuncBase.resample_parameters()
that re-samples all the randomized
parameters. Parameters that are randomized once for the function lifetime
(sample_each_loop
is False)
are sampled once and the parameter is set to that value. Parameters that are
randomized once for each loop iteration (see the docs for
sample_each_loop
) are sampled for
as many iterations they’ll experience and the samples are stored in
FuncBase.noisy_parameter_samples
. Then, the parameter is set to the
corresponding value for each iteration at the start in
FuncBase.init_func()
and FuncBase.init_loop_iteration()
.
As explained in Reference functions, Ceed supports function re-use by
creating a function and using references to it elsewhere. Parameters changed
in the original function will also be reflected in the references.
You have two options for how randomized parameters are handled in reference
functions, controlled by
lock_after_forked
. If it’s True,
all the reference function’s random values will be the same as the original’s
randomly generated values. If False, they will all have uniquely sampled
values.
Possible noise distributions are listed in the
ceed.function.param_noise.ParameterNoiseFactory
stored in
FunctionFactoryBase.param_noise_factory
. See
ceed.function.plugin
for how to add distributions.
lifecycle
The ultimate use of a function is to sample it for all timepoints that the function is active. Before the function is ready, however, it needs to be initialized, as well as for each loop iteration. Folowing are the overall steps performed by stages before using it in an experiment.
Starting with a stage, first the stage and all its functions are copied and
resampled using copy_and_resample()
. This calls
FuncBase.resample_parameters()
as described there.
Next, we call FuncBase.init_func_tree()
on each root function in the
stage. This calls FuncBase.init_func_tree()
recursively on
all the functions’s children (for group functions), if any.
Next, when the stage reaches this function as it ticks though all its
functions, it calls FuncBase.init_func()
to initialize this function run.
This will be called each time the stage or the function’s parent loops around
and restarts the function. It passes the current global time value to be used
by the function to convert subsequent timestemps from global to local
function values (relative to the start).
Then as the stage gets sequential timestamps to sample, it calls
CeedFunc.__call__()
with the time value uses the return value for its
shapes intensity. CeedFunc.__call__()
will call
FuncBase.init_loop_iteration()
as the timestemps reach the end of its
domain and it loops around (if any). When done with the loops, it’ll
raise a FuncDoneException
to indicate the function is done.
At the end of each loop iteration and at the end of the overall function it
calls FuncBase.finalize_loop_iteration()
, and
FuncBase.finalize_func()
, respectively.
Customizing functions
A FuncBase
has multiple methods that can be overwritten by a plugin.
Following are some relevant methods - see their docs for more details.
If the function generates the samples directly without using the public interfaces classes,
FuncBase.resample_parameters()
needs to be augmented if the function has any randomness.FuncBase.init_func_tree()
,FuncBase.init_func()
,FuncBase.init_loop_iteration()
,FuncBase.finalize_loop_iteration()
, andFuncBase.finalize_func()
may be augmented if any of these function lifecycle events requires additional initialization/finalization.CeedFunc.__call__()
is the method to inherit from and augment to return custom values from your function. See the built-in plugins, such asceed.function.plugin.LinearFunc
for examples, or below.See the function’s properties for additional customizations.
In all cases, FuncBase.get_state()
may also need to
be augmented to return any parameters that are part of the instance, otherwise
they won’t be copied when the function is copied and the function will use
incorrect values during an actual experiment when all functions are copied.
If functions are pre-computed, it may not have any side-effects, because they would occur before the experiment during pre-computing. So if these side-effects are needed (e.g. drug delivery), either turn off pre-computing for its stage or set the function’s duration to negative to disable it for the function.
Saving and restoring functions
Functions can be saved as a data dictionary and later restored into a function instance. E.g.:
>>> f = LinearFunc(function_factory=function_factory, duration=2, m=2, name='line')
>>> state = f.get_state()
>>> state
{'name': 'line',
'cls': 'LinearFunc',
'loop': 1,
'timebase_numerator': 0,
'timebase_denominator': 1,
'noisy_parameters': {},
'duration': 2,
't_offset': 0,
'm': 2,
'b': 0.0}
>>> # this is how we create a function from state
>>> f2 = function_factory.make_func(state)
>>> f2
<ceed.function.plugin.LinearFunc at 0x1a2cf679ac8>
>>> f2.get_state()
{'name': 'Linear',
'cls': 'LinearFunc',
'loop': 1,
'timebase_numerator': 0,
'timebase_denominator': 1,
'noisy_parameters': {},
'duration': 2,
't_offset': 0,
'm': 2,
'b': 0.0}
If you notice, name
was not restored to the new function. That’s because
name
is only restored if we pass clone=True
as name is considered an
internal property and not always user-customizable. Because we ensure
each function’s name in the GUI is unique. E.g. with clone:
>>> f3 = function_factory.make_func(state, clone=True)
>>> f3.get_state()
{'name': 'line',
'cls': 'LinearFunc',
'loop': 1,
...
'b': 0.0}
A fundumental part of Ceed is copying and reconstructing function objects.
E.g. this is required to recover functions from a template file, from old data,
or even to be able to run the experiment because it is run from a second
process. Consequently, anything required for the function to be reconstructed
must be returned by FuncBase.get_state()
.
Reference functions
Sometimes you may want to create a function group containing other functions. Instead of explicitly defining these sub-functions, we may want to refer to existing registered functions and let these sub-functions update when the existing function’s parameters are updated. They are mostly meant to be used from the GUI, although work fine otherwise.
CeedFuncRef
allows one to reference functions in such a manner.
Instead of copying a function, just get a reference to it with
FunctionFactoryBase.get_func_ref()
and add it to the function group.
When destroyed, such function references must be explicitly released with
FunctionFactoryBase.return_func_ref()
, otherwise the original function
cannot be deleted in the GUI.
Methods that accept functions (such as FuncGroup.add_func()
) should also
typically accept CeedFuncRef
functions.
CeedFuncRef
cannot be used directly, unlike normal function.
Therefore, they or any functions that contain them must first copy them and
expand them to refer to the orignal functions being referenced before using
them with FuncBase.copy_expand_ref()
or
CeedFuncRef.copy_expand_ref()
.
E.g.
>>> f = LinearFunc(function_factory=function_factory)
>>> f
<ceed.function.plugin.LinearFunc at 0x1a2cf679c18>
>>> ref_func = function_factory.get_func_ref(func=f)
>>> ref_func
<ceed.function.CeedFuncRef at 0x1a2cf74d828>
>>> ref_func.func
<ceed.function.plugin.LinearFunc at 0x1a2cf679c18>
>>> function_factory.return_func_ref(ref_func)
Copying functions
Functions can be copied automatically using deepcopy
or
FuncBase.copy_expand_ref()
. The former makes a full copy of all the
functions, but any CeedFuncRef
functions will only copy the
CeedFuncRef
, not the original function being refered to. The latter,
instead of copying the CeedFuncRef
, will replace any
CeedFuncRef
with copies of the class it refers to.
Functions can be manually copied with FuncBase.get_state()
and
FuncBase.set_state()
(although FunctionFactoryBase.make_func()
is
more appropriate for end-user creation).
Create function in script
A function and stages/shapes can be created in a script,
saved to a yaml file, and then imported from the GUI ready to be
used in an experiment. See
save_config_to_yaml()
for an example.
Custom plugin function example
As explained above, plugins can register customized FuncBase
sub-classes to be included in the GUI. Following is an example of how
CeedFunc.__call__()
can be overwritten in a plugin. See also
ceed.function.plugin
for complete examples:
class MyFunc(CeedFunc):
def __init__(
self, name='MyFunc', description='y(t) = random() * t',
**kwargs):
# this sets the default name of the class and description, but
# allows it to be overwritten when specified
super().__init__(name=name, description=description, **kwargs)
def __call__(self, t: Union[int, float, Fraction]) -> float:
# alwasy call super first, this handles loop iteration and checking
# whether the function is done and raises FuncDoneException
super().__call__(t)
# return the value
return random() * self.get_relative_time(t)
If properly registered from the plugin, this function type will be available in the Ceed GUI.
- exception ceed.function.FuncDoneException
Bases:
Exception
Raised when the
FuncBase
is called with a time value after its valid time interval.
- class ceed.function.FunctionFactoryBase(**kwargs)
Bases:
kivy._event.EventDispatcher
A global store of the defined
FuncBase
sub-classes and customized function instances.See
ceed.function
for details.- Events
- on_changed:
The event is triggered every time a function is added or removed from the factory or if a class is registered.
- on_data_event:
The event is dispatched by functions whenever the wish to log something. During an experiment, this data is captured and recorded in the file.
To dispatch, you must pass the function’s
ceed_id
and a string indicating the event type. You can also pass arbitrary arguments that gets recorded as well. E.g.function_factory.dispatch('on_data_event', func.ceed_id, 'drug', .4, 'h2o')
.See
event_data
for details on default events as well as data type and argument requirements.
- funcs_inst: Dict[str, ceed.function.FuncBase]
Dict whose keys is the function
FuncBase.name
and whose values is the corresponding function instances.Contains functions added with
add_func()
as well as those automatically created and added whenregister()
is called on a class.>>> function_factory.funcs_inst {'Group': <ceed.function.FuncGroup at 0x1da866f00b8>, 'Const': <ceed.function.plugin.ConstFunc at 0x1da866f0978>, 'Linear': <ceed.function.plugin.LinearFunc at 0x1da866f09e8>, 'Exp': <ceed.function.plugin.ExponentialFunc at 0x1da866f0a58>, 'Cos': <ceed.function.plugin.CosFunc at 0x1da866f0ac8>, 'line': <ceed.function.plugin.LinearFunc at 0x1da866f0278>, 'line-2': <ceed.function.plugin.LinearFunc at 0x1da866f0e48>}
- funcs_cls: Dict[str, Type[ceed.function.FuncType]] = {}
Dict whose keys is the name of the function classes registered with
register()
and whose values is the corresponding classes.:>>> function_factory.funcs_cls {'FuncGroup': ceed.function.FuncGroup, 'ConstFunc': ceed.function.plugin.ConstFunc, 'LinearFunc': ceed.function.plugin.LinearFunc, 'ExponentialFunc': ceed.function.plugin.ExponentialFunc, 'CosFunc': ceed.function.plugin.CosFunc}
- funcs_user: List[ceed.function.FuncBase] = []
List of the function instances registered with
add_func()
.It does not include the instances automatically created and stored in
funcs_inst_default
when a function class isregister()
.
- funcs_inst_default: Dict[str, ceed.function.FuncBase] = {}
Dict whose keys is the function
FuncBase.name
and whose values are the corresponding function instances.Contains only the functions that are automatically created and added when
register()
is called on a class.>>> function_factory.funcs_inst_default {'Group': <ceed.function.FuncGroup at 0x1da866f00b8>, 'Const': <ceed.function.plugin.ConstFunc at 0x1da866f0978>, 'Linear': <ceed.function.plugin.LinearFunc at 0x1da866f09e8>, 'Exp': <ceed.function.plugin.ExponentialFunc at 0x1da866f0a58>, 'Cos': <ceed.function.plugin.CosFunc at 0x1da866f0ac8>}
- plugin_sources: Dict[str, List[Tuple[Tuple[str], bytes]]] = {}
A dictionary of the names of all the plugin packages imported, mapped to the python file contents of the directories in the package. Each value is a list of tuples.
The first item of each tuple is also a tuple containing the names of the directories leading to and including the python filename relative to the package. The second item in the tuple is the bytes content of the file.
- unique_names: ceed.utils.UniqueNames = None
A set that tracks existing function names to help us ensure all global functions have unique names.
- param_noise_factory: ceed.function.param_noise.ParameterNoiseFactory = None
An automatically created instance of
ceed.function.param_noise.ParameterNoiseFactory
that is used to register and get noise classes for use with functions.
- get_func_ref(name: Optional[str] = None, func: Optional[ceed.function.FuncBase] = None) ceed.function.CeedFuncRef
Returns a
CeedFuncRef
instance that refers to the original function. Seeceed.function
for details.One of
name
orfunc
must be specified. The function being referenced byfunc
should have been registered with this class, although it is not explicitly enforced currently.If used,
return_func_ref()
must be called when the reference is not used anymore.- Parameters
name – The name of the function to lookup in
funcs_inst
.func – Or the actual function to use.
- Returns
A
CeedFuncRef
to the original function.
- return_func_ref(func_ref: ceed.function.CeedFuncRef)
Releases the function ref created by
get_func_ref()
.- Parameters
func_ref – Instance returned by
get_func_ref()
.
- register(cls: Type[ceed.function.FuncType], instance: Optional[ceed.function.FuncBase] = None)
Registers the class and adds it to
funcs_cls
. It also creates an instance (unlessinstance
is provided, in which case that is used) of the class that is added tofuncs_inst
andfuncs_inst_default
.See
ceed.function
for details.- Params
- cls: subclass of
FuncBase
The class to register.
- instance: instance of cls
The instance of cls to use. If None, a default class instance, using the default
FuncBase.name
is stored. Defaults to None.
- cls: subclass of
- add_plugin_source(package: str, contents: List[Tuple[Tuple[str], bytes]])
Adds the package contents to
plugin_sources
if it hasn’t already been added. Otherwise raises an error.
- get(name: str) Optional[Type[ceed.function.FuncType]]
Returns the class with name
name
that was registered withregister()
.See
ceed.function
for details.- Params
- name: str
The name of the class (e.g.
'ExpFunc'
).
- Returns
The class if found, otherwise None.
- get_names() List[str]
Returns the class names of all classes registered with
register()
.
- get_classes() List[Type[ceed.function.FuncType]]
Returns the classes registered with
register()
.
- add_func(func: ceed.function.FuncBase)
Adds the function to
funcs_user
andfuncs_inst
, which makes it available in the GUI.See
ceed.function
for details.If the
FuncBase.name
already exists infuncs_inst
,FuncBase.name
will be set to a unique name based on its original name. Once added until removed, anytime the function’sFuncBase.name
changes, if it clashes with an existing function’s name, it is automatically renamed.- Params
- func: a
FuncBase
derived instance. The function to add.
- func: a
- remove_func(func: ceed.function.FuncBase, force: bool = False) bool
Removes a function previously added with
add_func()
.- Params
- func: a
FuncBase
derived instance. The function to remove.
- force: bool
If True, it’ll remove the function even if there are references to it created by
get_func_ref()
.
- func: a
- Returns
Whether the function was removed successfully. E.g. if force is False and it has a ref, it won’t be removed.
- clear_added_funcs(force=False)
Removes all the functions registered with
add_func()
.- Params
- force: bool
If True, it’ll remove all functions even if there are references to them created by
get_func_ref()
.
- make_func(state: dict, instance: Optional[ceed.function.FuncBase] = None, clone: bool = False) ceed.function.FuncBase
Instantiates the function from the state and returns it.
This method must be used to instantiate a function from state. See
ceed.function
for details and an example.- Parameters
state – The state dict representing the function as returned by
FuncBase.get_state()
.instance – If None, a function instance of the type specified in
state
will be created and state will applied to it. Otherwise, it is applied to the given instance, which must be of the correct class.clone – If False, only user customizable properties of the function will be set, otherwise, all properties from state are applied to the function. Clone is meant to be an complete re-instantiation of the function.
- Returns
The function instance created.
- save_functions(use_cache=False) List[dict]
Returns a dict representation of all the functions added with
add_func()
.- Parameters
use_cache – If True, it’ll get the state using the cache from previous times the state was read and cached, if the cache exists.
It is a list of dicts where each item is the
FuncBase.get_state()
of the corresponding function inuser_funcs
.
- recover_funcs(function_states: List[dict]) Tuple[List[ceed.function.FuncBase], Dict[str, str]]
Takes a list of function states such as returned by
save_functions()
and instantiates the functions represented by the states and adds (add_func()
) the functions to the factory.- Parameters
function_states – List of function states.
- Returns
A tuple
funcs, name_map
,funcs
is the list of functions,name_map
is a map from the original function’s name to the new name given to the function (in case a function with that name already existed).
- class ceed.function.FuncBase(function_factory, **kwargs)
Bases:
kivy._event.EventDispatcher
,ceed.utils.CeedWithID
The base class for all functions.
See
ceed.function
for details.When writing a plugin with new functions, you probably want to inherit from
CeedFunc
because it provides some convenience methods (calling the function will check if it’s in the valid domain). See e.g.ceed.function.plugin.LinearFunc
for an example.- Events
- on_changed:
Triggered whenever a configurable property (i.e. it is returned as key in the
get_state()
dict) of this instance is changed.
- name: str
The name of the function instance. The name must be unique within a
FunctionFactoryBase
once it is added (FunctionFactoryBase.add_func()
) to theFunctionFactoryBase
, otherwise it’s automatically renamed.
- description
A description of the function. This is shown to the user and should describe the function.
- icon
The function icon. Not used currently.
- duration: Union[float, int]
How long after the start of the function until the function is complete and the stage continues on to the next
loop
or the function is completely done.The loop iteration is done when the function reaches
duration
after the functiont_start
. So, if it starts at 0 seconds andduration
is 1 second, it is done at exactly 1 second.-1 means go on forever and it never finishes (except if manually finished). This could be used e.g. if waiting for some external interrupt - set duration to -1, and only finish when the interrupt happens.
For
FuncGroup
this is automatically computed as the sum of all the sub-function duration. If any of them are negative, this will also be negative.See
ceed.function
for more details.The value is in
get_timebase()
units.
- duration_min: Union[float, int]
Same as
duration
, except it excludes any infinite portions of the function duration.I.e. any sub function whose duration is negative will be read as zero. This gives the estimated minimum duration not including any infinite portions of a single loop iteration.
See
ceed.function
for more details.The value is in
get_timebase()
units and is read only.
- duration_min_total: Union[float, int]
The total duration of the function including all the loops, excluding any infinite portions of the function duration.
Similar to
duration_min
, except it includes all the loops. So e.g. ifduration_min
is5
andloop
is2
,duration_min_total
would typically be10
.See
ceed.function
for more details.The value is in
get_timebase()
units and is read only.
- loop: int
- The number of times the function loops through before it is considered
done.
At the end of each loop
loop_count
is incremented until done, starting from zero.See
ceed.function
for more details.
- parent_func: Optional[ceed.function.FuncBase] = None
If this function is the child of another function, e.g. it’s a sub-function of a
FuncGroup
instance, thenparent_func
points to the parent function.
- has_ref: bool
Whether there’s a CeedFuncRef pointing to this function. If True, the function should not be deleted from the function factory that holds it.
This is automatically set by
FunctionFactoryBase.get_func_ref()
andFunctionFactoryBase.return_func_ref()
.
- display = None
The widget that visualize this function, if any.
- timebase: Union[float, fractions.Fraction]
The (read-only) timebase scale factor as computed by
timebase_numerator
/
timebase_denominator
. It returns either a float, or a Fraction instance when the numerator and denominator are ints.To set,
timebase_numerator
andtimebase_denominator
must be set individually. To use, callget_timebase()
, rather than usingtimebase
directly.The timebase is the scaling factor by which some function properties that relate to time, e.g.
duration
, are multiplied to convert from timebase units to time.By default
timebase_numerator
is 0 andtimebase_denominator
is 1 which makestimebase
0 indicating that the timebase used is given by the parent function or is 1. Whentimebase
is not 0 thistimebase
is used instead.See
ceed.function
andget_timebase()
for more details.Note
This property is dispatched whenever the value returned by
get_timebase()
would change, even iftimebase
didn’t change. E.g. when a parent’s timebase changes.
- t_start: Union[float, int, fractions.Fraction] = 0
The global time offset subtracted from the time passed to the function.
This is the global time with which the function or loop was initialized,
get_relative_time()
removes it to get to local time. The value is in seconds. Seeceed.function
for more details.Don’t set directly, it is set in
init_func()
andinit_loop_iteration()
.
- t_end: Union[float, int, fractions.Fraction] = 0
The time at which the loop or function ends in global timebase.
Set by the function after each loop is done (i.e.
CeedFunc.is_loop_done()
returned True) and is typically the second value fromget_domain()
, or the current time value if that is negative.It is updated before
finalize_loop_iteration()
, andfinalize_func()
are called.
- loop_count: int = 0
The current loop iteration.
This goes from zero (set by
init_func()
/init_loop_iteration()
) toloop
. The function is done when it is exactlyloop
, having loopedtimes
. Whenfinalize_loop_iteration()
andfinalize_func()
are called, it is the value for the loop iteration that just ended.See also
ceed.function
.
- loop_tree_count: int = 0
The current iteration, starting from zero, and incremented for each loop of the function, including outside loops that loop over the function.
See
init_func_tree()
for details.
- noisy_parameters: Dict[str, ceed.function.param_noise.NoiseBase]
A dict mapping parameter names of the function to
NoiseBase
instances that indicate how the parameter should be sampled, when the parameter needs to be stochastic.Only parameters returned in
get_noise_supported_parameters()
may have randomness associated with them.E.g.:
>>> f = LinearFunc(function_factory=function_factory, duration=2, m=2) >>> UniformNoise = function_factory.param_noise_factory.get_cls('UniformNoise') >>> f.set_parameter_noise( ... 'm', noise_obj=UniformNoise(min_val=10, max_val=20)) >>> f.m 2 >>> f.b 0.0 >>> f.resample_parameters() >>> f.m 12.902067284602595 >>> f.resample_parameters() >>> f.m 11.555420807597352 >>> f.b 0.0
- function_factory: ceed.function.FunctionFactoryBase = None
The
FunctionFactoryBase
instance with which this function is associated. This should be set by whoever creates the function by passing it to the constructor.
- noisy_parameter_samples: Dict[str, List[float]] = {}
For each parameter listed in
noisy_parameters
, ifsample_each_loop
, then during theresample_parameters()
stage we pre-compute the values for the parameter for each loop iteration and store it here.The pre-computed values are then used to update the parameter during each
init_func()
andinit_loop_iteration()
. The total number of samples includes all outside loops, seesample_each_loop
andinit_func_tree()
.
- get_timebase() Union[float, fractions.Fraction]
Returns the function’s timebase.
If
timebase_numerator
andtimebase
is 0, it returns the timebase of itsparent_func
withget_timebase()
if it has a parent. If it doesn’t have a parent, it return 1. Otherwise, it returnstimebase
.
- get_gui_props()
Called internally by the GUI to get the properties of the function that should be displayed to the user to be customized.
- Returns
A dict that contains all properties that should be displayed. The values of the property is as follows:
If it’s the string int, float, str or it’s the python type int, float, or str then the GUI will show a editable property for this type.
If it’s None, we look at the value of the property in the instance and display accordingly (e.g. if it’s a str type property, a string property is displayed to the user).
Note
The default value determines the type. So if the default value is
0
, the type will be int and a user won’t be able to enter a float. Use e.g.0.0
in the latter case.
E.g.:
>>> Cos = function_factory.get('CosFunc') >>> cos = Cos() >>> cos.get_gui_props() {'A': None, 'duration': None, 'f': None, 'loop': None, 'name': None, 't_offset': None, 'th0': None}
- get_prop_pretty_name()
Called internally by the GUI to get a translation dictionary which converts property names as used in
get_state()
into nicer property names used to display the properties to the user for customization. E.g. “timebase_numerator” may be displayed as “TB num” as that is more concise.- Returns
A dict that contains all properties whose names should be changed when displayed. Keys in the dict are the names as returned by
get_state()
, the values are the names that should be displayed instead. If a property is not included it’s original property name is used instead.
E.g.:
>>> f = LinearFunc(function_factory=function_factory) {'timebase_numerator': 'TB num', 'timebase_denominator': 'TB denom'}
- get_gui_elements() dict
Returns widget instances that should be displayed to the user along with this function’s editable properties of
get_gui_props()
.These widgets are displayed along with other config parameters for the function and can be used for custom config options. This is called by the Ceed GUI when the settings are first displayed to the user.
- Returns
It should return a dict of the name of each setting mapped to the widget controlling the setting. It will be displayed in two columns: the name followed by the widget on the same row.
E.g.:
>>> Cos = function_factory.get('CosFunc') >>> cos = Cos() >>> cos.get_gui_elements() {}
- get_noise_supported_parameters() Set[str]
Returns the set of property names of this function that supports randomness and may have an
ceed.function.param_noise.NoiseBase
instance associated with it.
- get_cached_state(use_cache=False) Dict
Like
get_state()
, but it caches the result. And next time it is called, ifuse_cache
is True, the cached value will be returned, unless the config changed in between. Helpful for backup so we don’t recompute the full state.- Parameters
use_cache – If True, it’ll get the state using the cache from previous times the state was read and cached, if the cache exists.
- Returns
The state dict.
- get_state(recurse=True, expand_ref=False) Dict
Returns a dict representation of the function so that it can be reconstructed later with
apply_state()
.- Params
- recurse: bool
When the function has children functions, e.g. a
FuncGroup
, if True all the children functions’ states will also be returned, otherwise, only this function’s state is returned. See the example.- expand_ref: bool
If True, if any sub-functions (or this function itself) are
CeedFuncRef
instances, they will be expanded to contain the state of the actual underlying function. Otherwise, it is returned as being a function reference.
- Returns
A dict with all the configuration data.
E.g.:
>>> Cos = function_factory.get('CosFunc') >>> cos = Cos() >>> cos.get_state() {'A': 1.0, 'cls': 'CosFunc', 'duration': 0, 'f': 1.0, 'loop': 1, 'name': 'Cos', 't_offset': 0, 'th0': 0.0} >>> Group = function_factory.get('FuncGroup') >>> g = Group() >>> g <ceed.function.FuncGroup at 0x4f85800> >>> g.add_func(cos) >>> g.get_state(recurse=True) {'cls': 'FuncGroup', 'funcs': [{'A': 1.0, 'cls': 'CosFunc', 'duration': 0, 'f': 1.0, 'loop': 1, 'name': 'Cos', 't_offset': 0, 'th0': 0.0}], 'loop': 1, 'name': 'Group'} >>> g.get_state(recurse=False) {'cls': 'FuncGroup', 'loop': 1, 'name': 'Group'}
- apply_state(state: Dict, clone=False)
Takes the state of the function saved with
get_state()
and applies it to this function. it also creates any children function e.g. in the case it is aFuncGroup
etc.It is called internally and should not be used directly. Use
FunctionFactoryBase.make_func()
instead.- Params
- state: dict
The dict to use to reconstruct the function as returned by
get_state()
.- clone: bool
If True will apply all the state exactly as in the original function, otherwise it doesn’t apply internal parameters listed in
_clone_props
that are not user customizable.
See
ceed.function
for an example.
- get_funcs(step_into_ref=True) Generator[Union[ceed.function.FuncBase, ceed.function.CeedFuncRef], None, None]
Generator that yields the function and all its children functions if it has any, e.g. for
FuncGroup
. It’s in DFS order.- Params
- step_into_ref: bool
If True, when it encounters a
CeedFuncRef
instance it’ll step into it and return that function and its children. Otherwise, it’ll just return theCeedFuncRef
and not step into it.
E.g.:
>>> Cos = function_factory.get('CosFunc') >>> cos = Cos() >>> Group = function_factory.get('FuncGroup') >>> g = Group() >>> g.add_func(cos) >>> [f for f in cos.get_funcs()] [<ceed.function.plugin.CosFunc at 0x4d71c78>] >>> [f for f in g.get_funcs()] [<ceed.function.FuncGroup at 0x4f85800>, <ceed.function.plugin.CosFunc at 0x4d71c78>]
- can_other_func_be_added(other_func: Union[ceed.function.CeedFuncRef, ceed.function.FuncBase]) bool
Checks whether the other function may be added to this function.
Specifically, it checks whether this function is a child of the other function, and if so we shouldn’t allow the other function to be added to this function as it’ll create a circular tree.
- Parameters
other_func – Another
FuncBase
instance.- Returns
Whether it is safe to add the other function as a child of this function.
- copy_expand_ref() ceed.function.FuncBase
Copies this function and all its sub-functions.
If any of them are
CeedFuncRef
instances, they are replaced with a copy of their original function.- Returns
A copy of this function, with all
CeedFuncRef
instances replaced by their original normal function.
- init_func_tree(root: Optional[ceed.function.FuncBase] = None) None
Initializes the function as part of the function tree so it is ready to be called to get the function values as part of the tree. It is called once for each function of the entire function tree.
- Parameters
root – The root of the function tree. If None, it’s self.
For example, for the following function structure:
GroupFunc: name: 'root' loop: 5 GroupFunc: name: 'child_a' loop: 3 ConstFunc: name: 'child' loop: 4
when the experiment is ready, after
resample_parameters()
and the stage is ready to run, ceed will callinit_func_tree()
once forroot
,child_a
, andchild
in that order. Theroot
parameter passed will be theroot
function.Then, it will call
init_func()
forroot
,child_a
, andchild
1, 5, and 15 times, respectively. Once for each time the function is started.Finally, it will call
init_loop_iteration()
forroot
,child_a
, andchild
4, 10, and 45 times, respectively. Once for each loop iteration of the function, except the first.
- init_func(t_start: Union[float, int, fractions.Fraction]) None
Initializes the function so it is ready to be called to get the function values. See also
init_func_tree()
andinit_loop_iteration()
. If overriding,super
must be called.- Parameters
t_start – The time in seconds in global time.
t_start
will be set to this value. All subsequent calls to the function with a time value will be relative to this given time.
- init_loop_iteration(t_start: Union[float, int, fractions.Fraction]) None
Initializes the function at the beginning of each loop.
It’s called internally at the start of every
loop
iteration, except the first. See alsoinit_func_tree()
andinit_func()
. If overriding,super
must be called.- Parameters
t_start – The time in seconds in global time.
t_start
will be set to this value. All subsequent calls to the function with a time value will be relative to this given time.
- finalize_func() None
Finalizes the function at the end of all its loops, when the function is done. See also
finalize_loop_iteration()
. If overriding,super
must be called.
- finalize_loop_iteration() None
Finalizes the function at the end of each loop, including the first and last. See also
finalize_func()
. If overriding,super
must be called.
- get_domain(current_iteration: bool = True) Tuple[Union[float, int, fractions.Fraction], Union[float, int, fractions.Fraction]]
Returns the current domain of the function.
- Parameters
current_iteration – If True, returns the domain for the current loop iteration (i.e. the end point is the expected end time of the current loop). Otherwise, returns the domain ending at the end of the final loop iteration. In both cases, the interval start is the time the current loop iteration started, in global time.
See
ceed.function
for details.
- get_relative_time(t: Union[float, int, fractions.Fraction]) Union[float, int, fractions.Fraction]
Converts the global time to the local function time.
At each time-step, the function is called with the global time. This function converts the time into local time, relative to the
t_start
. Also adds any function specific offset (e.g.CeedFunc.t_offset
.
- tick_loop(t: Union[float, int, fractions.Fraction]) bool
Increments
loop_count
and returns whether the function is done, which is when all theloop
iterations are done andloop_count
reachedloop
.t
is in seconds in global time and this is only called when the function time reached the end of its valid domain so that it makes sense to increment the loop. I.e. if the user called the function with a time past the current loop duration, this is called internally to increment the loop.May not be called with time values smaller than the domain. Or if the loop iterations are already done.
- Parameters
t – The time at which the last function in the tree or loop iteration of this function ended, in global time.
- Returns
True if it ticked the loop, otherwise False if we cannot tick because we hit the max and the function is done.
- resample_parameters(parent_tree: Optional[List[ceed.function.FuncBase]] = None, is_forked=False, base_loops: int = 1) None
Resamples all the function parameters that have randomness attached to it in
noisy_parameters
and updates their values.For all parameters that are randomized and
sample_each_loop
is True, the samples for all the iterations are also pre-sampled and stored innoisy_parameter_samples
.If
is_forked
, then iflock_after_forked
, then it won’t be re-sampled. This allows sampling aCeedFuncRef
function before forking it, and then, when copying and forking the source functions into individual functions we don’t resample them. Then all these individual functions share the same random parameters as the original referenced function.parent_tree
is not inclusive.base_loops
indicates the expected number of times the function is expected to loop due to the stage containing the function (if any). This is in addition toloop
of the function and its parent tree. So e.g. ifloop
is3
andbase_loops
is2
with no parents, then the function will be looped6
times, twice by the stage.E.g.:
>>> # get the classes >>> function_factory = FunctionFactoryBase() >>> register_all_functions(function_factory) >>> LinearFunc = function_factory.get('LinearFunc') >>> UniformNoise = function_factory.param_noise_factory.get_cls( ... 'UniformNoise') >>> # create function and add noise to parameters >>> f = LinearFunc(function_factory=function_factory) >>> f.set_parameter_noise('m', noise_obj=UniformNoise()) >>> f.set_parameter_noise( ... 'b' noise_obj=UniformNoise(lock_after_forked=True)) >>> # now add it to factory and create references to it >>> function_factory.add_func(f) >>> ref1 = function_factory.get_func_ref(func=f) >>> ref2 = function_factory.get_func_ref(func=f) >>> # resample the original function and fork refs into copies >>> f.resample_parameters() >>> f1 = ref1.copy_expand_ref() >>> f2 = ref2.copy_expand_ref() >>> # now resample only those that are not locked >>> f1.resample_parameters(is_forked=True) >>> f2.resample_parameters(is_forked=True) >>> # b is locked to pre-forked value and is not sampled after fork >>> f.m, f.b (0.22856343565686332, 0.3092686616300213) >>> f1.m, f1.b (0.05228392113705038, 0.3092686616300213) >>> f2.m, f2.b (0.9117196772532972, 0.3092686616300213)
- get_ref_src() ceed.function.FuncBase
Returns the function referenced by this object if we are a
CeedFuncRef
, otherwise it just returns this function.Useful to get the referenced function without having to check whether we are a
CeedFuncRef
. I.e.func.get_ref_src().name
.
- set_parameter_noise(parameter: str, noise_obj: Optional[ceed.function.param_noise.NoiseBase] = None, cls: Optional[str] = None) Optional[ceed.function.param_noise.NoiseBase]
Sets the noise of the parameter.
- Parameters
parameter – The name of the function parameter to randomize/reset.
noise_obj – A
NoiseBase
to use for the parameter. If None, we create one fromcls
.cls – If
noise_obj
is None and this is a string, we create a noise class from the noise factory of classcls
. If this is also None, if the parameter has a noise object it is removed.
- Returns
The noise object created/passed in, or None.
- ceed.function.FuncType
The type-hint type for
FuncBase
.alias of TypeVar(‘FuncType’, bound=
FuncBase
)
- ceed.function.CeedFuncOrRefInstance
Instance of either
CeedFunc
orCeedFuncRef
.alias of
Union
[FuncBase
,CeedFuncRef
]
- class ceed.function.CeedFunc(function_factory, **kwargs)
Bases:
ceed.function.FuncBase
A base class for typical Ceed functions.
See
ceed.function.plugin
for example functions that are based on this class.- t_offset
The amount of time in seconds to add the function time when computing the result. It allows some additional control over the function.
All functions that inherit from this class must add this time. E.g. the
LinearFunc
defines its function asy(t) = mt + b
with timet = (t_in - t_start + t_offset)
.The
duration
of the function is not affected by this property as it is independent of this. I.e. we check whether a time value is in the function’sget_domain()
ignoringt_offset
but we then add it to the given time before computing the function’s output.
- get_relative_time(t: Union[float, int, fractions.Fraction]) Union[float, int, fractions.Fraction]
Converts the global time to the local function time.
At each time-step, the function is called with the global time. This function converts the time into local time, relative to the
t_start
. Also adds any function specific offset (e.g.CeedFunc.t_offset
.
- is_loop_done(t: Union[float, int, fractions.Fraction]) bool
Whether the time
t
, in global time, is after the end of the current loop iteration.- Parameters
t – Time in seconds, in global time.
- Returns
Whether it’s past the end of the current loop iteration.
- get_gui_props()
Called internally by the GUI to get the properties of the function that should be displayed to the user to be customized.
- Returns
A dict that contains all properties that should be displayed. The values of the property is as follows:
If it’s the string int, float, str or it’s the python type int, float, or str then the GUI will show a editable property for this type.
If it’s None, we look at the value of the property in the instance and display accordingly (e.g. if it’s a str type property, a string property is displayed to the user).
Note
The default value determines the type. So if the default value is
0
, the type will be int and a user won’t be able to enter a float. Use e.g.0.0
in the latter case.
E.g.:
>>> Cos = function_factory.get('CosFunc') >>> cos = Cos() >>> cos.get_gui_props() {'A': None, 'duration': None, 'f': None, 'loop': None, 'name': None, 't_offset': None, 'th0': None}
- get_state(recurse=True, expand_ref=False)
Returns a dict representation of the function so that it can be reconstructed later with
apply_state()
.- Params
- recurse: bool
When the function has children functions, e.g. a
FuncGroup
, if True all the children functions’ states will also be returned, otherwise, only this function’s state is returned. See the example.- expand_ref: bool
If True, if any sub-functions (or this function itself) are
CeedFuncRef
instances, they will be expanded to contain the state of the actual underlying function. Otherwise, it is returned as being a function reference.
- Returns
A dict with all the configuration data.
E.g.:
>>> Cos = function_factory.get('CosFunc') >>> cos = Cos() >>> cos.get_state() {'A': 1.0, 'cls': 'CosFunc', 'duration': 0, 'f': 1.0, 'loop': 1, 'name': 'Cos', 't_offset': 0, 'th0': 0.0} >>> Group = function_factory.get('FuncGroup') >>> g = Group() >>> g <ceed.function.FuncGroup at 0x4f85800> >>> g.add_func(cos) >>> g.get_state(recurse=True) {'cls': 'FuncGroup', 'funcs': [{'A': 1.0, 'cls': 'CosFunc', 'duration': 0, 'f': 1.0, 'loop': 1, 'name': 'Cos', 't_offset': 0, 'th0': 0.0}], 'loop': 1, 'name': 'Group'} >>> g.get_state(recurse=False) {'cls': 'FuncGroup', 'loop': 1, 'name': 'Group'}
- class ceed.function.FuncGroup(name='Group', **kwargs)
Bases:
ceed.function.FuncBase
Function that represents a sequence of sub-functions.
See
ceed.function
for more details.When the function instance is called it goes through all its sub-functions sequentially until they are done. E.g.:
>>> Group = function_factory.get('FuncGroup') >>> Const = function_factory.get('ConstFunc') >>> g = Group() >>> g.add_func(Const(a=1, duration=5)) >>> g.add_func(Const(a=2, duration=3)) >>> g.add_func(Const(a=3, duration=2)) >>> g.init_func(0) >>> g(0) 1 >>> g(5) 2 >>> g(6) 2 >>> g(8) 3 >>> g(10) Traceback (most recent call last): g(10) File "g:\python\libs\ceed\ceed\function\__init__.py", line 934, in __call__ raise FuncDoneException FuncDoneException
- funcs: List[Union[ceed.function.FuncBase, ceed.function.CeedFuncRef]] = []
The list of children functions of this function.
- init_func_tree(root: Optional[ceed.function.FuncBase] = None) None
Initializes the function as part of the function tree so it is ready to be called to get the function values as part of the tree. It is called once for each function of the entire function tree.
- Parameters
root – The root of the function tree. If None, it’s self.
For example, for the following function structure:
GroupFunc: name: 'root' loop: 5 GroupFunc: name: 'child_a' loop: 3 ConstFunc: name: 'child' loop: 4
when the experiment is ready, after
resample_parameters()
and the stage is ready to run, ceed will callinit_func_tree()
once forroot
,child_a
, andchild
in that order. Theroot
parameter passed will be theroot
function.Then, it will call
init_func()
forroot
,child_a
, andchild
1, 5, and 15 times, respectively. Once for each time the function is started.Finally, it will call
init_loop_iteration()
forroot
,child_a
, andchild
4, 10, and 45 times, respectively. Once for each loop iteration of the function, except the first.
- init_func(t_start: Union[float, int, fractions.Fraction]) None
Initializes the function so it is ready to be called to get the function values. See also
init_func_tree()
andinit_loop_iteration()
. If overriding,super
must be called.- Parameters
t_start – The time in seconds in global time.
t_start
will be set to this value. All subsequent calls to the function with a time value will be relative to this given time.
- init_loop_iteration(t_start: Union[float, int, fractions.Fraction]) None
Initializes the function at the beginning of each loop.
It’s called internally at the start of every
loop
iteration, except the first. See alsoinit_func_tree()
andinit_func()
. If overriding,super
must be called.- Parameters
t_start – The time in seconds in global time.
t_start
will be set to this value. All subsequent calls to the function with a time value will be relative to this given time.
- replace_ref_func_with_source(func_ref: ceed.function.CeedFuncRef) Tuple[ceed.function.FuncBase, int]
Given the
CeedFuncRef
, it’ll locate it infunc
and replace it with the underlying function.The caller is responsible for returning the reference with
FunctionFactoryBase.return_func_ref()
.- Parameters
func_ref – The
CeedFuncRef
to replace infunc
.- Returns
A tuple of
(func, i)
wherefunc
is theFuncBase
that replaced the reference function. Andi
is the index infuncs
where it was found.
- add_func(func: Union[ceed.function.FuncBase, ceed.function.CeedFuncRef], after=None, index=None)
Adds
func
to this function as a sub-function infuncs
.If calling this manually, remember to check
FuncBase.can_other_func_be_added()
before adding if there’s potential for it to return False.
- remove_func(func)
Removes sub-function
func
fromfuncs
. It must exist infuncs
.- Params
- func:
FuncBase
The function instance to remove.
- func:
- get_state(recurse=True, expand_ref=False)
Returns a dict representation of the function so that it can be reconstructed later with
apply_state()
.- Params
- recurse: bool
When the function has children functions, e.g. a
FuncGroup
, if True all the children functions’ states will also be returned, otherwise, only this function’s state is returned. See the example.- expand_ref: bool
If True, if any sub-functions (or this function itself) are
CeedFuncRef
instances, they will be expanded to contain the state of the actual underlying function. Otherwise, it is returned as being a function reference.
- Returns
A dict with all the configuration data.
E.g.:
>>> Cos = function_factory.get('CosFunc') >>> cos = Cos() >>> cos.get_state() {'A': 1.0, 'cls': 'CosFunc', 'duration': 0, 'f': 1.0, 'loop': 1, 'name': 'Cos', 't_offset': 0, 'th0': 0.0} >>> Group = function_factory.get('FuncGroup') >>> g = Group() >>> g <ceed.function.FuncGroup at 0x4f85800> >>> g.add_func(cos) >>> g.get_state(recurse=True) {'cls': 'FuncGroup', 'funcs': [{'A': 1.0, 'cls': 'CosFunc', 'duration': 0, 'f': 1.0, 'loop': 1, 'name': 'Cos', 't_offset': 0, 'th0': 0.0}], 'loop': 1, 'name': 'Group'} >>> g.get_state(recurse=False) {'cls': 'FuncGroup', 'loop': 1, 'name': 'Group'}
- apply_state(state={}, clone=False)
Takes the state of the function saved with
get_state()
and applies it to this function. it also creates any children function e.g. in the case it is aFuncGroup
etc.It is called internally and should not be used directly. Use
FunctionFactoryBase.make_func()
instead.- Params
- state: dict
The dict to use to reconstruct the function as returned by
get_state()
.- clone: bool
If True will apply all the state exactly as in the original function, otherwise it doesn’t apply internal parameters listed in
_clone_props
that are not user customizable.
See
ceed.function
for an example.
- get_funcs(step_into_ref=True) Generator[Union[ceed.function.FuncBase, ceed.function.CeedFuncRef], None, None]
Generator that yields the function and all its children functions if it has any, e.g. for
FuncGroup
. It’s in DFS order.- Params
- step_into_ref: bool
If True, when it encounters a
CeedFuncRef
instance it’ll step into it and return that function and its children. Otherwise, it’ll just return theCeedFuncRef
and not step into it.
E.g.:
>>> Cos = function_factory.get('CosFunc') >>> cos = Cos() >>> Group = function_factory.get('FuncGroup') >>> g = Group() >>> g.add_func(cos) >>> [f for f in cos.get_funcs()] [<ceed.function.plugin.CosFunc at 0x4d71c78>] >>> [f for f in g.get_funcs()] [<ceed.function.FuncGroup at 0x4f85800>, <ceed.function.plugin.CosFunc at 0x4d71c78>]
- resample_parameters(parent_tree: Optional[List[ceed.function.FuncBase]] = None, is_forked=False, base_loops: int = 1) None
Resamples all the function parameters that have randomness attached to it in
noisy_parameters
and updates their values.For all parameters that are randomized and
sample_each_loop
is True, the samples for all the iterations are also pre-sampled and stored innoisy_parameter_samples
.If
is_forked
, then iflock_after_forked
, then it won’t be re-sampled. This allows sampling aCeedFuncRef
function before forking it, and then, when copying and forking the source functions into individual functions we don’t resample them. Then all these individual functions share the same random parameters as the original referenced function.parent_tree
is not inclusive.base_loops
indicates the expected number of times the function is expected to loop due to the stage containing the function (if any). This is in addition toloop
of the function and its parent tree. So e.g. ifloop
is3
andbase_loops
is2
with no parents, then the function will be looped6
times, twice by the stage.E.g.:
>>> # get the classes >>> function_factory = FunctionFactoryBase() >>> register_all_functions(function_factory) >>> LinearFunc = function_factory.get('LinearFunc') >>> UniformNoise = function_factory.param_noise_factory.get_cls( ... 'UniformNoise') >>> # create function and add noise to parameters >>> f = LinearFunc(function_factory=function_factory) >>> f.set_parameter_noise('m', noise_obj=UniformNoise()) >>> f.set_parameter_noise( ... 'b' noise_obj=UniformNoise(lock_after_forked=True)) >>> # now add it to factory and create references to it >>> function_factory.add_func(f) >>> ref1 = function_factory.get_func_ref(func=f) >>> ref2 = function_factory.get_func_ref(func=f) >>> # resample the original function and fork refs into copies >>> f.resample_parameters() >>> f1 = ref1.copy_expand_ref() >>> f2 = ref2.copy_expand_ref() >>> # now resample only those that are not locked >>> f1.resample_parameters(is_forked=True) >>> f2.resample_parameters(is_forked=True) >>> # b is locked to pre-forked value and is not sampled after fork >>> f.m, f.b (0.22856343565686332, 0.3092686616300213) >>> f1.m, f1.b (0.05228392113705038, 0.3092686616300213) >>> f2.m, f2.b (0.9117196772532972, 0.3092686616300213)
- set_ceed_id(min_available: int) int
Sets the ID of this and any sub objects, each to a number equal or greater than
min_available
and returns the next minimum number available to be used.See
event_data
for more details.- Parameters
min_available – The minimum number available to be used for the ID so it is unique.
- Returns
The next minimum available number that can be used. Any number larger or equal to it is free to be used.
- class ceed.function.CeedFuncRef(function_factory, func=None)
Bases:
object
A function that refers to another function.
This is never manually created, but rather returned by
FunctionFactoryBase.get_func_ref()
. SeeFunctionFactoryBase.get_func_ref()
andceed.function
for details.- display = None
Same as
FuncBase.display
.
- parent_func = None
Same as
FuncBase.parent_func
.
- function_factory = None
Same as
FuncBase.function_factory
.
- get_ref_src() ceed.function.FuncBase
See
CeedStage.get_ref_src()
.
- ceed.function.register_all_functions(function_factory: ceed.function.FunctionFactoryBase)
Registers all the internal plugins and built-in functions and function distributions with the
FunctionFactoryBase
andFunctionFactoryBase.param_noise_factory
instance, respectively.It gets and registers all the plugins functions and function distributions under
ceed/function/plugin
usingget_plugin_functions()
. See that function for how to make your plugin functions and distributions discoverable.- Parameters
function_factory – a
FunctionFactoryBase
instance.
- ceed.function.register_external_functions(function_factory: ceed.function.FunctionFactoryBase, package: str)
Registers all the plugin functions and function distributions in the package with the
FunctionFactoryBase
andFunctionFactoryBase.param_noise_factory
instance, respectively.See
get_plugin_functions()
for how to make your plugin functions and distributions discoverable.Plugin source code files are copied to the data file when a data file is created. However, it doesn’t copy all files (i.e. it ignores non-python files) so it should be independently tracked for each experiment.
- Parameters
function_factory – A
FunctionFactoryBase
instance.package – The name of the package containing the plugins.