Simple Gearbox Tutorial

Simple Gearbox Tutorial

1 - Introduction

The objective of this tutorial is to understand how to optimize the fuel consumption of a car which goes through a WLTP cycle by finding the best gear ratio configuration for its gearbox. So in order to do this there are some specifications that must be considered, such as the following ones:

  • Gear ratios must vary only inside an imposed range;
  • Gear selection must satisfy imposed speed ranges;

2 - Build the bot in 5 steps

Before beginning, you have access to the tutorial9 file in the tutorials folder cloned as explained in the first tutorial. We will apply the DessIA method to find the best solution to our problem.

2.0 - Formatting model engineering data

Before we dive right into the engineering system steps, this first step is very important to mention because it will format most part of the data needed for the following calculations.

For this first step, we have created two classes, the EfficiencyMap and WLTPCycle. The first one is responsible for calculating the Break Specific Fuel Consumption (BSFC) as well as the Efficiency values for the engine, this will help us later determine the fuel efficiency at each given configuration. To do so, the class is initialized with some engine parameters such as a list of engine speeds and another one of engine torques, a matrix of fuel mass flow rate in which each value corresponds to one configuration of the engine's speed and torque, in addition to the lower heating value of the fuel used followed by a name. The BSFC and efficiency were calculated using the respective following formulas:

Break Specific Fuel Consumption:

BSFC=1ωτ{\normalsize{BSFC = \frac{\normalsize 1} {\normalsize {\mathbf{\omega}} \cdot \normalsize \tau}}}


η=1BSFCQ_hv{\normalsize{\mathbf{\eta} = \frac{\normalsize 1} {\normalsize BSFC \cdot \normalsize Q \text{\textunderscore} hv}}}

class EfficiencyMap(DessiaObject):
    _standalone_in_db = True
    Build the engine map and then determine its efficiency
    def __init__(self, engine_speeds: List[float], engine_torques: List[float], mass_flow_rate: List[Tuple[float, float]],
                 fuel_hv: float, name: str = ''):
        self.engine_speeds = engine_speeds  # in rad/s
        self.engine_torques = engine_torques  # in Nm
        self.mass_flow_rate = mass_flow_rate
        self.fuel_hv = fuel_hv  # fuel lower heating value in J/kg
        DessiaObject.__init__(self, name=name)
        BSFC = []
        for i, engine_speed in enumerate(self.engine_speeds):
            list_bsfc = []
            for j, engine_torque in enumerate(self.engine_torques):
                bsfc = self.mass_flow_rate[i][j]/(engine_speed*engine_torque)  # in kg/J
        self.bsfc = BSFC
        efficiencies = []
        for list_bsfc in BSFC:
            list_efficiencies = []
            for bsfc in list_bsfc:
                efficiency = 1/(bsfc*self.fuel_hv)
        self.efficiencies = efficiencies

The second one, WLTPCycle is responsible for finishing the calculations for the cycle torques in the wheel of the car, and for that it receives as parameters the cycle speeds, the mass of the car, the tyre radius and the time interval between each cycle point, and as always, a name. In the image shown below we can see how the torque in the wheel is calculated, first we find the car acceleration between each two velocity points and then with the car mass and the tyre radius, we can then find the torque.

class WLTPCycle(DessiaObject):
    _standalone_in_db = True
    WLTP cycle paremeters and wheel torque calculations
    def __init__(self, cycle_speeds: List[float], car_mass: float, tire_radius: float, dt: float = 1, name: str = ''):
        self.cycle_speeds = cycle_speeds
        self.car_mass = car_mass
        self.tire_radius = tire_radius
        self.dt = dt
        accelerations = []
        for i in range(len(self.cycle_speeds[:-1])):
            acceleration = (self.cycle_speeds[i + 1] - self.cycle_speeds[i]) / dt  # acceleration in m/s^2
            if acceleration < 0:
                acceleration *= -1
        for acceleration in accelerations:
            torque = acceleration*car_mass*tire_radius/2  # torque in Nm
        self.cycle_torques = cycle_torques

