# app.py # Busbar sizing Gradio app # Run locally: pip install -r requirements.txt ; python app.py # To deploy on Hugging Face Spaces: push this file + requirements.txt to your space repo. import math import gradio as gr import pandas as pd # Material properties and default current densities (A/mm^2) # These are conservative engineering defaults — adjust per your standards/practice. MATERIALS = { "Copper": { "resistivity": 1.724e-8, # ohm·m at 20°C "J_natural": 1.6, # A/mm^2 (natural convection) "J_forced": 2.5, # A/mm^2 (forced air / ventilated) }, "Aluminium": { "resistivity": 2.82e-8, # ohm·m at 20°C "J_natural": 0.9, "J_forced": 1.4, } } # Common commercial thicknesses (mm) and widths (mm) to propose (you can extend) COMMON_THICKNESSES = [3, 5, 6, 8, 10, 12, 15] # mm COMMON_WIDTHS = [10, 12, 15, 20, 25, 30, 40, 50, 60, 80, 100, 120, 150, 200] # mm def nearest_busbar_size(required_area_mm2, thicknesss=COMMON_THICKNESSES, widths=COMMON_WIDTHS): """ Find the smallest commercial thickness x width combination whose area >= required_area_mm2. Returns a list of candidate dicts sorted ascending by area. """ candidates = [] for t in thicknesss: for w in widths: area = t * w # mm^2 for rectangular busbar if area >= required_area_mm2: candidates.append({ "thickness_mm": t, "width_mm": w, "area_mm2": area }) # if none found (very large current), return a few top oversized combos if not candidates: # create a fallback by scaling up widths scale = 2 for t in thicknesss: w = widths[-1] * scale candidates.append({"thickness_mm": t, "width_mm": w, "area_mm2": t * w}) candidates.sort(key=lambda x: x["area_mm2"]) return candidates[:6] candidates.sort(key=lambda x: (x["area_mm2"], x["thickness_mm"], x["width_mm"])) return candidates[:6] # top 6 suggestions def size_busbar(current_A: float, material: str = "Copper", cooling: str = "Natural (no forced ventilation)", select_thickness: int = None, prefer_forced: bool = False, voltage_V: float = 415.0): """ Main calculation function for busbar sizing. """ # Input validation / fixups if current_A <= 0: return "Current must be positive.", None, None mat = MATERIALS.get(material) if mat is None: return f"Unknown material: {material}", None, None # Choose current density J (A/mm^2) if "forced" in cooling.lower() or prefer_forced: J = mat["J_forced"] cooling_label = "Forced ventilation" else: J = mat["J_natural"] cooling_label = "Natural convection" required_area_mm2 = current_A / J # mm^2 # If user selected a thickness, compute required width suggestions = [] if select_thickness in COMMON_THICKNESSES: t = select_thickness required_width = math.ceil(required_area_mm2 / t) # round up to nearest standard width std_width = next((w for w in COMMON_WIDTHS if w >= required_width), required_width) area_actual = t * std_width suggestions.append({ "thickness_mm": t, "width_mm": std_width, "area_mm2": area_actual }) else: suggestions = nearest_busbar_size(required_area_mm2) # pick the best suggestion (smallest area) best = suggestions[0] # Resistance per meter (DC approximation) area_m2 = best["area_mm2"] * 1e-6 # mm2 -> m2 resistivity = mat["resistivity"] R_per_m = resistivity / area_m2 # ohm per meter voltage_drop_per_m_V = R_per_m * current_A percent_drop_per_m = (voltage_drop_per_m_V / voltage_V) * 100 # Build outputs summary = ( f"Design current: {current_A:.1f} A\n" f"Material: {material}\n" f"Cooling: {cooling_label}\n" f"Design current density used: {J:.2f} A/mm²\n" f"Required cross-sectional area (ideal): {required_area_mm2:.2f} mm²\n\n" f"Recommended busbar (best match): {best['thickness_mm']} mm thick × {best['width_mm']} mm wide\n" f" -> Cross-sectional area: {best['area_mm2']:.1f} mm²\n\n" f"Estimated DC resistance: {R_per_m:.6f} Ω/m\n" f"Voltage drop at {current_A:.1f} A: {voltage_drop_per_m_V:.3f} V/m ({percent_drop_per_m:.4f}% of {voltage_V} V per meter)\n\n" "Note: This is a simplified design aid. Verify with thermal/mechanical checks, short-circuit stability, standards (IEC/NEC/IEC 61439 etc.), and local practice." ) # Prepare a DataFrame of the suggestions for display df = pd.DataFrame(suggestions) df = df[["thickness_mm", "width_mm", "area_mm2"]] df.columns = ["Thickness (mm)", "Width (mm)", "Area (mm²)"] return summary, df, { "required_area_mm2": round(required_area_mm2, 2), "chosen_thickness_mm": best["thickness_mm"], "chosen_width_mm": best["width_mm"], "chosen_area_mm2": best["area_mm2"], "resistance_ohm_per_m": R_per_m, "voltage_drop_V_per_m": voltage_drop_per_m_V } # ---- Gradio UI ---- with gr.Blocks(title="Busbar sizing calculator") as demo: gr.Markdown("# Busbar sizing — Gradio app\nEnter design current and choices; app suggests rectangular busbar sizes.") with gr.Row(): with gr.Column(): current_in = gr.Number(label="Design current (A)", value=400, precision=1) material_in = gr.Radio(choices=list(MATERIALS.keys()), value="Copper", label="Material") cooling_in = gr.Radio(choices=["Natural (no forced ventilation)", "Forced ventilation"], value="Natural (no forced ventilation)", label="Cooling") prefer_forced = gr.Checkbox(label="Prefer forced-ventilation values (override cooling)", value=False) thickness_in = gr.Dropdown(choices=["Auto (choose best)"] + [str(t) for t in COMMON_THICKNESSES], value="Auto (choose best)", label="Prefer thickness (mm) - optional") voltage_in = gr.Number(label="Nominal system voltage (V) — for % drop calc", value=415) run_btn = gr.Button("Calculate") with gr.Column(): output_text = gr.Textbox(label="Summary", interactive=False, lines=10) output_table = gr.Dataframe(label="Suggested commercial sizes", interactive=False) output_json = gr.JSON(label="Raw results (for engineering use)") def on_calculate(current, material, cooling, prefer_forced, thickness_choice, voltage): sel_thickness = None if thickness_choice and thickness_choice != "Auto (choose best)": try: sel_thickness = int(thickness_choice) except: sel_thickness = None summary, df, raw = size_busbar(current, material, cooling, sel_thickness, prefer_forced, voltage) return summary, df, raw run_btn.click( on_calculate, inputs=[current_in, material_in, cooling_in, prefer_forced, thickness_in, voltage_in], outputs=[output_text, output_table, output_json] ) if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=7860)