File size: 6,741 Bytes
71f597c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4166e25
71f597c
 
 
4166e25
71f597c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3ac5798
71f597c
 
3ac5798
 
71f597c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import math # For access to infinity

import gradio # For building the interface
import pandas # For working with tables

from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline # For LLMS

# Instantiate the model that we'll be calling. This is a tiny one!
MODEL_ID = "HuggingFaceTB/SmolLM2-135M-Instruct"
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
pipe = pipeline(
    task="text-generation",
    model=AutoModelForCausalLM.from_pretrained(
        MODEL_ID,
    ),
    tokenizer=tokenizer
)

# Create a function to do the calculations
def expansion_calc(L0: float, alpha: float, Temp0: float, Temp1: float, Clearance: float) -> dict:
    """
    1D thermal expansion. Given inputs: L0 = original length of rod.
    alpha = coefficient of thermal expansion. Temp0 = initial temperature.
    Temp1 = final temperature. Clearance = length the rod is able to expand before failure.
    Returns final length of the rod, and determines whether it is safely within clearance.
    """
    #input validation
    if Temp1 < Temp0:
        print("Error: Final temperature is lower than initial temperature.")
        return
    elif L0 < 0:
        print("Error: Initial Length must be greater than 0")
        return
    elif alpha < 0:
        print("Error: Coefficient of thermal expansion must be greater than 0")
        return
    elif Clearance < 0:
        print("Error: Clearance must be greater than 0")
        return

    # Calculations
    dT = Temp1 - Temp0
    convert_alpha = 1e-6 * alpha #convert to ℃^-1
    convert_Clearance = Clearance * 1e-3 #convert to m
    dL = L0*convert_alpha*dT
    L1 = L0+dL
    expansion_ok = dL < convert_Clearance

    return dict(
        results={
            "Final_Length": L1,
            "Change_in_Length": dL,
        },
        verdict={
            "passes_expansion": bool(expansion_ok),
            "service_message": (
                "OK: deflection is less than limit" if expansion_ok
                else "Not OK: deflection is greater than limit"
            ),
        },
    )

# This helper function applies a chat format to help the LLM understand what
# is going on
def _format_chat(system_prompt: str, user_prompt: str) -> str:
    messages = [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": user_prompt},
    ]
    template = getattr(tokenizer, "chat_template", None)
    return tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True
    )

# This functoin uses hte LLM to generate a response.
def _llm_generate(prompt: str, max_tokens: int) -> str:
    out = pipe(
        prompt,
        max_new_tokens=max_tokens,
        do_sample=True,
        temperature=0.5,
        return_full_text=False,
    )
    return out[0]["generated_text"]

# This function generates an explanation of the results
def llm_explain(results: dict, inputs: list) -> str:
    L0, alpha, Temp0, Temp1, Clearance = inputs
    r = results["results"]
    v = results["verdict"]

    system_prompt = (
        "You explain engineering to a smart 5-year-old. "
        "You always return CONCISE responses, only one sentence."
    )

    user_prompt = (
        f"The one-dimensional beam is initially {L0:g} m long.\n"
        f"The material has a coefficient of thermal expansion of {alpha:g} *10^-6/℃.\n"
        f"The initial temperature of the beam is {Temp0:g} degrees celsius, and the final temperature of the beam is {Temp1:g} degrees celsius.\n"
        f"The allowed clearance of the beam is {Clearance:g}.\n "
        f"The final length of the beam is {r['Final_Length']:.2f} m, and the total expansion is {r['Change_in_Length']:.2f} m.\n"
        f"This means that the thermal expansion is {v['service_message']}; "
        "Explain the verdict of whether or not the thermal expansion is OK in ONE friendly sentence for a non-expert"
        ""
    )

    formatted = _format_chat(system_prompt, user_prompt)
    return _llm_generate(formatted, max_tokens=128)


# This function ties everythign together (evaluation, LLM explanaation, output)
# And will be out main entry point for teh GUI
def run_once(L0, alpha, Temp0, Temp1, Clearance):
    inputs = [L0, alpha, Temp0, Temp1, Clearance]
    d = expansion_calc(
        L0=float(L0),
        alpha=float(alpha),
        Temp0=float(Temp0),
        Temp1=float(Temp1),
        Clearance=float(Clearance)
    )

    df = pandas.DataFrame([{
        "Final Length [m]": round(d["results"]["Final_Length"], 3),
        "Total Expansion [m]": round(d["results"]["Change_in_Length"], 3),
        "Thermal Expansion Verdict": d["verdict"]["service_message"],
    }])

    narrative = llm_explain(d, inputs).split("\n")[0]
    return df, narrative

# Last but not least, here's the UI!
with gradio.Blocks() as demo:

    # Let's start by adding a title and introduction
    gradio.Markdown(
        "# Run and Explain 1D Thermal Expansion Calcs"
    )
    gradio.Markdown(
        "This app runs some simple calculations for 1 dimensional thermal expansion and returns a natural language description of the results"
        "This assumes the rod is homogeneous and isotropic, and a constant coefficient of thermal expansion."
    )

    # This row contains all of the physical parameters
    with gradio.Row():
        L0 = gradio.Number(value=2.0, label="Initial Rod length [m]")
        alpha = gradio.Number(value=23.0, label="Coefficient of Thermal Expansion α [10^-6/℃]")

    # This row contains the material properties
    with gradio.Row():
        Temp0 = gradio.Number(value=150.0, label="Initial Temperature [℃]")
        Temp1 = gradio.Number(value=200.0, label="Final Temperature [℃]")
        Clearance = gradio.Number(value=3.0, label="Clearance [mm]")

    # Add a button to click to run the interface
    run_btn = gradio.Button("Compute")

    # These are the outputs. We use both a dataframe (for tabular info) and a markdown box
    # for info from teh LLM
    results_df = gradio.Dataframe(label="Numerical results (deterministic)", interactive=False)
    explain_md = gradio.Markdown(label="Explanation")

    # Run the calculations when the button is clicked
    run_btn.click(fn=run_once, inputs=[L0, alpha, Temp0, Temp1, Clearance], outputs=[results_df, explain_md])

    # Finally, add a few examples
    gradio.Examples(
        examples=[
            [2.0, 23.0, 150.0, 200.0, 3.0],
            [4.5, 10.8, 320.0, 450.0, 5.5],
            [3.0, 8.0, 40.0, 50.0, 0.5],
        ],
        inputs=[L0, alpha, Temp0, Temp1, Clearance],
        label="Representative cases",
        examples_per_page=3,
        cache_examples=False,
    )

if __name__ == "__main__":
    demo.launch(debug=True)