2.1 - Describe your engineering system

Our engineering system is basically composed of two components, an engine and a gearbox. So as you have seen in previous tutorials, there is a class dedicated for each one of these components.

  • The Engine class is initialized following 3 main requirements, it receives an EfficiencyMap object with all the important information concerning its efficiency parameters along with a speed and torque set point parameters for the engine's idling state. These last two parameters mentioned will be important while determining gears when the car is actually stopped.
class Engine(DessiaObject):
    _standalone_in_db = True
    def __init__(self, efficiency_map: EfficiencyMap, setpoint_speed: float, setpoint_torque: float, name:str=''):
        self.efficiency_map = efficiency_map
        self.setpoint_speed = setpoint_speed
        self.setpoint_torque = setpoint_torque
  • The GearBox class receives as inputs at least two parameters, an engine object and a list of speed ranges where each given range corresponds to one gear configuration, that is the first range stands for the first gear, and the last one for the last gear. It is important to note that one range can intersect with the next one.
class GearBox(DessiaObject):
    _standalone_in_db = True
    def __init__(self, engine: Engine,
                 speed_ranges: List[Tuple[float, float]] = None,
                 name: str = ''):
        self.engine = engine
        self.speed_ranges = speed_ranges
        self.ratios = None
        DessiaObject.__init__(self, name=name)

2.2 - Define engineering simulations needed

Two methods were created in the Engine class, efficiency and consumption_efficiency. These two methods receive two engine parameters: a speed and a torque. Using these entry parameters and the 2D interpolation method from scipy, they calculate the engine efficiency or the engine fuel efficiency from the EfficiencyMap object and the corresponding value is returned from the respective method. These methods will be important later when optimizing our gearbox.

class Engine(DessiaObject):
    _standalone_in_db = True
    def __init__(self, efficiency_map: EfficiencyMap, setpoint_speed: float, setpoint_torque: float, name:str=''):
        self.efficiency_map = efficiency_map
        self.setpoint_speed = setpoint_speed
        self.setpoint_torque = setpoint_torque
    def efficiency(self, speed:float, torque:float):
        interpolate = interp2d(self.efficiency_map.engine_torques, self.efficiency_map.engine_speeds, self.efficiency_map.efficiencies)
        interpolate_efficiency = interpolate(torque, speed)
        return interpolate_efficiency[0]
    def consumption_efficiency(self, speed:float, torque: float):
        interpolate = interp2d(self.efficiency_map.engine_torques, self.efficiency_map.engine_speeds, self.efficiency_map.bsfc)
        interpolate_consumption_efficiency = interpolate(torque, speed)
        return float(interpolate_consumption_efficiency[0])

2.3 - Create an Optimizer

The next step is to create an optimizer to our tutorial. If you have gone through all the previous tutorials you have realized that we use the minimize method from scipy.optimize quite often and here we will use it once again. To do so, a class called GearBoxOptimizer is first created, where all methods needed to achieve the final purpose are created. The class optimizer is initialized with a few parameters such as a gearbox and a WLTP Cycle object which were created just above, along with a first_gear_ratio_min_max parameter which stands for the range of possible ratio values for the first gear. The last entry parameter needed is a list of coefficient ranges used to determine the relation between the actual gear and the previous one. The coeff_between_gear is primarily set as None, but if specified differently it should have (number of gears - 1) coefficient ranges.

class GearBoxOptimizer(DessiaObject):
    _standalone_in_db = True
    def __init__(self, gearbox: GearBox, wltp_cycle: WLTPCycle, firstgear_ratio_min_max: Tuple[float,float], coeff_between_gears: List[Tuple[float, float]] = None, name: str = ''):
        self.gearbox = gearbox
        self.wltp_cycle = wltp_cycle
        self.coeff_between_gears = coeff_between_gears
        self.firstgear_ratio_min_max = firstgear_ratio_min_max

2.3.1 - Defining the x vector

