Interfaces#
FINAM is primarily a collection of interfaces that allows different models and other components to communicate.
For all interfaces, FINAM also provides abstract or concrete implementations to speed up component development.
Class diagram#
The following figure shows a diagram of FINAM’s core interfaces and classes. Arrows indicate inheritance. The properties and methods are those typically used or implemented by developers.
Figure 1: FINAM interfaces class diagram.
Components#
Components represent linkable entities like models.
There are two interfaces for components: IComponent
and ITimeComponent
.
IComponent
#
IComponent
serves for pull-based components without an explicit time step.
It provides all the basic methods required for component communication and execution.
IComponent.initialize()
sets up the componentIComponent.connect()
pushes initial values to output slotsIComponent.validate()
checks the component for validityIComponent.update()
makes a calculation stepIComponent.finalize()
shuts down the component
These methods are called by the scheduler in the given order (and repeatedly for IComponent.update()
),
each for all components, before proceeding to the next method.
To access a component’s input and output slots, there are the properties:
IComponent.inputs
returns a dict-like ofIInput
slots by nameIComponent.outputs
returns a dict-like ofIOutput
slots by nameIComponent.status
returns the component’s currentComponentStatus
(CREATED
,INITIALIZED
, …)
The abstract class Component
provides a basic implementation for IComponent
.
Classes extending Component
must override methods named of the first block, with underscore, like Component._initialize()
.
Component.inputs
, Component.outputs
and Component.status
are provided as basic implementations.
ITimeComponent
#
ITimeComponent
extends IComponent
and serves for components with explicit time step, like simulation models.
In addition to IComponent
, it adds one property:
ITimeComponent.time
should report the component’s current time, as a datetime object
As ITimeComponent
extends IComponent
, only ITimeComponent
needs to be implemented.
The abstract class TimeComponent
provides a basic implementation for ITimeComponent
.
It is basically identical to Component
, and in addition provides a basic implementation for TimeComponent.time
.
Inputs and Outputs#
Interfaces IInput
and IOutput
define coupling slots.
The classes Input
and Output
are provided as implementations for IInput
and IOutput
, respectively.
They should suffice most use cases.
IInput
#
IInput
represents a data exchange input slot, with the following methods:
IInput.source()
= … sets anIOutput
as source for this inputIInput.source()
returns theIOutput
that is the source for this inputIInput.source_updated()
informs the input that the connectedIOutput
has new data availableIInput.pull_data()
retrieves and returns the connectedIOutput
’s data
Components usually only use IInput.pull_data()
in their Component._update()
method.
All other methods are only used under the hood.
All these methods are implemented in Input
, so there is normally no need to write an own implementation for IInput
.
Another implementation is provided by CallbackInput
, for use in push-based components without a time step.
They can connect to IInput.source_updated()
by providing a callback function.
Other classes derived from Input
can overwrite the method Input.source_updated()
.
IOutput
#
IOutput
represents a data exchange output slot, with the following methods:
IOutput.add_target()
adds anIInput
as target for this outputIOutput.get_targets()
returns the list ofIInput
targets of this outputIOutput.push_data()
is used to populate the output with data after an updateIOutput.notify_targets()
informs coupledIInput
that new data is availableIOutput.get_data()
returns the data in this outputIOutput.chain()
connects this output to anIInput
(or an adapter)
Components usually only use IOutput.push_data()
in their Component._update()
method.
During coupling setups, IOutput.chain()
or it’s synonym operator >>
are used.
All other methods are only used under the hood.
All these methods are implemented in Output
, so there is normally no need to write an own implementation for IOutput
.
Other classes derived from Output
can overwrite the method Output.get_data()
.
Adapters#
Adapters serve for data transformations between outputs and inputs of different components.
IAdapter
#
The interface IAdapter
serves for implementing adapters.
It simply combines IInput
and IOutput
, so it is both at the same time.
IAdapter
provides all the methods of IInput
and IOutput
, but most of them are only used under the hood.
Classes implementing IAdapter
can extend Adapter
, which provides default implementations for IInput
and IOutput
methods.
Time-independent/one-shot adapters need to override Adapter._get_data()
.
Inside this method, they get their input via self.pull_data(time), transform it, and return the result.
Time-aware adapters, e.g. for temporal interpolation, usually override Adapter._source_updated()
and Adapter._get_data()
.
In Adapter._source_updated()
, incoming data is collected (and potentially aggregated), while in Adapter._get_data()
the result is returned.
For details, see chapter Writing adapters.
NoBranchAdapter
#
Some time-aware adapters may not allow for branching in the subsequent adapter chain.
I.e. they do not support multiple target components.
For these cases, NoBranchAdapter
is provided as a marker interface without any methods.