File size: 7,897 Bytes
8e9b78d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# src/models.py

import numpy as np
import pandas as pd
from typing import List, Dict
import logging

from src.utils.logger import setup_logger

logger = setup_logger(__name__)


class TrafficData:
    def __init__(self, axle_loads: List[float], traffic_growth_rate: float, analysis_period: int):
        """
        Initialize TrafficData with axle loads, growth rate, and analysis period.

        :param axle_loads: List of axle loads in kN.
        :param traffic_growth_rate: Annual traffic growth rate (decimal, e.g., 0.02 for 2%).
        :param analysis_period: Number of years for analysis.
        """
        self.axle_loads = axle_loads
        self.traffic_growth_rate = traffic_growth_rate
        self.analysis_period = analysis_period

    def get_total_axle_loads(self) -> List[List[float]]:
        """
        Project total axle loads over the analysis period considering growth rate.

        :return: A list of lists, each sublist represents axle loads for a year.
        """
        total_loads = []
        for year in range(self.analysis_period):
            growth_factor = (1 + self.traffic_growth_rate) ** year
            projected_loads = [load * growth_factor for load in self.axle_loads]
            total_loads.append(projected_loads)
            logger.debug(f"Year {year + 1}: {projected_loads}")
        return total_loads

    @staticmethod
    def from_dataframe(df: pd.DataFrame) -> 'TrafficData':
        """
        Create TrafficData instance from a pandas DataFrame.

        :param df: DataFrame with columns 'Axle_Loads', 'Traffic_Growth_Rate', 'Analysis_Period'.
        :return: TrafficData instance.
        """
        try:
            axle_loads = df['Axle_Loads'].dropna().tolist()
            traffic_growth_rate = float(df['Traffic_Growth_Rate'].iloc[0])
            analysis_period = int(df['Analysis_Period'].iloc[0])
            logger.info("TrafficData loaded successfully from DataFrame.")
            return TrafficData(axle_loads, traffic_growth_rate, analysis_period)
        except Exception as e:
            logger.error(f"Error creating TrafficData from DataFrame: {e}")
            raise ValueError(f"Invalid Traffic Data: {e}")


class ClimateData:
    def __init__(self, average_temperature: float, temperature_variation: float, rainfall: float):
        """
        Initialize ClimateData with average temperature, temperature variation, and rainfall.

        :param average_temperature: Average temperature in °C.
        :param temperature_variation: Temperature variation in °C.
        :param rainfall: Annual rainfall in mm.
        """
        self.average_temperature = average_temperature
        self.temperature_variation = temperature_variation
        self.rainfall = rainfall

    @staticmethod
    def from_dataframe(df: pd.DataFrame) -> 'ClimateData':
        """
        Create ClimateData instance from a pandas DataFrame.

        :param df: DataFrame with columns 'Average_Temperature', 'Temperature_Variation', 'Rainfall'.
        :return: ClimateData instance.
        """
        try:
            average_temperature = float(df['Average_Temperature'].iloc[0])
            temperature_variation = float(df['Temperature_Variation'].iloc[0])
            rainfall = float(df['Rainfall'].iloc[0])
            logger.info("ClimateData loaded successfully from DataFrame.")
            return ClimateData(average_temperature, temperature_variation, rainfall)
        except Exception as e:
            logger.error(f"Error creating ClimateData from DataFrame: {e}")
            raise ValueError(f"Invalid Climate Data: {e}")


class SubgradeProperties:
    def __init__(self, modulus: float, CBR: float):
        """
        Initialize SubgradeProperties with modulus and California Bearing Ratio (CBR).

        :param modulus: Modulus of subgrade reaction in kPa/m.
        :param CBR: California Bearing Ratio in %.
        """
        self.modulus = modulus
        self.CBR = CBR

    @staticmethod
    def from_dataframe(df: pd.DataFrame) -> 'SubgradeProperties':
        """
        Create SubgradeProperties instance from a pandas DataFrame.

        :param df: DataFrame with columns 'Modulus', 'CBR'.
        :return: SubgradeProperties instance.
        """
        try:
            modulus = float(df['Modulus'].iloc[0])
            CBR = float(df['CBR'].iloc[0])
            logger.info("SubgradeProperties loaded successfully from DataFrame.")
            return SubgradeProperties(modulus, CBR)
        except Exception as e:
            logger.error(f"Error creating SubgradeProperties from DataFrame: {e}")
            raise ValueError(f"Invalid Subgrade Properties Data: {e}")


class MaterialProperties:
    def __init__(self, asphalt_modulus: float, concrete_strength: float, thermal_coeff: float):
        """
        Initialize MaterialProperties with asphalt modulus, concrete strength, and thermal coefficient.

        :param asphalt_modulus: Asphalt modulus in MPa.
        :param concrete_strength: Concrete strength in MPa.
        :param thermal_coeff: Thermal coefficient (°C^-1).
        """
        self.asphalt_modulus = asphalt_modulus
        self.concrete_strength = concrete_strength
        self.thermal_coeff = thermal_coeff

    @staticmethod
    def from_dataframe(df: pd.DataFrame) -> 'MaterialProperties':
        """
        Create MaterialProperties instance from a pandas DataFrame.

        :param df: DataFrame with columns 'Asphalt_Modulus', 'Concrete_Strength', 'Thermal_Coeff'.
        :return: MaterialProperties instance.
        """
        try:
            asphalt_modulus = float(df['Asphalt_Modulus'].iloc[0])
            concrete_strength = float(df['Concrete_Strength'].iloc[0])
            thermal_coeff = float(df['Thermal_Coeff'].iloc[0])
            logger.info("MaterialProperties loaded successfully from DataFrame.")
            return MaterialProperties(asphalt_modulus, concrete_strength, thermal_coeff)
        except Exception as e:
            logger.error(f"Error creating MaterialProperties from DataFrame: {e}")
            raise ValueError(f"Invalid Material Properties Data: {e}")


class Pavement:
    def __init__(self, layers: List[float], pavement_type: str = 'Flexible'):
        """
        Initialize Pavement structure.

        :param layers: List of layer thicknesses in mm.
        :param pavement_type: Type of pavement ('Flexible', 'Rigid', 'Composite').
        """
        self.layers = layers
        self.pavement_type = pavement_type

    def add_layer(self, thickness: float):
        """
        Add a new layer to the pavement structure.

        :param thickness: Thickness of the new layer in mm.
        """
        self.layers.append(thickness)
        logger.debug(f"Added layer: {thickness} mm. Total layers: {self.layers}")

    def remove_layer(self, index: int):
        """
        Remove a layer from the pavement structure by index.

        :param index: Index of the layer to remove.
        """
        if 0 <= index < len(self.layers):
            removed = self.layers.pop(index)
            logger.debug(f"Removed layer at index {index}: {removed} mm. Remaining layers: {self.layers}")
        else:
            logger.warning(f"Attempted to remove non-existent layer at index {index}.")

    def set_pavement_type(self, pavement_type: str):
        """
        Set the type of pavement.

        :param pavement_type: Type of pavement ('Flexible', 'Rigid', 'Composite').
        """
        if pavement_type in ['Flexible', 'Rigid', 'Composite']:
            self.pavement_type = pavement_type
            logger.debug(f"Pavement type set to {pavement_type}.")
        else:
            logger.error(f"Invalid pavement type: {pavement_type}. Must be 'Flexible', 'Rigid', or 'Composite'.")
            raise ValueError("Invalid pavement type. Choose from 'Flexible', 'Rigid', or 'Composite'.")