<jupyter_start><jupyter_text>Making updated mixin classes<jupyter_code>class VesselProperties:
    """Mixin class: Something that has vessel properties

    type: can contain info on vessel type (avv class, cemt_class or other)
    B: vessel width
    L: vessel length
    H_e: vessel height unloaded
    H_f: vessel height loaded
    T_e: draught unloaded
    T_f: draught loaded

    Add information on possible restrictions to the vessels, i.e. height, width, etc.
    """

    def __init__(
        self,
        type,
        B,
        L,
        H_e,
        H_f,
        T_e,
        T_f,
        *args,
        **kwargs
        ):
        super().__init__(*args, **kwargs)

        """Initialization"""
        self.type = type

        self.B = B
        self.L = L

        self.H_e = H_e
        self.H_f = H_e

        self.T_e = T_e
        self.T_f = T_f

    @property
    def H(self):
        """ Calculate current height based on filling degree """

        return (
            self.filling_degree * (self.H_f - self.H_e)
            + self.H_e
        )

    @property
    def T(self):
        """ Calculate current draught based on filling degree
        
        Here we should implement the rules from Van Dorsser et al 
        """

        return (
            self.filling_degree * (self.T_f - self.T_e)
            + self.T_e
        )

    def get_route(
        self,
        origin,
        destination,
        graph=None,
        minWidth=None,
        minHeight=None,
        minDepth=None,
        randomSeed=4,
    ):
        """ Calculate a path based on vessel restrictions """

        graph = graph if graph else self.env.FG
        minWidth = minWidth if minWidth else 1.1 * self.B
        minHeight = minWidth if minHeight else 1.1 * self.H
        minDepth = minWidth if minDepth else 1.1 * self.T

        # Check if information on restrictions is added to the edges
        random.seed(randomSeed)
        edge = random.choice(list(graph.edges(data=True)))
        edge_attrs = list(edge[2].keys())

        # IMPROVE THIS TO CHECK ALL EDGES AND COMBINATIONS OF RESTRICTIONS

        if all(item in edge_attrs for item in ["Width", "Height", "Depth"]):
            edges = []
            nodes = []

            for edge in graph.edges(data=True):
                if (
                    edge[2]["Width"] >= minWidth
                    and edge[2]["Height"] >= minHeight
                    and edge[2]["Depth"] >= minDepth
                ):
                    edges.append(edge)

                    nodes.append(graph.nodes[edge[0]])
                    nodes.append(graph.nodes[edge[1]])

            subGraph = graph.__class__()

            for node in nodes:
                subGraph.add_node(
                    node["name"],
                    name=node["name"],
                    geometry=node["geometry"],
                    position=(node["geometry"].x, node["geometry"].y),
                )

            for edge in edges:
                subGraph.add_edge(edge[0], edge[1], attr_dict=edge[2])

            try:
                return nx.dijkstra_path(subGraph, origin, destination)
            except:
                raise ValueError(
                    "No path was found with the given boundary conditions."
                )

        # If not, return shortest path
        else:
            return nx.dijkstra_path(graph, origin, destination)