The minimize method used needs a x vector that specifies the parameters to be optimized. For this particular tutorial the x vector is composed of the first element being the ratio of the first gear of the gearbox and the following ones being the coefficients between each two gears. The number of parameters in the x vector is as big as the number of gears of our gearbox.

2.3.2 - Defining Bounds for Optimization

For each one of the parameters in the x vector, we must define a bound, that is, a minimum and maximum value possible fo this parameter. For our first bound corresponding to the first gear ratio, we use the first_gear_ratio_min_max parameter given by the user, and the for the bounds of coefficients between each two gears, we use the coefficient range of the coeff_between_gears parameter. This last parameter if not specified by the user, all ranges are set with the same pre defined value.

class GearBoxOptimizer(DessiaObject):
    _standalone_in_db = True
    def __init__(self, gearbox: GearBox, wltp_cycle: WLTPCycle, first_gear_ratio_min_max: Tuple[float,float], coeff_between_gears: List[Tuple[float, float]] = None, name: str = ''):
        self.gearbox = gearbox
        self.wltp_cycle = wltp_cycle
        self.coeff_between_gears = coeff_between_gears
        self.first_gear_ratio_min_max = first_gear_ratio_min_max
        if self.coeff_between_gears == None:
            self.coeff_between_gears = (len(self.gearbox.speed_ranges)-1)*[[0.5,1]]
        for i in range(len(self.gearbox.speed_ranges)):
            if i == 0:
                bounds.append([self.coeff_between_gears[i-1][0], self.coeff_between_gears[i-1][1]])
        self.bounds = bounds

2.3.3 - Initial Conditions

The initial condition for this tutorial is created using exactly the same reasoning as you have seen in the tutorial 2 and 3.

def cond_init(self):
        x0 = []
        for interval in self.bounds:
        return x0

2.3.4 Create an update method

Before we build the objective function it is important to create an update method which receives the vector x and with it, updates the ratios of the gearbox. These parameters will change during the optimization. When a method from the GearBox class is called, you have to make sure your object is up-to-date, ie. is updated with the newest x vector. This method is also used to verify all gear changes as well as other important parameters such as the fuel consumption and engine speeds which are stored in self parameters and some are used in the objective function later.

Methods in the Class GearBox

Update Method

To update the ratios an updated method is also created in the GearBox class, which also receives the vector x and what is does is that it takes the first ratio and with all the coefficients it updates all the gearbox ratios.

def update(self, x):
        ratios = []
        for i in range(len(x)):
            if i == 0:
                ratio = float(x[0])
                ratio *= float(x[i])
        self.ratios = ratios

gear_choice Method

The second method created in GearBox is gear_choice, which receives two parameters: the wltp speed and torque at a time t of the cycle. This method choses the gears that the gearbox is supposed to be by using the speed_ranges parameter from the GearBox object, and with this information it then calculates the engine speed and torque using the corresponding ratio which are used to find the fuel consumption at this configuration using the "consumptionefficiency" method from the Engine class presented above. Because the ranges from the _speed_ranges parameter may intersect one another, all possible configurations are saved in lists and at the end, the configuration that gives the lowest consumption efficiency is chosen. This method returns the gear choice along with other important parameters for the final result's post-treatment analyses.

def gear_choice(self, cycle_speed, cycle_torque):
        fuel_consumption_gpkwh = 0
        engine_speed = 0
        engine_torque = 0
        gear = 0
        ratio = 0
        if cycle_speed == 0:
            engine_speed = self.engine.setpoint_speed
            engine_torque = self.engine.setpoint_torque
            fuel_consumption_gpkwh = self.engine.consumption_efficiency(engine_speed, engine_torque)
            gear = 0
            ratio = 0
            list_ratio = []
            list_gear = []
            list_fuel_c = []
            list_torque = []
            list_speed = []
            for i, speed_range in enumerate(self.speed_ranges):
                if cycle_speed  >= speed_range[0] and cycle_speed  < speed_range[1]:
                    ratio = self.ratios[i]
                    engine_speed = cycle_speed * ratio
                    engine_torque = cycle_torque / ratio
                    fuel_consumption_gpkwh = self.engine.consumption_efficiency(engine_speed, engine_torque)
                    gear = i + 1
            fuel_consumption_gpkwh = min(list_fuel_c)
            ratio = list_ratio[list_fuel_c.index(fuel_consumption_gpkwh)]
            gear = list_gear[list_fuel_c.index(fuel_consumption_gpkwh)]
            engine_speed = list_speed[list_fuel_c.index(fuel_consumption_gpkwh)]
            engine_torque = list_torque[list_fuel_c.index(fuel_consumption_gpkwh)]
        return [ gear, ratio, fuel_consumption_gpkwh, engine_speed, engine_torque]

