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.

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 when register() 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 is register().

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. See ceed.function for details.

One of name or func must be specified. The function being referenced by func 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 (unless instance is provided, in which case that is used) of the class that is added to funcs_inst and funcs_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.

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 with register().

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 and funcs_inst, which makes it available in the GUI.

See ceed.function for details.

If the FuncBase.name already exists in funcs_inst, FuncBase.name will be set to a unique name based on its original name. Once added until removed, anytime the function’s FuncBase.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.

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().

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 in user_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 the FunctionFactoryBase, 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 function t_start. So, if it starts at 0 seconds and duration 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. if duration_min is 5 and loop is 2, duration_min_total would typically be 10.

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, then parent_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() and FunctionFactoryBase.return_func_ref().

display = None

The widget that visualize this function, if any.

timebase_numerator: Union[float, int]

The numerator of the timebase. See timebase.

timebase_denominator: Union[float, int]

The denominator of the timebase. See timebase.

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 and timebase_denominator must be set individually. To use, call get_timebase(), rather than using timebase 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 and timebase_denominator is 1 which makes timebase 0 indicating that the timebase used is given by the parent function or is 1. When timebase is not 0 this timebase is used instead.

See ceed.function and get_timebase() for more details.

Note

This property is dispatched whenever the value returned by get_timebase() would change, even if timebase 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. See ceed.function for more details.

Don’t set directly, it is set in init_func() and init_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 from get_domain(), or the current time value if that is negative.

It is updated before finalize_loop_iteration(), and finalize_func() are called.

loop_count: int = 0

The current loop iteration.

This goes from zero (set by init_func() / init_loop_iteration()) to loop. The function is done when it is exactly loop, having looped times. When finalize_loop_iteration() and finalize_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, if sample_each_loop, then during the resample_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() and init_loop_iteration(). The total number of samples includes all outside loops, see sample_each_loop and init_func_tree().

get_timebase() Union[float, fractions.Fraction]

Returns the function’s timebase.

If timebase_numerator and timebase is 0, it returns the timebase of its parent_func with get_timebase() if it has a parent. If it doesn’t have a parent, it return 1. Otherwise, it returns timebase.

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, if use_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 a FuncGroup 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 the CeedFuncRef 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 call init_func_tree() once for root, child_a, and child in that order. The root parameter passed will be the root function.

Then, it will call init_func() for root, child_a, and child 1, 5, and 15 times, respectively. Once for each time the function is started.

Finally, it will call init_loop_iteration() for root, child_a, and child 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() and init_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 also init_func_tree() and init_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 the loop iterations are done and loop_count reached loop.

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 in noisy_parameter_samples.

If is_forked, then if lock_after_forked, then it won’t be re-sampled. This allows sampling a CeedFuncRef 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 to loop of the function and its parent tree. So e.g. if loop is 3 and base_loops is 2 with no parents, then the function will be looped 6 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 from cls.

  • cls – If noise_obj is None and this is a string, we create a noise class from the noise factory of class cls. 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 or CeedFuncRef.

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 as y(t) = mt + b with time t = (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’s get_domain() ignoring t_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 call init_func_tree() once for root, child_a, and child in that order. The root parameter passed will be the root function.

Then, it will call init_func() for root, child_a, and child 1, 5, and 15 times, respectively. Once for each time the function is started.

Finally, it will call init_loop_iteration() for root, child_a, and child 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() and init_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 also init_func_tree() and init_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 in func 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 in func.

Returns

A tuple of (func, i) where func is the FuncBase that replaced the reference function. And i is the index in funcs 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 in funcs.

If calling this manually, remember to check FuncBase.can_other_func_be_added() before adding if there’s potential for it to return False.

Params
func: FuncBase

The function instance to add.

after: FuncBase, defaults to None.

The function in funcs after which to add this function if specified.

index: int, defaults to None.

The index where to insert the function if specified.

remove_func(func)

Removes sub-function func from funcs. It must exist in funcs.

Params
func: FuncBase

The function instance to remove.

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 a FuncGroup 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 the CeedFuncRef 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 in noisy_parameter_samples.

If is_forked, then if lock_after_forked, then it won’t be re-sampled. This allows sampling a CeedFuncRef 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 to loop of the function and its parent tree. So e.g. if loop is 3 and base_loops is 2 with no parents, then the function will be looped 6 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(). See FunctionFactoryBase.get_func_ref() and ceed.function for details.

display = None

Same as FuncBase.display.

parent_func = None

Same as FuncBase.parent_func.

func = None

The original FuncBase this reference function is referring to.

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 and FunctionFactoryBase.param_noise_factory instance, respectively.

It gets and registers all the plugins functions and function distributions under ceed/function/plugin using get_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 and FunctionFactoryBase.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.