File size: 5,768 Bytes
d323307
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import math
import gradio
import pandas
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline

# 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 Young's Modulus calculation
def youngs_modulus_calc(F_N: float, A_m2: float, L0_m: float, delta_L_m: float) -> float:
    """
    Calculates Young's Modulus (E) in GPa.
    F_N: Force in Newtons
    A_m2: Area in square meters
    L0_m: Original length in meters
    delta_L_m: Change in length in meters
    """
    if A_m2 == 0 or L0_m == 0 or delta_L_m == 0:
        return math.inf  # Avoid division by zero
    stress_Pa = F_N / A_m2
    strain = delta_L_m / L0_m
    E_Pa = stress_Pa / strain
    E_GPa = E_Pa / 1e9
    return E_GPa

# 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(E_GPa: float, inputs: list) -> str:
    system_prompt = (
        "Explain engineering concepts to a smart 5-year-old using food analogies. "
        "Keep responses concise, only one sentence."
    )

    F_N, A_m2, L0_m, delta_L_m = inputs

    aluminum_E_GPa = 70.0
    comparison = "more flexible" if E_GPa < aluminum_E_GPa else "stiffer" if E_GPa > aluminum_E_GPa else "about as stiff"


    user_prompt = (
        f"An unknown material was tested with a force of {F_N:g} N, area of {A_m2:g} m², original length of {L0_m:g} m, and change in length of {delta_L_m:g} m.\n"
        f"The unknown material's Young's Modulus is {E_GPa:.2f} GPa.\n"
        f"Aluminum's Young's Modulus is {aluminum_E_GPa} GPa.\n"
        f"Explain if this material is {comparison} than aluminum."
        "Explain the Young's modulus of the unknown material and the compare it to aluminum 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(F_N, A_m2, L0_m, delta_L_m):
    # Young's Modulus calculation part
    youngs_modulus_inputs = [F_N, A_m2, L0_m, delta_L_m]
    youngs_modulus_result = youngs_modulus_calc(
        F_N=float(F_N),
        A_m2=float(A_m2),
        L0_m=float(L0_m),
        delta_L_m=float(delta_L_m)
    )

    aluminum_E_GPa = 70.0
    stiffness_comparison = "stiff" if youngs_modulus_result > aluminum_E_GPa else "flexible" if youngs_modulus_result < aluminum_E_GPa else "similar stiffness"

    youngs_modulus_df = pandas.DataFrame([{
        "Young's Modulus [GPa]": round(youngs_modulus_result, 3),
        "Compared to Aluminum": stiffness_comparison
        }])

    youngs_modulus_narrative = llm_explain(youngs_modulus_result, youngs_modulus_inputs).split("\n")[0]


    return youngs_modulus_df, youngs_modulus_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 Young's Modulus Calculation"
    )
    gradio.Markdown(
        "This app calculates Young's Modulus and provides a natural language description of the result, comparing it to aluminum."
    )

    # This row contains all of the physical parameters for Young's Modulus
    with gradio.Row():
        F_N = gradio.Number(value=1000.0, label="Force [N]")
        A_m2 = gradio.Number(value=0.01, label="Area [m²]")
        L0_m = gradio.Number(value=1.0, label="Original Length [m]")
        delta_L_m = gradio.Number(value=0.001, label="Change in Length [m]")

    # Add a button to click to run the interface
    youngs_modulus_run_btn = gradio.Button("Compute Young's Modulus")

    # These are the outputs for Young's Modulus
    youngs_modulus_results_df = gradio.Dataframe(label="Numerical results (deterministic)", interactive=False)
    youngs_modulus_explain_md = gradio.Markdown(label="Explanation")

    # Run the calculations when the button is clicked
    youngs_modulus_run_btn.click(
        fn=run_once,
        inputs=[F_N, A_m2, L0_m, delta_L_m],
        outputs=[youngs_modulus_results_df, youngs_modulus_explain_md]
    )

    # Finally, add a few examples
    gradio.Examples(
        examples=[
            [1000000.0, 0.001, 1.0, 0.01], # Example 1 (should be around 100 GPa - stiffer than aluminum)
            [50000.0, 0.01, 1.0, 0.0002], # Example 2 (should be around 25 GPa - more flexible than aluminum)
            [700000.0, 0.01, 1.0, 0.001], # Example 3 (should be around 70 GPa - about as stiff as aluminum)
        ],
        inputs=[F_N, A_m2, L0_m, delta_L_m],
        label="Representative Young's Modulus Cases",
        examples_per_page=3,
        cache_examples=False,
    )


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