class HasEnergy:
    """Mixin class: Something that has energy usage.

    P_installed: installed engine power [kW]
    C_b: blockage factor [-]
    C_BB: breadth coefficient [-]
    nu: kinematic viscosity [m^2/s]
    rho: density of the surrounding water [kg/m^3]
    g: gravitational accelleration [m/s^2]
    x: number of propellors [-]
    eta_0: open water efficiency of propellor [-]
    eta_r: relative rotative efficiency [-]
    eta_t: transmission efficiency [-]
    eta_g: gearing efficiency [-]
    c_stern: shape of the afterbody [-]
    one_k2: appendage resistance factor [-]
    """

    def __init__(
        self, 
        P_installed,
        C_b,
        C_BB,
        nu = 1*10**(-6), # kinematic viscosity
        rho = 1000,
        g = 9.81,
        x = 2, # number of propellors
        eta_0 = 0.7,
        eta_r = 0.98,
        eta_t = 0.98,
        eta_g = 0.96,
        c_stern = 0,
        one_k2 = 1.5,
        *args, 
        **kwargs
        ):
        super().__init__(*args, **kwargs)

        """Initialization"""
        self.P_installed = P_installed
        self.C_b = C_b
        self.C_BB = C_BB
        self.nu = nu 
        self.rho = rho
        self.g = g
        self.x = x
        self.eta_0 = eta_0
        self.eta_r = eta_r
        self.eta_t = eta_t
        self.eta_g = eta_g
        self.c_stern = c_stern
        self.one_k2 = one_k2
                
    def calculate_properties(self):
        self.C_M = 1.006 - 0.0056 * self.C_b ** (-3.56)
        self.C_wp = (1 + 2 * self.C_b) / 3
        self.C_p = self.C_b / self.C_M

        self.delta = self.C_b * self.L * self.B * self.T #water displacement

        self.lcb = -13.5 + 19.4 * self.C_p
        self.L_R = self.L * (1 - self.C_p + (0.06 * self.C_p * self.lcb)/(4 * self.C_p - 1))

        self.A_BT = self.C_BB * self.B * self.T * self.C_M
        self.A_T = 0.1 * self.B * self.T

        self.S_T = self.L * (2 * self.T + self.B) * np.sqrt(self.C_M) * (0.453 + 0.4425 * self.C_b - 0.2862 * self.C_M - 0.003467 * (self.B / self.T) + 0.3696 * self.C_wp) + 2.38 * (self.A_BT / self.C_b)
        self.S_APP = 0.05 * self.S_T
        self.S_B = self.L * self.B

        self.D_s = 0.7 * self.T
        
    def calculate_frictional_resistance(self, V_0, h):
        """Section 2.2.1 Frictional resistance"""

        self.R_e = V_0 * self.L / self.nu
        self.D = h - self.T #distance from bottom ship to the bottom of the fairway

        self.Cf_0 = 0.075 / ((np.log10(self.R_e) - 2) ** 2)
        self.Cf_proposed = (0.08169 / ((np.log10(self.R_e) - 1.717) ** 2)) * (1 + (0.003998 / (np.log10(self.R_e) - 4.393)) * (self.D / self.L) ** (-1.083))

        self.a = 0.042612 * np.log10(self.R_e) + 0.56725
        self.Cf_katsui = 0.0066577 / ((np.log10(self.R_e) - 4.3762) ** self.a)

        self.V_B = 0.4277 * V_0 * np.exp((h / self.T) ** (-0.07625))
                
        self.C_f = self.Cf_0 + (self.Cf_proposed - self.Cf_katsui) * (self.S_B / self.S_T) * (self.V_B / V_0) ** 2

        self.R_f = (self.C_f * 0.5 * self.rho * (V_0 ** 2) * self.S_T) / 1000 #kN

    def calculate_viscous_resistance(self):
        """Section 2.2.2 Viscous resistance"""

        self.c_14 = 1 + 0.0011 * self.c_stern
        self.one_k1 = 0.93 + 0.487 * self.c_14 * ((self.B / self.L) ** 1.068) * ((self.T/self.L) ** 0.461) * ((self.L / self.L_R) ** 0.122) * (((self.L ** 3) / self.delta) ** 0.365) * ((1 - self.C_p) ** (-0.604))
        
    def calculate_appendage_resistance(self, V_0):
        """Section 2.2.3 Appendage resistance"""
        
        self.R_APP = (0.5 * self.rho * (V_0 ** 2) * self.S_APP * self.one_k1 * self.C_f) / 1000 #kN
     
    def calculate_wave_resistance(self, V_0):
        """Section 2.2.4 Wave resistance"""

        self.F_n = V_0 / np.sqrt(self.g * self.L) #Froude number

        #coefficient c_7
        if self.B / self.L < 0.11:
            self.c_7 = 0.229577 * (self.B / self.L) ** 0.33333
        elif self.B / self.L > 0.25:
            self.c_7 = 0.5 - 0.0625 * (self.L / self.B)
        else:
            self.c_7 = self.B / self.L

        self.i_E = 125.67 * (self.B / self.L) - 162.25 * (self.C_p ** 2) + 234.32 * (self.C_p ** 3) + 0.155087 * (self.lcb ** 3)
        self.c_1 = 2223105 * (self.c_7 ** 3.78613) * ((self.T / self.B) ** 1.07961) * ((90 - self.i_E) ** (-1.37565))

        self.c_3 = 0.56 * (self.A_BT ** 1.5) / (self.B * self.T * (0.31 * np.sqrt(self.A_BT) + self.T - 0.5*self.T))
        self.c_2 = np.exp(-1.89 * np.sqrt(self.c_3))
        self.c_5 = 1 - 0.8 * self.A_T / (self.B * self.T * self.C_M)

        #coefficient c_16
        if self.C_p < 0.80:
            self.c_16 = 8.07981 * self.C_p - (13.8673 * self.C_p ** 2) + (6.984388 * self.C_p ** 3)
        else:
            self.c_16 = 1.73014 - 0.7067 * self.C_p

        self.m_1 = 0.0140407 * (self.L/self.T) - (1.75254 * self.delta**(1/3))/self.L - 4.79323 * (self.B/self.L) - self.c_16

        #coefficient c_15
        if (self.L**3)/self.delta < 512:
            self.c_15 = -1.69385
        elif (self.L**3)/self.delta > 1727:
            self.c_15 = 0
        else:
            self.c_15 = -1.69385 + (self.L / (self.delta**(1/3)) - 8.0) / 2.36

        self.m_2 = self.c_15 * (self.C_p**2) * np.exp(-0.1 * (self.F_n)**(-2))

        #coefficient lambda
        if self.L/self.B < 12:
            self.lmbda = 1.446 * self.C_p - 0.03 * (self.L/self.B)
        else:
            self.lmbda = 1.446 * self.C_p - 0.36


        self.R_W = (self.c_1 * self.c_2 * self.c_5 * self.delta * self.rho * self.g * np.exp(self.m_1 * (self.F_n**-0.9) + self.m_2 * np.cos(self.lmbda * (self.F_n**(-2))))) / 1000 #kN

    def calculate_residual_resistance(self, V_0):
        """Section 2.2.5 Residual resistance terms"""

        #Resistance resulting from the bulbouw bow
        self.P_B = 0.56 * np.sqrt(self.A_BT) / (self.T - 1.5 * 0.5*self.T)
        self.F_ni = V_0 / np.sqrt(self.g * (self.T - 0.5*self.T - 0.25*np.sqrt(self.A_BT)) + 0.15*(V_0**2))

        self.R_B = (0.11 * np.exp(-3 * self.P_B**(-2)) * (self.F_ni**3) * (self.A_BT**1.5) * self.rho * self.g / (1 + (self.F_ni**2))) / 1000 #kN

        #Resistance due to immersed transom
        self.F_nt = V_0 / np.sqrt(2 * self.g * self.A_T / (self.B + self.B * self.C_wp))

        # ToDo: didn't undertand what happened here. Think this was for running with an array of values?
        self.c_6 = 0.2 * (1 - 0.2 * self.F_nt)

