Source code for flight_mech.motor

"""
Module to define motor models.
"""

###########
# Imports #
###########

# Python imports #

import os
from typing import Literal
import json

# Dependencies #

import numpy as np
from scipy.optimize import minimize

# Local imports #

from flight_mech._common import plot_graph

#############
# Constants #
#############

# Define a default plane database location
default_motor_database = os.path.join(
    os.path.dirname(__file__), "motor_database")

# Define the units of the variables to indicate them on the plot labels
VARIABLE_TO_UNIT = {
    "efficiency": ".",
    "rotation_speed": "rad.s-1",
    "power": "W",
    "torque": "N.m"
}

###########
# Classes #
###########

[docs] class ElectricalMotor: """ Class to define an electrical motor controlled by tension. Parameters ---------- I0 : float No-load current in A. internal_resistance : float Internal resistance of the motor in Ohm. external_resistance : float External resistance of the batteries in Ohm. K : float Coefficient between tension and rotation speed in s-1.V-1. """ I_0: float # A internal_resistance: float # Ohm external_resistance: float # Ohm Kv: float # rad.s-1.V-1 I_max: float # A U_max: float # V mass: float | None # kg # Internal variables _U: float | None = None # V _I: float | None = None # A _rotation_speed: float | None = None # rad.s-1 _power: float | None = None # W _torque: float | None = None # N.m def __init__(self, motor_data_name: str | None = None, motor_database_folder: str = default_motor_database, motor_parameters_dict: dict | None = None): if motor_data_name is not None and motor_parameters_dict is not None: raise ValueError( "You have provided both a data file to load and a parameters dictionary. Please provide only one of them to create a motor instance.") elif motor_data_name is not None: self.load_motor_data(motor_data_name, motor_database_folder) elif motor_parameters_dict is not None: self.set_motor_parameters(**motor_parameters_dict)
[docs] def load_motor_data(self, motor_data_name: str, motor_data_folder: str = default_motor_database): """ Load the data from a motor stored in the given database folder. Parameters ---------- motor_data_name : str Name of the motor. motor_data_folder : str, optional Path to the database folder, by default default_motor_database """ # Load the json file file_path = os.path.join(motor_data_folder, motor_data_name + ".json") with open(file_path, "r") as file: motor_parameters_dict = json.load(file) # Load the parameters in the instance self.set_motor_parameters(**motor_parameters_dict)
[docs] def set_motor_parameters(self, I_0: float, I_max: float, internal_resistance: float, external_resistance: float, Kv: float, U_max: float, mass: float | None = None): """ Set the parameters of the motor. Parameters ---------- I0 : float No-load current in A. internal_resistance : float Internal resistance of the motor in Ohm. external_resistance : float External resistance of the batteries in Ohm. Kv : float Coefficient between tension and rotation speed in s-1.V-1. U_max : float Max tension of the motor in V. mass : float | None, optional Mass of the motor in kg. """ self.I_0 = I_0 self.I_max = I_max self.internal_resistance = internal_resistance self.external_resistance = external_resistance self.Kv = Kv self.U_max = U_max self.mass = mass
@property def U(self): if self._U is not None: return self._U else: return self.U_max @U.setter def U(self, value): if value > self.U_max: raise ValueError( f"The given tension ({value}) is above the motor maximum tension ({self.U_max}). Please provide a lower value or change the value of U_max.") self._U = value @property def _I_minimize_start(self): return (self.I_0 + self.I_max) / 2 @property def I(self): if self._I is not None: return self._I elif self._rotation_speed is not None: return self._compute_I_from_rotation_speed() elif self._power is not None: return self._compute_I_from_power() elif self._torque is not None: return self._compute_I_from_torque() else: raise ValueError( "No value to compute the current, please specify a desired current, rotation speed, power or torque.") @I.setter def I(self, value: float): if value > self.I_max: raise ValueError( f"The given current ({value} A) exceeds the max current of the motor ({self.I_max} A).") elif value < self.I_0: raise ValueError( f"The given current ({value} A) is under the no-load current of the motor ({self.I_0} A).") self._I = value self._rotation_speed = None self._power = None self._torque = None @property def rotation_speed(self): if self._rotation_speed is not None: return self._rotation_speed else: return self._compute_rotation_speed_from_I(self.I) @rotation_speed.setter def rotation_speed(self, value: float): self._rotation_speed = value self._I = None self._power = None self._torque = None @property def power(self): if self._power is not None: return self._power else: return self._compute_power_from_I(self.I) @power.setter def power(self, value: float): self._power = value self._I = None self._rotation_speed = None self._torque = None @property def torque(self): if self._torque is not None: return self._torque else: return self._compute_torque_from_I(self.I) @torque.setter def torque(self, value: float): self._torque = value self._I = None self._rotation_speed = None self._power = None @property def efficiency(self): efficiency = self._compute_efficiency_from_I(self.I) return efficiency @property def electromotive_force(self): electromotive_force = self._compute_electromotive_force_from_I(self.I) return electromotive_force @property def max_efficiency(self): max_efficiency = self._compute_efficiency_from_I( self.compute_I_at_max_efficiency()) return max_efficiency def _compute_efficiency_from_I(self, I: float): """ Compute the efficiency at the given current. Parameters ---------- I : float Current in the motor. Returns ------- float Efficiency of the motor. """ efficiency = 1 - (self.I_0 * (1 / I - self.external_resistance / self.U) + ( (self.internal_resistance + self.external_resistance) / self.U) * I) return efficiency def _compute_electromotive_force_from_I(self, I: float): """ Compute the electromotive force at the given current. Parameters ---------- I : float Current in the motor. Returns ------- float Electromotive force of the motor. """ electromotive_force = self.U - self.internal_resistance * \ (I - self.I_0) - self.external_resistance * I return electromotive_force def _compute_rotation_speed_from_I(self, I: float): """ Compute the rotation speed at the given current. Parameters ---------- I : float Current in the motor. Returns ------- float Rotation speed of the motor in rad.s-1. """ electromotive_force = self._compute_electromotive_force_from_I(I) rotation_speed = self.Kv * electromotive_force return rotation_speed def _compute_power_from_I(self, I: float): """ Compute the power at the given current. Parameters ---------- I : float Current in the motor. Returns ------- float Power of the motor. """ power = self.U * I * self._compute_efficiency_from_I(I) return power def _compute_torque_from_I(self, I: float): """ Compute the torque at the given current. Parameters ---------- I : float Current in the motor. Returns ------- float Torque of the motor. """ torque = self._compute_power_from_I(I)\ / self._compute_rotation_speed_from_I(I) return torque def _compute_I_from_rotation_speed(self): """ Compute I using the rotation speed value. Returns ------- float Current in A. """ I = (1 / (self.internal_resistance + self.external_resistance)) * \ (self.U + self.internal_resistance * self.I_0 - self.rotation_speed / self.Kv) return I def _compute_I_from_power(self): """ Compute I using the power value. Returns ------- float Current in A. """ a = -(self.internal_resistance + self.external_resistance) / self.U b = 1 + self.external_resistance * self.I_0 / self.U c = - self.I_0 - self.power / self.U delta = np.power(b, 2) - 4 * a * c I = (-b + np.sqrt(delta)) / (2 * a) return I def _compute_I_from_torque(self): """ Compute I using the torque value. Returns ------- float Current in A. """ a = -(self.internal_resistance + self.external_resistance) / self.U b = 1 + self.external_resistance * self.I_0 / self.U + \ (self.external_resistance + self.internal_resistance) * \ self.torque * self.Kv / self.U c = - self.I_0 - (self.U + self.internal_resistance * self.I_0) * \ self.torque * self.Kv / self.U delta = np.power(b, 2) - 4 * a * c I = (-b + np.sqrt(delta)) / (2 * a) return I
[docs] def plot_graph(self, variable: Literal["efficiency", "rotation_speed", "power", "torque"], nb_points=100, **kwargs): """ Plot the graph of evolution of the given variable with the current in the motor. Parameters ---------- variable : Literal["efficiency", "rotation_speed", "power", "torque"] Name of the variable to plot. """ # Allocate list for the x and y values values_array = np.zeros(nb_points) current_array = np.linspace(self.I_0, self.I_max, nb_points) # Extract the values for i, value in enumerate(current_array): values_array[i] = self.__getattribute__( f"_compute_{variable}_from_I")(value) # Plot plot_graph( x_array=current_array, y_array=values_array, title=f"{variable.capitalize()} graph", x_label="Current [A]", y_label=f"{variable.capitalize()} [{VARIABLE_TO_UNIT[variable]}]", **kwargs )
[docs] def compute_I_at_max_efficiency(self): """ Compute the current that gives the maximum efficiency. Returns ------- float Current of maximum efficiency """ I = np.sqrt(self.I_0 * self.U / (self.internal_resistance + self.external_resistance)) return I
[docs] def set_at_max_efficiency(self): """ Set the motor in the maximum efficiency conditions. """ self.I = self.compute_I_at_max_efficiency()