Source code for civilutils.indian_standards.concrete

"""Concrete Mix Design using IS 456 and 10262"""
from enum import Enum
import math
import warnings

[docs] class ConcreteGrade(Enum): """Concrete grades as per IS 456. Args: Enum (str): The concrete grade designation. """ M10 = "M10" M15 = "M15" M20 = "M20" M25 = "M25" M30 = "M30" M35 = "M35" M40 = "M40" M45 = "M45" M50 = "M50" M55 = "M55"
[docs] class MaximumNominalSize(Enum): """Maximum nominal sizes of aggregates as per IS 456. Args: Enum (int): The maximum nominal size in mm. """ SIZE_10 = 10 SIZE_20 = 20 SIZE_40 = 40
[docs] class CoarseAggregateType(Enum): """Coarse aggregate types as per IS 456. Args: Enum (str): The coarse aggregate type designation. """ CRUSHED_STONE = "Crushed Stone" GRAVEL = "Gravel" ROUNDED_GRAVEL = "Rounded Gravel" CRUSHED_ANGULAR = "Crushed Angular"
[docs] class FineAggregateZone(Enum): """Fine aggregate zones as per IS 456. Args: Enum (str): The fine aggregate zone designation. """ ZONE_I = "Zone I" ZONE_II = "Zone II" ZONE_III = "Zone III" ZONE_IV = "Zone IV"
[docs] class ExposureCondition(Enum): """ Environmental exposure categories based on IS456 (Table 3). These categories are used to select design limits (for example maximum water/cement ratio, minimum cement content and cover) according to the service environment the concrete will face. Members: - MILD: Concrete surfaces protected against weather or aggressive conditions (e.g., sheltered or protected locations). - MODERATE: Exposed to condensation, rain or continuous wetting (including contact with non-aggressive soil/ground water); sheltered from severe weather. - SEVERE: Exposed to severe rain, alternate wetting and drying, occasional freezing when wet, immersion in seawater or coastal environment, or saturated salt air. - VERY_SEVERE: Exposed to sea water spray, corrosive fumes, severe freezing, or concrete in contact with or buried under aggressive subsoil/ground water. - EXTREME: Members in tidal zones or in direct contact with liquid or solid aggressive chemicals. """ MILD = "Mild" MODERATE = "Moderate" SEVERE = "Severe" VERY_SEVERE = "Very Severe" EXTREME = "Extreme"
[docs] class Materials(Enum): """Material types as per IS 456. Args: Enum (str): The material designation. """ CEMENT = "Cement" FINE_AGGREGATE = "Fine Aggregate" COARSE_AGGREGATE = "Coarse Aggregate" WATER = "Water" ADMIXTURE = "Admixture" FLY_ASH = "Fly Ash"
[docs] class ChemicalAdmixture(Enum): """Chemical admixtures as per IS 456. Args: Enum (str): The chemical admixture designation. """ SUPERPLASTICIZER = "Superplasticizer" PLASTICIZER = "Plasticizer" #TODO: find and implement logic
[docs] def __init__(self, label: str, default_percentage: float = 20.0): """Initialize the chemical admixture. Args: label (str): The chemical admixture designation. default_percentage (float, optional): The default percentage of the admixture. Defaults to 20.0. """ self.label = label self.default_percentage = float(default_percentage)
[docs] class MineralAdmixture(Enum): """Mineral admixtures as per IS 456. Args: Enum (str): The mineral admixture designation. """ FLY_ASH = ("Fly Ash", 30.0) # default percentage (e.g. % replacement of cement)
[docs] def __init__(self, label: str, default_percentage: float = 0.0): """Initialize the mineral admixture. Args: label (str): The mineral admixture designation. default_percentage (float, optional): The default percentage of replacement for cement. Defaults to 0.0. """ self.label = label self.default_percentage = float(default_percentage)
[docs] class SpecificGravity: """Specific gravity of materials as per IS 456. """
[docs] def __init__(self, material: Materials, value: float): """Initialize the specific gravity of a material. Args: material (Materials): The material type. value (float): The specific gravity value. """ self.material = material self.value = value
[docs] class ConcreteMixDesign: """Concrete mix design parameters as per IS 10262 and IS 456. """
[docs] def __init__(self, concrete_grade: ConcreteGrade, exposure_condition: ExposureCondition, specific_gravities: list[SpecificGravity], maximum_nominal_size: MaximumNominalSize = MaximumNominalSize.SIZE_20, maximum_cement_content: float = 450.0, is_pumpable: str | bool = True , chemical_admixture: ChemicalAdmixture | None = None, chemical_admixture_percentage: float | None = None, coarse_aggregate_type: CoarseAggregateType = CoarseAggregateType.CRUSHED_ANGULAR, coarse_aggregate_water_absorption: float = 0.0, coarse_aggregate_surface_moisture: float = 0.0, fine_aggregate_zone: FineAggregateZone = FineAggregateZone.ZONE_II, fine_aggregate_surface_moisture: float = 0.0, fine_aggregate_water_absorption: float = 0.0, slump_mm: float = 50.0, mineral_admixture: MineralAdmixture | None = None, mineral_admixture_percentage: float | None = None): """Initialize the concrete mix design parameters. Args: concrete_grade (ConcreteGrade): The grade of concrete (e.g. M20, M25, etc.). exposure_condition (ExposureCondition): The exposure condition (e.g. Mild, Moderate, Severe). specific_gravities (list[SpecificGravity]): The specific gravities of the materials. maximum_nominal_size (MaximumNominalSize, optional): The maximum nominal size of the aggregate. Defaults to MaximumNominalSize.SIZE_20. maximum_cement_content (float, optional): The maximum cement content (kg/m3). Defaults to 450.0. is_pumpable (str | bool, optional): Whether the concrete is pumpable. Defaults to True. chemical_admixture (ChemicalAdmixture | None, optional): The type of chemical admixture used. Defaults to None. chemical_admixture_percentage (float | None, optional): The percentage of chemical admixture used. Defaults to None. coarse_aggregate_type (CoarseAggregateType, optional): The type of coarse aggregate used. Defaults to CoarseAggregateType.CRUSHED_ANGULAR. coarse_aggregate_water_absorption (float, optional): The water absorption of coarse aggregate (%). Defaults to 0.0. coarse_aggregate_surface_moisture (float, optional): The surface moisture of coarse aggregate (%). Defaults to 0.0. fine_aggregate_zone (FineAggregateZone, optional): The zone of fine aggregate as per IS 383. Defaults to FineAggregateZone.ZONE_II. fine_aggregate_surface_moisture (float, optional): The surface moisture of fine aggregate (%). Defaults to 0.0. fine_aggregate_water_absorption (float, optional): The water absorption of fine aggregate (%). Defaults to 0.0. slump_mm (float, optional): The slump of concrete (mm). Defaults to 50.0. mineral_admixture (MineralAdmixture | None, optional): The type of mineral admixture used. Defaults to None. mineral_admixture_percentage (float | None, optional): The percentage of mineral admixture used. Defaults to None. Raises: ValueError: If any of the parameters are invalid. """ self.concrete_grade = concrete_grade self.maximum_nominal_size = maximum_nominal_size self.exposure_condition = exposure_condition # slump (mm) used to adjust water content; reference table is for 50 mm self.slump_mm = float(slump_mm) if slump_mm is not None else None # fraction change per 25 mm (e.g. 0.03 == 3% change per 25 mm) self.slump_adjustment_pct_per_25mm = 0.03 self.maximum_cement_content = maximum_cement_content #TODO : implement logic self.chemical_admixture = chemical_admixture if self.chemical_admixture is None: self.chemical_admixture_percentage = 0.0 else: if chemical_admixture_percentage is None: self.chemical_admixture_percentage = float(chemical_admixture.default_percentage) else: self.chemical_admixture_percentage = float(chemical_admixture_percentage) mandatory_items = {Materials.CEMENT, Materials.COARSE_AGGREGATE, Materials.WATER, Materials.FINE_AGGREGATE} # Accept either a list[SpecificGravity] or a dict[Materials, SpecificGravity] if isinstance(specific_gravities, dict): sg_map = specific_gravities else: sg_map = {sg.material: sg for sg in specific_gravities} if not mandatory_items.issubset(set(sg_map.keys())): raise ValueError("Missing mandatory specific gravities.") self.specific_gravities = sg_map self.is_pumpable = is_pumpable self.coarse_aggregate_type = coarse_aggregate_type self.coarse_aggregate_water_absorption = coarse_aggregate_water_absorption self.coarse_aggregate_surface_moisture = coarse_aggregate_surface_moisture self.fine_aggregate_zone = fine_aggregate_zone self.fine_aggregate_surface_moisture = fine_aggregate_surface_moisture self.fine_aggregate_water_absorption = fine_aggregate_water_absorption self.mineral_admixture = mineral_admixture #TODO : implement logic # resolved percentage: user-provided overrides enum default if self.mineral_admixture is None: self.mineral_admixture_percentage = 0.0 else: if mineral_admixture_percentage is None: self.mineral_admixture_percentage = float(mineral_admixture.default_percentage) else: self.mineral_admixture_percentage = float(mineral_admixture_percentage)
def __calculate_water_cement_ratio_by_is456(self, reinforced: bool = True) -> float: """ Determine maximum water/cement ratio from IS456 Table 5 for normal-weight aggregates of 20 mm nominal maximum size. Returns and sets self.maximum_water_cement_ratio. Table values (maximum free water-cement ratio): - Plain concrete: Mild 0.60, Moderate 0.60, Severe 0.50, Very severe 0.45, Extreme 0.40 - Reinforced concrete:Mild 0.55, Moderate 0.50, Severe 0.45, Very severe 0.45, Extreme 0.40 """ if self.exposure_condition is None: raise ValueError("exposure_condition must be set to determine water/cement ratio") plain_map = { ExposureCondition.MILD: 0.60, ExposureCondition.MODERATE: 0.60, ExposureCondition.SEVERE: 0.50, ExposureCondition.VERY_SEVERE: 0.45, ExposureCondition.EXTREME: 0.40, } reinforced_map = { ExposureCondition.MILD: 0.55, ExposureCondition.MODERATE: 0.50, ExposureCondition.SEVERE: 0.45, ExposureCondition.VERY_SEVERE: 0.45, ExposureCondition.EXTREME: 0.40, } mapping = reinforced_map if reinforced else plain_map wcr = mapping[self.exposure_condition] self.initial_water_cement_ratio=wcr if self.chemical_admixture == ChemicalAdmixture.SUPERPLASTICIZER: wcr -= 0.05 self.water_cement_ratio = float(wcr) if getattr(self, "_display_flag", False): print("\n" + "-"*60) print("Water / Cement Ratio (IS456 Table 5)") print("-"*60) print(f"Exposure condition : {self.exposure_condition.value}") print(f"Reinforced member : {'Yes' if reinforced else 'No'}") print(f"Calculated W/C : {self.water_cement_ratio:.3f}") print("-"*60) return self.water_cement_ratio def __calculate_water_content(self): """ Determine reference water content (kg/m^3) based on nominal maximum aggregate size (IS table). 10 mm -> 208, 20 mm -> 186, 40 mm -> 165 Stores and returns self.maximum_water_content. Reference table values correspond to 50 mm slump. If self.slump_mm is provided the water content is adjusted by slump_adjustment_pct_per_25mm for every 25 mm difference from 50 mm. If superplasticizer is used, the water content is reduced by 20%. """ if self.maximum_nominal_size is None: raise ValueError("maximum_nominal_size must be set to determine water content") mapping = { MaximumNominalSize.SIZE_10: 208.0, MaximumNominalSize.SIZE_20: 186.0, MaximumNominalSize.SIZE_40: 165.0, } base_water = float(mapping[self.maximum_nominal_size]) if self.slump_mm is not None: steps = (self.slump_mm - 50.0) / 25.0 # positive if slump > 50, negative if < 50 adjusted_water = base_water * (1.0 + steps * self.slump_adjustment_pct_per_25mm) else: adjusted_water = base_water if self.chemical_admixture == ChemicalAdmixture.SUPERPLASTICIZER: adjusted_water *= (1 - self.chemical_admixture_percentage / 100) adjusted_water = round(adjusted_water) self.maximum_water_content = adjusted_water if getattr(self, "_display_flag", False): print("\n" + "-"*60) print("Water Content Determination") print("-"*60) print(f"Maximum nominal aggregate size : {self.maximum_nominal_size.value} mm") print(f"Base water content (50 mm slump): {base_water:.2f} litres") if self.slump_mm is not None: pct_change = (self.slump_adjustment_pct_per_25mm * 100.0) print(f"Slump provided : {self.slump_mm:.1f} mm") print(f"Adjustment per 25 mm : {pct_change:.1f}%") print(f"Adjusted water content : {adjusted_water:.2f} litres") if self.chemical_admixture == ChemicalAdmixture.SUPERPLASTICIZER: print(f"Superplasticizer used: water reduced by {self.chemical_admixture_percentage}%") print("-"*60) return adjusted_water def __calculate_target_mean_compressive_strength(self) -> float: """ Compute target mean compressive strength for the selected concrete grade. Uses the standard deviations from the provided table: - M10, M15 : 3.5 N/mm^2 - M20, M25 : 4.0 N/mm^2 - M30, M35, M40, M45, M50, M55 : 5.0 N/mm^2 Formula used: target_mean = f_ck + 1.65 * standard_deviation (1.65 corresponds to approximately 95% probability / 5% risk) """ std_dev_map = { ConcreteGrade.M10: 3.5, ConcreteGrade.M15: 3.5, ConcreteGrade.M20: 4.0, ConcreteGrade.M25: 4.0, ConcreteGrade.M30: 5.0, ConcreteGrade.M35: 5.0, ConcreteGrade.M40: 5.0, ConcreteGrade.M45: 5.0, ConcreteGrade.M50: 5.0, ConcreteGrade.M55: 5.0, } s = std_dev_map[self.concrete_grade] try: fck = int(str(self.concrete_grade.value).lstrip("M").strip()) except Exception: fck = int(self.concrete_grade.name.lstrip("M")) target_mean = fck + 1.65 * s self.characteristic_strength = fck self.standard_deviation = s self.target_mean_compressive_strength = target_mean if getattr(self, "_display_flag", False): print("\n" + "-"*60) print("Target Mean Compressive Strength") print("-"*60) print(f"Concrete grade (f_ck) : {self.concrete_grade.value} => {fck} N/mm^2") print(f"Standard deviation : {s:.2f} N/mm^2") print(f"Target mean strength : {target_mean:.2f} N/mm^2") print("-"*60) return target_mean def __calculate_cement_content(self, water_cement_ratio, water_content): MINIMUM_CEMENT_CONTENT_BY_EXPOSURE = { ExposureCondition.MILD: 300.0, ExposureCondition.MODERATE: 300.0, ExposureCondition.SEVERE: 320.0, ExposureCondition.VERY_SEVERE: 340.0, ExposureCondition.EXTREME: 360.0, } minimum_cement_content = MINIMUM_CEMENT_CONTENT_BY_EXPOSURE[self.exposure_condition] cement_content= water_content / water_cement_ratio if cement_content < minimum_cement_content: warnings.warn( f"Calculated cement content {cement_content:.2f} kg/m^3 is less than minimum required for exposure condition {minimum_cement_content:.2f} kg/m^3", UserWarning ) self.minimum_cement_content = max(cement_content, minimum_cement_content) if getattr(self, "_display_flag", False): print("\n" + "-"*60) print("Cement Content Calculation") print("-"*60) print(f"Computed cement (water / w/c) : {cement_content:.2f} kg/m^3") print(f"Minimum cement for exposure : {minimum_cement_content:.2f} kg/m^3") print(f"Final cement content selected : {self.minimum_cement_content:.2f} kg/m^3") print("-"*60) return self.minimum_cement_content def __calculate_cement_with_flyash_content(self, water_cement_ratio, water_content): MINIMUM_CEMENT_CONTENT_BY_EXPOSURE = { ExposureCondition.MILD: 300.0, ExposureCondition.MODERATE: 300.0, ExposureCondition.SEVERE: 320.0, ExposureCondition.VERY_SEVERE: 340.0, ExposureCondition.EXTREME: 360.0, } minimum_cement_content = MINIMUM_CEMENT_CONTENT_BY_EXPOSURE[self.exposure_condition] cement_content = water_content / water_cement_ratio if cement_content < minimum_cement_content: warnings.warn( f"Calculated cement content {cement_content:.2f} kg/m^3 is less than minimum required for exposure condition {minimum_cement_content:.2f} kg/m^3", UserWarning ) # initial minimum to compare with after using cementitious materials initial_minimum_cement_content = max(cement_content, minimum_cement_content) # assume cementitious material content increased by 10% to account for replacement chemistry cementitious_material_content = cement_content * 1.10 # new effective water/cement_ratio based on total cementitious content new_water_cement_ratio = water_content / cementitious_material_content # determine fly ash replacement mass (use resolved mineral_admixture_percentage if provided) replacement_fraction = (self.mineral_admixture_percentage / 100.0) if self.mineral_admixture_percentage > 0 else 0.30 fly_ash_content = math.floor(cementitious_material_content * replacement_fraction) new_cement_content = cementitious_material_content - fly_ash_content cement_savings = initial_minimum_cement_content - new_cement_content # store computed values on the instance for later reporting self.fly_ash_content = float(fly_ash_content) self.cementitious_material_content = float(cementitious_material_content) self.new_water_cement_ratio = float(new_water_cement_ratio) self.new_cement_content = float(new_cement_content) self.cement_savings = float(cement_savings) # update the working minimum cement and water/cement ratio self.minimum_cement_content = self.new_cement_content #self.water_cement_ratio = self.new_water_cement_ratio if getattr(self, "_display_flag", False): print("\n" + "-"*60) print("Cement + Fly Ash (Mineral Admixture) Calculation") print("-"*60) print(f"Computed cement (water / w/c) : {cement_content:.2f} kg/m^3") print(f"Minimum cement for exposure : {minimum_cement_content:.2f} kg/m^3") print(f"Initial cement selected (base) : {initial_minimum_cement_content:.2f} kg/m^3") print(f"Cementitious material (cement + others) : {self.cementitious_material_content:.2f} kg/m^3") print(f"Fly ash replacement (mass) : {self.fly_ash_content:.2f} kg/m^3 ({replacement_fraction*100:.1f}%)") print(f"Final cement content after replacement : {self.new_cement_content:.2f} kg/m^3") print(f"Cement savings : {self.cement_savings:.2f} kg/m^3") print(f"Adjusted water/cement ratio (effective) : {self.new_water_cement_ratio:.3f}") print("-"*60) return self.minimum_cement_content,self.fly_ash_content def __calculate_aggregate_content(self): """Determine volume fraction of coarse aggregate per unit volume of total aggregate from IS table (Table 3) depending on fine aggregate zone and nominal maximum size. Raises: ValueError: If fine_aggregate_zone or maximum_nominal_size is not set. ValueError: If unsupported combination of maximum_nominal_size and fine_aggregate_zone is used. Returns: float: Proportion of coarse aggregate. """ if self.fine_aggregate_zone is None or self.maximum_nominal_size is None: raise ValueError("fine_aggregate_zone and maximum_nominal_size must be set to determine coarse aggregate proportion") table = { MaximumNominalSize.SIZE_10: { FineAggregateZone.ZONE_IV: 0.50, FineAggregateZone.ZONE_III: 0.48, FineAggregateZone.ZONE_II: 0.46, FineAggregateZone.ZONE_I: 0.44, }, MaximumNominalSize.SIZE_20: { FineAggregateZone.ZONE_IV: 0.66, FineAggregateZone.ZONE_III: 0.64, FineAggregateZone.ZONE_II: 0.62, FineAggregateZone.ZONE_I: 0.60, }, MaximumNominalSize.SIZE_40: { FineAggregateZone.ZONE_IV: 0.75, FineAggregateZone.ZONE_III: 0.73, FineAggregateZone.ZONE_II: 0.71, FineAggregateZone.ZONE_I: 0.69, }, } try: prop = table[self.maximum_nominal_size][self.fine_aggregate_zone] except KeyError: raise ValueError("unsupported combination of maximum_nominal_size and fine_aggregate_zone") self.coarse_aggregate_proportion = float(prop) if self.water_cement_ratio == 0.5: self.coarse_aggregate_proportion = float(prop) elif self.water_cement_ratio < 0.5: decrease = abs(round((0.5 - self.water_cement_ratio) / 0.05)) self.coarse_aggregate_proportion = float(prop) + decrease * 0.01 else: increase = abs(round((self.water_cement_ratio - 0.5) / 0.05)) self.coarse_aggregate_proportion = float(prop) - increase * 0.01 if self.is_pumpable: self.coarse_aggregate_proportion = round(self.coarse_aggregate_proportion * 0.9, 2) fine_aggregate_proportion = 1 - self.coarse_aggregate_proportion self.fine_aggregate_proportion = float(fine_aggregate_proportion) if getattr(self, "_display_flag", False): print("\n" + "-"*60) print("Aggregate Proportions (by volume)") print("-"*60) print(f"Fine aggregate zone : {self.fine_aggregate_zone.value}") print(f"Nominal max size (mm) : {self.maximum_nominal_size.value}") print(f"Base coarse proportion (table) : {prop:.3f}") print(f"W/C ratio : {self.water_cement_ratio:.3f}") print(f"Adjusted coarse proportion : {self.coarse_aggregate_proportion:.3f}") if self.is_pumpable: print("Pumpable mix adjustment applied: coarse proportion reduced by 10%") print(f"Fine aggregate proportion : {self.fine_aggregate_proportion:.3f}") print("-"*60) return self.coarse_aggregate_proportion, self.fine_aggregate_proportion
[docs] def calculate_volume_based_on_mass_and_specific_gravity(self,mass,specific_gravity,round_value=3): """Calculate the volume based on mass and specific gravity. Args: mass (float): The mass of the material. specific_gravity (float): The specific gravity of the material. round_value (int): The number of decimal places to round the result. Returns: float: The volume of the material. """ volume = (mass / specific_gravity) * (1/1000) return round(volume, round_value)
def __get_specific_gravity(self, material: Materials) -> float: """Return the numeric specific gravity for a given material (raises if missing).""" sg = self.specific_gravities.get(material) if sg is None: raise ValueError(f"specific gravity for {material} not provided") return float(sg.value)
[docs] def compute_mix_design(self, display_result: bool = False): """Compute the concrete mix design based on IS 456 and IS 10262. Args: display_result (bool, optional): Whether to display the calculation results. Defaults to False. Returns: dict: A dictionary containing the mix design parameters. """ self._display_flag = bool(display_result) if self._display_flag: print("\n" + "="*60) print("Concrete Mix Design - Calculation Summary") print("="*60) target_mean_strength = self.__calculate_target_mean_compressive_strength() water_cement_ratio = self.__calculate_water_cement_ratio_by_is456() water_content = self.__calculate_water_content() if self.mineral_admixture == MineralAdmixture.FLY_ASH: # call dedicated cement-with-flyash path which updates w/c and cement quantities cement_content, fly_ash_content = self.__calculate_cement_with_flyash_content(water_cement_ratio, water_content) else: cement_content = self.__calculate_cement_content(water_cement_ratio, water_content) fly_ash_content = 0.0 coarse_aggregate_proportion, fine_aggregate_proportion = self.__calculate_aggregate_content() volume_of_concrete=1 volume_of_cement = self.calculate_volume_based_on_mass_and_specific_gravity( cement_content, self.__get_specific_gravity(Materials.CEMENT) ) # determine fly ash volume — Materials may not include FLY_ASH; default to 1.0 only for fly ash fly_ash_member = getattr(MineralAdmixture, "FLY_ASH", None) if fly_ash_member is not None: try: fly_ash_sg = self.__get_specific_gravity(fly_ash_member) except Exception: fly_ash_sg = 1.0 else: fly_ash_sg = 1.0 volume_of_fly_ash = self.calculate_volume_based_on_mass_and_specific_gravity( fly_ash_content, fly_ash_sg ) volume_of_water = self.calculate_volume_based_on_mass_and_specific_gravity( water_content, self.__get_specific_gravity(Materials.WATER) ) # admixture content is percentage of cement content (use resolved percentage) self.admixture_content = cement_content * 0.02 volume_of_admixture = self.calculate_volume_based_on_mass_and_specific_gravity( self.admixture_content, self.__get_specific_gravity(Materials.ADMIXTURE) ) if fly_ash_member is not None: volume_of_all_in_aggregate = 1 - volume_of_cement - volume_of_fly_ash - volume_of_water - volume_of_admixture else: volume_of_all_in_aggregate = 1 - volume_of_cement - volume_of_water - volume_of_admixture self.coarse_aggregate_content = volume_of_all_in_aggregate * coarse_aggregate_proportion \ * self.__get_specific_gravity(Materials.COARSE_AGGREGATE) * 1000 self.fine_aggregate_content = volume_of_all_in_aggregate * fine_aggregate_proportion \ * self.__get_specific_gravity(Materials.FINE_AGGREGATE) * 1000 coarse_aggregate_water_absorption = self.coarse_aggregate_content * self.coarse_aggregate_water_absorption * 0.01 fine_aggregate_water_absorption = self.fine_aggregate_content * self.fine_aggregate_water_absorption * 0.01 coarse_aggregate_surface_moisture = self.coarse_aggregate_content * self.coarse_aggregate_surface_moisture * 0.01 fine_aggregate_surface_moisture = self.fine_aggregate_content * self.fine_aggregate_surface_moisture * 0.01 free_water_after_correction = water_content + coarse_aggregate_water_absorption + fine_aggregate_water_absorption - coarse_aggregate_surface_moisture - fine_aggregate_surface_moisture self.aggregate_absorbed_water = {"coarse": coarse_aggregate_water_absorption, "fine": fine_aggregate_water_absorption} self.aggregate_surface_moisture = {"coarse": coarse_aggregate_surface_moisture, "fine": fine_aggregate_surface_moisture} self.free_water_after_correction = float(free_water_after_correction) volume_of_free_water = self.calculate_volume_based_on_mass_and_specific_gravity( free_water_after_correction, self.__get_specific_gravity(Materials.WATER) ) self.volume_of_free_water = float(volume_of_free_water) self.coarse_aggregate_volume = self.calculate_volume_based_on_mass_and_specific_gravity( self.coarse_aggregate_content, self.__get_specific_gravity(Materials.COARSE_AGGREGATE) ) self.fine_aggregate_volume = self.calculate_volume_based_on_mass_and_specific_gravity( self.fine_aggregate_content, self.__get_specific_gravity(Materials.FINE_AGGREGATE) ) if self._display_flag: print("\n" + "-"*60) print("Final Mix Quantities (per m^3 of concrete)") print("-"*60) print(f"{'Component':<20} | {'Mass (kg/m^3)':>15} | {'Volume (m^3)':>12}") print("-"*60) # volumes were calculated earlier print(f"{'Cement':<20} | {cement_content:15.2f} | {volume_of_cement:12.4f}") if fly_ash_member is not None: print(f"{'Fly Ash':<20} | {fly_ash_content:15.2f} | {volume_of_fly_ash:12.4f}") print(f"{'Water':<20} | {water_content:15.2f} | {volume_of_water:12.4f}") print(f"{'Admixture':<20} | {self.admixture_content:15.2f} | {volume_of_admixture:12.4f}") print(f"{'Coarse aggregate':<20} | {self.coarse_aggregate_content:15.2f} | {self.coarse_aggregate_volume:12.4f}") print(f"{'Fine aggregate':<20} | {self.fine_aggregate_content:15.2f} | {self.fine_aggregate_volume:12.4f}") print("-"*60) # reference only: free water after absorption/surface moisture corrections print(f"After absorption and surface moisture adjustments, free water available: " f"{self.free_water_after_correction:.2f} kg " f"({self.volume_of_free_water:.4f} m^3)") # additional reference: aggregate absorbed water and surface moisture (kg) print(f"Coarse aggregate absorbed water : {coarse_aggregate_water_absorption:.2f} kg") print(f"Fine aggregate absorbed water : {fine_aggregate_water_absorption:.2f} kg") print(f"Coarse aggregate surface moisture: {coarse_aggregate_surface_moisture:.2f} kg") print(f"Fine aggregate surface moisture: {fine_aggregate_surface_moisture:.2f} kg") print("-"*60) # clear display flag self._display_flag = False return { "summary": { "target_mean_strength_N_per_mm2": "self.target_mean_compressive_strength", "water_cement_ratio": "self.water_cement_ratio" }, "mix_per_m3": { "components": { "cement": { "mass_kg": cement_content, "volume_m3": volume_of_cement, "specific_gravity": self.__get_specific_gravity(Materials.CEMENT) }, "fly_ash": { "mass_kg": fly_ash_content, "volume_m3": volume_of_fly_ash, "specific_gravity": fly_ash_sg }, "water": { "mass_kg": water_content, "volume_m3": volume_of_water, "specific_gravity": self.__get_specific_gravity(Materials.WATER) }, "admixture": { "mass_kg": self.admixture_content, "volume_m3": volume_of_admixture, "specific_gravity": self.__get_specific_gravity(Materials.ADMIXTURE), "type": getattr(self.chemical_admixture, "name", None) }, "coarse_aggregate": { "mass_kg": self.coarse_aggregate_content, "volume_m3": self.coarse_aggregate_volume, "specific_gravity": self.__get_specific_gravity(Materials.COARSE_AGGREGATE), "volume_proportion": self.coarse_aggregate_proportion }, "fine_aggregate": { "mass_kg": self.fine_aggregate_content, "volume_m3": self.fine_aggregate_volume, "specific_gravity": self.__get_specific_gravity(Materials.FINE_AGGREGATE), "volume_proportion": self.fine_aggregate_proportion } }, "total_concrete_volume_m3": 1.0 }, "aggregate_adjustments_kg": { "coarse_absorbed_water": self.aggregate_absorbed_water['coarse'], "fine_absorbed_water": self.aggregate_absorbed_water['fine'], "coarse_surface_moisture": self.aggregate_surface_moisture['coarse'], "fine_surface_moisture": self.aggregate_surface_moisture['fine'], "free_water_after_correction": self.free_water_after_correction }, "provenance": { "maximum_nominal_size_mm": self.maximum_nominal_size.value, "fine_aggregate_zone": self.fine_aggregate_zone.value, "is_pumpable": self.is_pumpable, "slump_mm": self.slump_mm, "chemical_admixture": getattr(self.chemical_admixture, "value", None), "chemical_admixture_percentage": self.chemical_admixture_percentage, "mineral_admixture": getattr(self.mineral_admixture, "value", None), "mineral_admixture_percentage": self.mineral_admixture_percentage } }
[docs] def compute_mix_design_for_volume(self, volume_m3: float, display_result: bool = False): """Compute mix quantities for a specified volume (multiples of the 1 m^3 design). This method calls compute_mix_design() to obtain quantities per 1 m^3 and scales the masses and volumes by the requested volume_m3. Args: volume_m3 (float): Target concrete volume in m^3 (must be > 0). display_result (bool, optional): Whether to print a summary similar to compute_mix_design. Returns: dict: A dictionary containing scaled mix design parameters for the requested volume. """ volume_m3 = float(volume_m3) if volume_m3 <= 0: raise ValueError("volume_m3 must be greater than zero") # obtain base design for 1 m^3 (suppress its display to control output here) base = self.compute_mix_design(display_result=False) scale = volume_m3 base_components = base["mix_per_m3"]["components"] scaled_components = {} for name, comp in base_components.items(): mass = comp.get("mass_kg", 0.0) or 0.0 vol = comp.get("volume_m3", 0.0) or 0.0 # copy and scale comp_scaled = dict(comp) comp_scaled["mass_kg"] = mass * scale comp_scaled["volume_m3"] = vol * scale scaled_components[name] = comp_scaled # scale aggregate adjustments (kg) agg_adj = base.get("aggregate_adjustments_kg", {}) scaled_agg_adj = {k: (v * scale if isinstance(v, (int, float)) else v) for k, v in agg_adj.items()} result = { "summary": base["summary"], "mix_for_volume_m3": { "components": scaled_components, "total_concrete_volume_m3": scale }, "aggregate_adjustments_kg": scaled_agg_adj, "provenance": base["provenance"] } if display_result: print("\n" + "=" * 60) print(f"Concrete Mix Design - Quantities for {scale:.3f} m^3") print("=" * 60) print("\n" + "-" * 60) print("Final Mix Quantities") print("-" * 60) print(f"{'Component':<20} | {'Mass (kg)':>15} | {'Volume (m^3)':>12}") print("-" * 60) for comp_name in ("cement", "fly_ash", "water", "admixture", "coarse_aggregate", "fine_aggregate"): comp = scaled_components.get(comp_name) if comp is None: continue print(f"{comp_name.replace('_', ' ').title():<20} | {comp['mass_kg']:15.2f} | {comp['volume_m3']:12.4f}") print("-" * 60) free_water = scaled_agg_adj.get("free_water_after_correction") if free_water is not None: vol_free_water = free_water / self.__get_specific_gravity(Materials.WATER) / 1000.0 print(f"After absorption and surface moisture adjustments, free water available: " f"{free_water:.2f} kg ({vol_free_water:.4f} m^3) for {scale:.3f} m^3") print(f"Coarse aggregate absorbed water : {scaled_agg_adj.get('coarse_absorbed_water', 0.0):.2f} kg") print(f"Fine aggregate absorbed water : {scaled_agg_adj.get('fine_absorbed_water', 0.0):.2f} kg") print(f"Coarse aggregate surface moisture: {scaled_agg_adj.get('coarse_surface_moisture', 0.0):.2f} kg") print(f"Fine aggregate surface moisture: {scaled_agg_adj.get('fine_surface_moisture', 0.0):.2f} kg") print("-" * 60) return result