File size: 7,390 Bytes
fe5061b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# 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)