Refactor OperatorImpl for backend/export
Refactor OperatorImpl for backend/export.
- 
Changed Registrableclass template to be more general (usingFuncinstead ofstd::function<Func>) - 
Fixed a bug in Log::log()that did not handle well formatted string for file log. - 
Moved producer consumer model in a separate class ProdConso, that is returned by theprodConso()method inOperatorImpl. - 
Added new ImplSpecstruct, that allows to specify an implementation (I/O type, format, dims...). - 
Added new Implstruct, that contains the implementation details (producer consumer model, forward and backward kernels). - 
Added getRequiredSpec(),getBestMatch(),getAdaptation()andgetBestAdaptation()toOperatorImpl; - 
Added adaptToBackend()recipe ; - 
Proposal for fixing #153 (closed);  - 
Add Operator::getAvailableBackend();  - 
Fixed bug with Identity implementation and Scheduler. To work correctly, the output tensor cannot be the input, as this implies that values are the same even before next forward()call. - 
Allow to set a list of backends by order of preference;  - 
Adapt operator implementation registrars for CPU backend;  - 
Adapt operator implementation registrars for CUDA backend.  
TODO
- 
Double check with @cmoineau that it fits nicely with export.  
TODO maybe later
- Add Operator registry that lists all available Operators 
➡ this has some issues: it needs an additional registrar for Operators that conflicts with the operators backend registrars, it is somewhat redundant with registries in ONNX import/export, it requires a value (with current map-based registry system) which is unclear...; - Add a base 
OperatorWithImplclass with CRTP that factor backend methods?➡ may be nice to factorize some code that is currently repeated for each operator, without requiring any API change. Out of the scope of this MR; 
Usage as export backend
# Export class for "arm_cortexm". The same class needs to be registered for
# all operators supported by the export.
class OperatorExport_arm_cortexm(aidge_core.OperatorImpl):
    # Registry for all "arm_cortexm" operator exports
    registry = dict()
    def __init__(self, operator):
        super(OperatorExport_arm_cortexm, self).__init__(operator, "export_arm_cortexm")
    # Override the virtual OperatorImpl method, in order to provide available 
    # implementation specifications
    def get_available_impl_specs(self):
        if self.get_operator().type() in self.registry:
            return list(self.registry[self.get_operator().type()].keys())
        else:
            return []
    # Decorator to register kernels for this export
    @staticmethod
    def register(type, spec):
        def decorator(operator):
            def wrapper(*args, **kwargs):
                return operator(*args, **kwargs)
            if (type not in OperatorExport_arm_cortexm.registry):
                OperatorExport_arm_cortexm.registry[type] = dict()
            OperatorExport_arm_cortexm.registry[type][spec] = operator
            return wrapper
        return decorator
# Register kernels: for a given type and a given implementation specification
# TODO: the FCOpKernel could inherit from a generic base class for this export or all export types...
@OperatorExport_arm_cortexm.register(aidge_core.FCOp.Type, aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.int16)))
class FCOpKernel():
    def generate():
        print("Gen code for FCOp")
# TODO: register other kernels...
# Register all operator types to the same export class
aidge_core.register_FCOp("export_arm_cortexm", OperatorExport_arm_cortexm)
aidge_core.register_ReLUOp("export_arm_cortexm", OperatorExport_arm_cortexm)
# TODO: register other operators... 
# Use the CPU backend for Tensor in the "export_arm_cortexm" backend
aidge_core.register_Tensor(["export_arm_cortexm", aidge_core.dtype.float32],
                           aidge_core.get_key_value_Tensor(["cpu", aidge_core.dtype.float32]))
Edited  by Cyril Moineau