#         self.c_6 = np.zeros(101)
#         counter = 0 
#         for i in self.F_nt:
#             if i < 5:
#                 self.c_6[counter] = 0.2 * (1 - 0.2 * i)
#             else:
#                 self.c_6[counter] = 0

#             counter += 1

        self.R_TR = (0.5 * self.rho * (V_0**2) * self.A_T * self.c_6) / 1000 #kN

        #Model-ship correlation resistance
        if self.T/self.L > 0.04:
            self.c_4 = 0.04
        else:
            self.c_4 = self.T / self.L

        self.C_A = 0.006 * ((self.L + 100) ** (-0.16)) - 0.00205 + 0.003 * np.sqrt(self.L / 7.5) * (self.C_b ** 4) * self.c_2 * (0.04 - self.c_4)

        self.R_A = (0.5 * self.rho * (V_0 ** 2) * self.S_T * self.C_A) / 1000 #kW

    def calculate_total_resistance(self, V_0, h):
        
        self.calculate_properties()
        self.calculate_frictional_resistance(V_0, h)
        self.calculate_viscous_resistance()
        self.calculate_appendage_resistance(V_0)
        self.calculate_wave_resistance(V_0)
        self.calculate_residual_resistance(V_0)
        
        self.R_tot = self.R_f * self.one_k1 + self.R_APP + self.R_W + self.R_B + self.R_TR + self.R_A

    def calculate_total_power_required(self):
        """ Section 2.1 Total required power """

        #2.1.1 Required power for systems on board
        self.P_hotel = 0.081 * self.P_installed

        #2.1.2 Required power for propulsion

        #Effective Horse Power (EHP)
        self.P_EHP = self.V_B * self.R_tot

        dw = np.zeros(101)
        counter = 0 

        #Calculation hull efficiency
        if self.F_n < 0.2:
            self.dw = 0
        else:
            self.dw = 0.1


        self.w = 0.11 * (0.16 / self.x) * self.C_b * np.sqrt((self.delta**(1/3)) / self.D_s) - self.dw

        if self.x == 1:
            self.t = 0.6 * self.w * (1 + 0.67 * self.w)
        else:
            self.t = 0.8 * self.w * (1 + 0.25 * self.w)

        self.eta_h = (1 - self.t) / (1 - self.w)

        #Delivered Horse Power (DHP)

        self.P_DHP = self.P_EHP / (self.eta_0 * self.eta_r * self.eta_h)

        #Brake Horse Power (BHP)
        self.P_BHP = self.P_DHP / (self.eta_t * self.eta_g)

        self.P_tot = self.P_hotel + self.P_BHP<jupyter_output><empty_output><jupyter_text>Create a vessel object<jupyter_code># Vessel type