So finally the update method from the GearBoxOptimizer class is shown is the scheme below.

 def update(self, x):
        fuel_consumptions = []
        gears = []
        ratios = []
        engine_speeds = []
        engine_torques = []
        for (cycle_speed, cycle_torque) in zip(self.wltp_cycle.cycle_speeds, self.wltp_cycle.cycle_torques):
            cycle_speed = cycle_speed*2/self.wltp_cycle.tire_radius
            gear_choice = self.gearbox.gear_choice(cycle_speed, cycle_torque)
        self.engine_speeds = engine_speeds
        self.engine_torques = engine_torques
        self.gears = gears
        self.ratios = ratios
        self.fuel_consumptions = fuel_consumptions

2.3.5 - Create an objective function

To build the objective function it is important to remember what it is that is being optimized and in this tutorial we aim to minimize the car fuel consumption through a wltp cycle by optimizing the gearbox ratios. Therefore the objective function takes into account the average fuel consumption of the cycle calculated with the gear ratios updated with the vector x as explained above. In addition, a condition was created to check if the engine torque exceeds the maximum torque permitted. If this happens, a big positive number is added to the objective function, which will allow the elimination of this solution during the validation step.

def objective(self, x):
        objective_function = 0
        objective_function += mean(self.fuel_consumptions)
        for engine_torque in self.engine_torques:
            if engine_torque > max(self.gearbox.engine.efficiency_map.engine_torques):
                objective_function += 1000
        return objective_function

2.3.6 - Create an optimize method

The next step is to finally build an optimize method inside the class GearBoxOptimizer which calls the minimize method from scipy inside a while loop so it can try to minimize the objective function several times and at each time use a different initial condition that may help find better solutions. To validate a solution, two dontitions are used: it is verified that there was no issue during the minimization process with .success method and it is also verified that the maximum torque is valid by comparing the minimize result value, which is actually the cycle's average fuel consumption, with the maximum fuel consumption value available in the consumption_efficiency parameter from the EfficiencyMap object. If the solution is validated, the current gearbox along with all important parameters needed for post-treatment analyses are saved into another object called GearBoxResults which will be explained later. This method returns all validated solutions.