Vessel = type('Vessel', 
              (core.Identifiable,      # so we can identify an individual vessel (by name and id)
               VesselProperties,       # so that we can provide vessel properties
               core.Movable,           # so we van make the vessel move
               core.HasContainer,      # so that we can indicate the amount of cargo it carries
               core.HasResource,       # so that a vessel be requested to do something
               core.Routeable,         # so that you can provide a route that the vessel will follow
               HasEnergy),        # so that you can calculate energy used on route
              {})<jupyter_output><empty_output><jupyter_text>Instantiate a version of the vessel object<jupyter_code>vessel = Vessel(name='Vessel', v=1, type=[], 
                geometry=[], 
                capacity=1, level=1,
                B=11.75, L = 135, 
                H_e=[], H_f=[], T_e=2.75, T_f=2.75, 
                route=[], env=[],
                P_installed=1267,
                C_b = 0.922218379546862,
                C_BB = 0.185
                )<jupyter_output><empty_output><jupyter_text>Calculate the R_tot for a given speed and depth<jupyter_code>V_0 = 5 # m/s
h = 10  # m

vessel.calculate_total_resistance(V_0, h)
vessel.calculate_total_power_required()

print(vessel.R_tot)
print(vessel.P_tot)<jupyter_output>146.2086162045085
1296.119591619958
{'fraction_non_alphanumeric': 0.13859855733843662, 'fraction_numerical': 0.055792727808037686, 'mean_word_length': 1.6814683244523387, 'pattern_counts': {'":': 0, '<': 22, '<?xml version=': 0, '>': 22, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '4', 'dir': 'jupyter-structured-clean-dedup', 'id': '75716', 'license': 'MIT', 'n_tokens_mistral': 5542, 'n_tokens_neox': 4943, 'n_words': 1124, 'path': 'notebooks/Energy consumption.ipynb', 'repo_name': 'TUDelft-CITG/OpenTNSim', 'size': '13586'}