def optimize(self, max_loops:int = 1000):
        valid = True
        count = 0
        list_gearbox_results = []
        while valid and count < max_loops:
            x0 = self.cond_init()
            sol = minimize(self.objective, x0, bounds = self.bounds)
            count += 1
            if < max([j for i in self.gearbox.engine.efficiency_map.bsfc for j in i]) and sol.success:
                self.average_fuel_consumption = float(
                gearbox = self.gearbox.copy()
                gearbox.ratios = self.gearbox.ratios
                gearbox_results = GearBoxResults(gearbox, self.wltp_cycle, self.engine_speeds,  self.engine_torques,  self.fuel_consumptions,  self.gears, self.ratios, self.average_fuel_consumption)
        return list_gearbox_results

2.3.7 - Create a Class GearBoxResults to store the results of each solution

The objective of creating the GearBoxResults class is to have all the result parameters in one place and some of them are not necessarily an object's parameter, like for example the engine's speeds over the wltp cycle. Furthermore, a plot_data method is built to make it easier to visualize some particular parameters of each different solution.

class GearBoxResults(DessiaObject):
    _standalone_in_db = True
    def __init__(self, gearbox: GearBox, wltp_cycle: WLTPCycle, engine_speeds: List[float], engine_torques: List[float], fuel_consumptions:List[float],
                 gears: List[float], ratios:List[float], average_fuel_consumption:float, name: str = ''):
        self.gearbox = gearbox
        self.wltp_cycle = wltp_cycle
        self.engine_speeds =engine_speeds
        self.engine_torques = engine_torques
        self.fuel_consumptions = fuel_consumptions
        self.gears = gears
        self.ratios = ratios
        self.average_fuel_consumption = average_fuel_consumption
        self.average_engine_speed = mean(self.engine_speeds)
        self.average_engine_torque = mean(self.engine_torques)
        self.ratio_min = min(self.gearbox.ratios)
        self.ratio_max = max(self.gearbox.ratios)
        self.average_ratio = mean(self.gearbox.ratios)
    def _to_plot_point(self):
        points = []
        cycle_time = [i+1 for i in range(len(self.wltp_cycle.cycle_speeds[:-1]))]
        for i, (car_speed, wheel_torque, engine_speed, engine_torque, fuel_consumption, time, gear)\
                in enumerate(zip(self.wltp_cycle.cycle_speeds[:-1], self.wltp_cycle.cycle_torques, self.engine_speeds,
                                 self.engine_torques, self.fuel_consumptions, cycle_time, self.gears_ratios[0])):
            data = {'c_s': car_speed, 'whl_t': wheel_torque, 'w_e': engine_speed, 't_e': engine_torque,
                    'f_cons (g/kWh)': fuel_consumption*3.6e9, 'time': time, 'gear': gear}
            points.append(plot_data.Sample(values=data, reference_path=f"#/data/{i}"))
        return points
    @plot_data_view(selector="MultiPlot 1")
    def plot_data(self):
        points = self._to_plot_point()
        color_fill = LIGHTBLUE
        color_stroke = GREY
        point_style = plot_data.PointStyle(color_fill=color_fill, color_stroke=color_stroke)
        axis = plot_data.Axis()
        attributes = ['c_s', 'f_cons (g/kWh)']
        tooltip = plot_data.Tooltip(attributes=attributes)
        objects = [plot_data.Scatter(tooltip=tooltip, x_variable=attributes[0],
                                     elements=points, axis=axis)]
        attributes = ['whl_t', 'f_cons (g/kWh)']
        tooltip = plot_data.Tooltip(attributes=attributes)
                                         elements=points, axis=axis))
        attributes = ['w_e','t_e','f_cons (g/kWh)']
        edge_style = plot_data.EdgeStyle()
        rgbs = [[192, 11, 11], [14, 192, 11], [11, 11, 192]]
        multiplot = plot_data.MultiplePlots(elements=points, plots=objects,
        return multiplot
    @plot_data_view(selector="MultiPlot 2")
    def plot_data_2(self):
        cycle_time = [i+1 for i in range(len(self.wltp_cycle.cycle_speeds[:-1]))]
        points = self._to_plot_point()
        list_colors = [BLUE, BROWN, GREEN, BLACK]
        graphs2d = []
        point_style = plot_data.PointStyle(color_fill=RED,
                                           color_stroke=BLACK, size=1)
        tooltip = plot_data.Tooltip(attributes=['sec', 'gear'])
        edge_style = plot_data.EdgeStyle(line_width=0.5,
        elements = []
        for i, gear in enumerate(self.gears_ratios[0]):
            elements.append({'sec': cycle_time[i], 'gear': gear})
        dataset = plot_data.Dataset(elements=elements, edge_style=edge_style,
                                    tooltip=tooltip, point_style=point_style)
        tooltip = plot_data.Tooltip(attributes=['sec', 'f_cons (g/kWh)'])
        edge_style = plot_data.EdgeStyle(line_width=0.5,
        elements = []
        for i, gear in enumerate(self.gears_ratios[0]):
            point = {'sec': cycle_time[i],
                     'f_cons (g/kWh)': self.fuel_consumptions[i]*3.6e9}
        dataset = plot_data.Dataset(elements=elements, edge_style=edge_style,
                                    tooltip=tooltip, point_style=point_style)
        graphs2d.append(plot_data.Graph2D(graphs=[dataset], x_variable='sec',
                                          y_variable='f_cons (g/kWh)'))
        tooltip = plot_data.Tooltip(attributes=['sec', 'w_e'])
        edge_style = plot_data.EdgeStyle(line_width=0.5,
        elements = []
        for i, torque in enumerate(self.wltp_cycle.cycle_torques):
            elements.append({'sec': cycle_time[i], 'w_e': self.engine_speeds[i]})
        dataset = plot_data.Dataset(elements=elements, edge_style=edge_style,
                                    tooltip=tooltip, point_style=point_style)
        graphs2d.append(plot_data.Graph2D(graphs=[dataset], x_variable='sec',
        tooltip = plot_data.Tooltip(attributes=['sec', 'w_t'])
        edge_style = plot_data.EdgeStyle(line_width=0.5,
        elements = []
        for i, torque in enumerate(self.wltp_cycle.cycle_torques):
        dataset = plot_data.Dataset(elements=elements, edge_style=edge_style,
                                    tooltip=tooltip, point_style=point_style)
        graphs2d.append(plot_data.Graph2D(graphs=[dataset], x_variable='sec',
        coords = [(0, 0), (0, 187.5), (0, 375), (0, 562.5)]
        sizes = [plot_data.Window(width=1500, height=187.5),
                 plot_data.Window(width=1500, height=187.5),
                 plot_data.Window(width=1500, height=187.5),
                 plot_data.Window(width=1500, height=187.5)]
        multiplot2 = plot_data.MultiplePlots(elements=points, plots=graphs2d,
        return multiplot2

For that, two multiplot types of data_plot were built. For the first Multiplot plot_data, two scatter plots were created, showing the fuel consumption as well as the relation between the car speed the fuel consumption and the wheel torque. In this first multiplot there is also a parallel plot showing the relation between the engine torque and speed and the fuel consumption. The results are shown in the first image below;

The second Multiplot plot_data_2 created is actually a set of four 2D type graphs of different parameters over the time of whole WLTP Cycle. The first shows the different changes in gear over time. The second, the third and fourth respectively show the consumption, the engine speed and the torque speed over time, to allow us to have a view of what is happening during all cycle.


As you might have seen before, to test our tutorial a script file is created. The script starts by defining the EfficiencyMap object and for that it uses some engine information such as a list of the engine's speed and torque and a fuel mass flow rate matrix where each value corresponds to one combination of engine speed and torque values, and the last parameter specified is the fuel lower heating value. When defining any object be sure that all specific parameters are well introduced in the SI units.

The next object to be defined is one from the WLTPCycle class, and for that we specify the list of the wltp cycle speeds as well as the mass and tire radius of the car plus the cycle time interval between one point and the next. Finally, with these first two objects created it is now possible to create the elements composing the engineering system. Now, to define the engine you only have to define the engine speed and torque setpoint, corresponding to the engine's goal state when it is stopped. The gearbox is then defined with the engine just created, alongside with the car speed ranges allowed by the gears. The last object defined is the GearboxOptimizer which is also initialized with the GearBox and WLTPCycle defined above, in addition to a list containing the minimum and maximum values the first gear ratio can bear.

import tutorials.tutorial9_simple_gearbox as objects
import numpy as np
import plot_data
from dessia_api_client import Client
Engine efficiency map
engine_speeds = list(np.linspace(500, 6000, num = 12)) #Engine speed in rpm
engine_speeds = [float(i)*np.pi/30 for i in engine_speeds]  # in rad/s
engine_torques = [15.6,31.2, 46.8, 62.4, 78, 93.6, 109.2, 124.8, 140.4, 156, 171.6] #engine torque in N*m
mass_flow_rate = [[0.1389, 0.2009, 0.2524, 0.3006, 0.3471, 0.4264, 0.4803, 0.5881, 0.5881, 0.6535, 0.7188],
                  [0.2777, 0.3659, 0.4582, 0.5587, 0.6453, 0.7792, 0.8977, 1.0325, 1.1762, 1.3069, 1.4376],
                  [0.4166, 0.5538, 0.7057, 0.8332, 0.9557, 1.0733, 1.2127, 1.3428, 1.5438, 1.9604, 2.1564],
                  [0.5391, 0.7188, 0.9116, 1.0913, 1.2497, 1.4115, 1.5552, 1.7774, 2.0290, 2.3851, 2.8752],
                  [0.6330, 0.8658, 1.0904, 1.2906, 1.5111, 1.6786, 1.9440, 2.2217, 2.4995, 2.8997, 3.5940],
                  [0.7106, 0.9949, 1.2718, 1.5193, 1.7888, 2.0878, 2.3671, 2.6661, 2.9993, 3.5286, 4.3128],
                  [0.7433, 1.0806, 1.3722, 1.7839, 2.2013, 2.5490, 2.8817, 3.1562, 3.5507, 4.1739, 5.0316],
                  [0.9475, 1.2938, 1.7290, 2.2087, 2.5648, 2.9993, 3.3391, 3.6855, 4.2932, 4.8355, 5.7504],
                  [1.1027, 1.6026, 2.1525, 2.5877, 2.9957, 3.4184, 3.8852, 4.4108, 5.0151, 5.6238, 6.4692],
                  [1.5519, 2.0910, 2.5730, 3.0222, 3.4715, 3.8717, 4.4998, 5.0642, 5.7781, 6.4528, 7.1880],
                  [1.8868, 2.5517, 3.1537, 3.6479, 4.0882, 4.4206, 5.2203, 5.8941, 6.5500, 7.2329, 7.9068],
                  [2.0584, 2.8817, 3.5286, 4.0775, 4.5578, 5.1165, 5.6948, 6.4300, 7.1455, 7.8414, 8.6256]] #mass flow rate in g/s
mass_flow_rate_kgps = []
for list_mass_flow_rate in mass_flow_rate:
    list_mass_flow = []
    for mass_flow in list_mass_flow_rate:
        list_mass_flow.append(mass_flow/1000)                                                                #mass flow rate in kg/s
fuel_hv = 0.012068709                                                       # in kWh/g
fuel_hv = fuel_hv*3.6e9                                                    # in J/kg
efficiency_map = objects.EfficiencyMap(engine_speeds = engine_speeds, engine_torques = engine_torques, mass_flow_rate = mass_flow_rate_kgps, fuel_hv = fuel_hv)
WLTP cycle
cycle_speeds= [0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.2,3.1,5.7,8.0,10.1,12.0,13.8,15.4,16.7,17.7,18.3,18.8,
                0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0]        # velocity in km/h
car_mass = 1524                                                             # Midsize car wheight in kilograms
dt = 1                                                                      # time interval in seconds
tire_radius = 0.1905                                                        # tire radius in m
cycle_speeds = [speed*1000/3600 for speed in cycle_speeds] #cycle speed in m/s
wltp_cycle = objects.WLTPCycle(cycle_speeds = cycle_speeds, car_mass = car_mass, tire_radius = tire_radius)
setpoint_speed = 600*np.pi/30 # in rad/s
setpoint_torque = 100
engine = objects.Engine(efficiency_map = efficiency_map, setpoint_speed = setpoint_speed, setpoint_torque = setpoint_torque)
speed_ranges = [[0, 30], [20 ,40], [30,50], [45, 70]] # in km/h
speed_ranges = [[speed_range[0]*(1000*2*np.pi)/(3600*np.pi*tire_radius), speed_range[1]*(1000*2*np.pi)/(3600*np.pi*tire_radius)] for speed_range in speed_ranges] #in rad/s
gearbox = objects.GearBox(engine = engine, speed_ranges = speed_ranges)
GearBox Optimizer
optimizer = objects.GearBoxOptimizer(gearbox = gearbox, wltp_cycle = wltp_cycle, first_gear_ratio_min_max = [.5, 4.5])
results = optimizer.optimize(2)

If you run the following code in the console, you will be able to see some plots that allow you to analyse what you get for one of the results

plot_data.plot_canvas(plot_data_object=results[0][0].plot_data(), canvas_id='canvas')
plot_data.plot_canvas(plot_data_object=results[0][0].plot_data_2(), canvas_id='canvas')

Here is one of the results you should get by running this code

2.4 - Build a Workflow

Create a workflow for an optimizer

This part is about building a good workflow from the optimizer. The first step consists in transforming the Optimizer object into workflow blocks.

import tutorials.tutorial9_simple_3ratios_gearbox as objects
from dessia_common.workflow.blocks import (
from dessia_common.workflow.core import Workflow, Pipe
import numpy as np
block_optimizer = InstantiateModel(objects.GearBoxOptimizer, name="Gearbox Optimizer")
method_type_optimize = MethodType(objects.GearBoxOptimizer, "optimize")
method_optimize = ModelMethod(method_type_optimize, name="Optimize")

Like the Optimizer, four other workflow blocks must be instantiated: an EfficiencyMap, a WLTPCycle, an Engine and a GearBox blocks

block_gearbox = InstantiateModel(objects.GearBox, name="Gearbox")
block_engine = InstantiateModel(objects.Engine, name="Engine")
block_efficiencymap = InstantiateModel(objects.EfficiencyMap, name="Efficiency Map")
block_wltpcycle = InstantiateModel(objects.WLTPCycle, name="WLTP Cycle")

Then, for the output results, a display workflow block is created, which receives a list of interesting attributes which will help compare all available solutions

list_attribute = [
display = MultiPlot(selector_name="Multiplot", attributes=list_attribute, name="Display")

To finish creating the workflow, the only step remaining is to connect the blocks

block_workflow = [
pipe_workflow = [
    Pipe(block_optimizer.outputs[0], method_optimize.inputs[0]),
    Pipe(block_gearbox.outputs[0], block_optimizer.inputs[0]),
    Pipe(block_wltpcycle.outputs[0], block_optimizer.inputs[1]),
    Pipe(block_engine.outputs[0], block_gearbox.inputs[0]),
    Pipe(block_efficiencymap.outputs[0], block_engine.inputs[0]),
    Pipe(method_optimize.outputs[0], display.inputs[0]),
workflow = Workflow(block_workflow, pipe_workflow, method_optimize.outputs[0])

After creating your workflow, you can import it and visualize the plots on the platform. Note that If you have not created your workflow locally (script) you can directly create it on the platform using the workflow builder, this way of doing things is the most recommended because it allows you to visualize the workflow being created and thus avoid making mistakes (for example in connecting ports), to do this you just need to install the application (package) on the platform (in this case tutorials) and requirements (see file in tutorials package) so that you have access to the class and methods and thus create your blocks. Note that you can export your workflow from the platform in script format if necessary.

from dessia_api_client import Client
workflow_run =
c = Client(api_url = '<>')
r = c.create_object_from_python_object(workflow)

What you get if you push your workflow into the platform or directly create it there.


While specifying the input values of the workflow, the block_optimizer has one input(firstgear_ratio_min_max), the _method_optimize has one input(maxloops) the _block_gearbox has one input (speedranges), the _block_engine has two inputs (1:setpointspeed, 2:setpoint_torque), the _block_efficiencymap has 4 inputs (1: enginespeeds, 2: enigne_torques, 3: f mass_fuel_rate, 4: Q_hv) and finally the _block_wltpcycle has three inputs (1: cycle_speeds, 2:car_mass, 3: tire_radius)

input_values = {workflow.input_index(block_optimizer.inputs[2]): [.5, 4.5],
                workflow.input_index(method_optimize.inputs[1]): 5,
workflow_run =