bridge_forge / server /simulation.py
eventhorizon28's picture
Upload folder using huggingface_hub
425895b verified
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.
import math
import os
import uuid
from typing import Dict, List, Optional, Tuple
from anastruct import SystemElements
try:
from ..models import MATERIALS
except (ImportError, ValueError):
from models import MATERIALS
def _member_length(n1: Dict, n2: Dict) -> float:
return math.sqrt((n2["x"] - n1["x"]) ** 2 + (n2["y"] - n1["y"]) ** 2)
def _member_mass(length: float, section_area: float, material: str) -> float:
density = MATERIALS[material]["density"]
return density * section_area * length
def _member_cost(mass_kg: float, material: str) -> float:
return mass_kg * MATERIALS[material]["cost_per_kg"]
def run_simulation(
nodes: List[Dict],
members: List[Dict],
supports: List[Dict],
loads: List[Dict],
constraints: Dict,
static_dir: str = "/tmp/bridge_forge_static",
) -> Dict:
os.makedirs(static_dir, exist_ok=True)
if not nodes or not members:
return {
"structural_status": "fail",
"max_deflection_mm": 0.0,
"max_stress_ratio": 0.0,
"failed_members": [],
"total_mass_kg": 0.0,
"cost_inr": 0.0,
"member_count": 0,
"visualization_url": "",
"errors": ["No nodes or members defined"],
}
node_map = {n["node_id"]: n for n in nodes}
ss = SystemElements()
element_ids = {}
member_props = {}
for m in members:
n1 = node_map.get(m["node_start"])
n2 = node_map.get(m["node_end"])
if n1 is None or n2 is None:
continue
material = m.get("material", "steel")
section_area = m.get("section_area", 0.01)
mat = MATERIALS.get(material, MATERIALS["steel"])
E_kn = mat["E"] / 1000
EA = E_kn * section_area
elem_id = ss.add_truss_element(
location=[[n1["x"], n1["y"]], [n2["x"], n2["y"]]],
EA=EA,
)
if isinstance(elem_id, (list, tuple)):
elem_id = elem_id[0]
element_ids[m["member_id"]] = elem_id
length = _member_length(n1, n2)
mass = _member_mass(length, section_area, material)
cost = _member_cost(mass, material)
member_props[m["member_id"]] = {
"material": material,
"section_area": section_area,
"length": length,
"mass": mass,
"cost": cost,
"yield_stress": mat["yield_stress"],
"E": mat["E"],
}
for sup in supports:
nid = sup["node_id"]
n = node_map.get(nid)
if n is None:
continue
node_id_in_ss = ss.find_node_id(vertex=[n["x"], n["y"]])
if node_id_in_ss is None:
continue
if sup["support_type"] == "pin":
ss.add_support_hinged(node_id=node_id_in_ss)
elif sup["support_type"] == "roller":
ss.add_support_roll(node_id=node_id_in_ss)
seismic_zone = constraints.get("seismic_zone", 0)
lateral_factor = {0: 0, 1: 0.02, 2: 0.04, 3: 0.06, 4: 0.10, 5: 0.16}.get(
seismic_zone, 0
)
for ld in loads:
nid = ld["node_id"]
n = node_map.get(nid)
if n is None:
continue
node_id_in_ss = ss.find_node_id(vertex=[n["x"], n["y"]])
if node_id_in_ss is None:
continue
Fx = ld.get("Fx", 0.0)
Fy = ld.get("Fy", 0.0)
if lateral_factor > 0:
Fx += abs(Fy) * lateral_factor
ss.point_load(node_id=node_id_in_ss, Fx=Fx, Fy=Fy)
try:
ss.solve()
except Exception as e:
return {
"structural_status": "fail",
"max_deflection_mm": 0.0,
"max_stress_ratio": 0.0,
"failed_members": [],
"total_mass_kg": 0.0,
"cost_inr": 0.0,
"member_count": len(members),
"visualization_url": "",
"errors": [str(e)],
}
max_deflection_m = 0.0
try:
for node_id_ss in ss.node_map:
displacements = ss.get_node_displacements(node_id=node_id_ss)
if displacements:
ux = float(displacements.get("ux", 0.0))
uy = float(displacements.get("uy", 0.0))
defl = math.sqrt(ux**2 + uy**2)
max_deflection_m = max(max_deflection_m, defl)
except Exception:
pass
max_deflection_mm = max_deflection_m * 1000
max_stress_ratio = 0.0
failed_members_list = []
for mid, eid in element_ids.items():
props = member_props[mid]
try:
element = ss.element_map.get(eid)
if element is None:
continue
N = element.N_1 if hasattr(element, "N_1") else 0.0
if N is None:
N = 0.0
N_newtons = abs(float(N)) * 1000
actual_stress = N_newtons / props["section_area"]
ratio = actual_stress / props["yield_stress"]
max_stress_ratio = max(max_stress_ratio, ratio)
if ratio > 1.0:
failed_members_list.append(mid)
except Exception:
pass
total_mass = sum(p["mass"] for p in member_props.values())
total_cost = sum(p["cost"] for p in member_props.values())
span = 0.0
if nodes:
xs = [n["x"] for n in nodes]
span = max(xs) - min(xs)
deflection_limit_m = max(span / 10, 0.5)
if max_deflection_m > deflection_limit_m:
structural_status = "fail"
elif max_stress_ratio <= 1.0 and len(failed_members_list) == 0:
structural_status = "pass"
else:
structural_status = "fail"
viz_filename = f"bridge_{uuid.uuid4().hex[:8]}.png"
viz_path = os.path.join(static_dir, viz_filename)
try:
fig = ss.show_structure(show=False, verbosity=0)
if fig is not None:
fig.savefig(viz_path, dpi=100, bbox_inches="tight")
import matplotlib.pyplot as plt
plt.close(fig)
except Exception:
viz_path = ""
return {
"structural_status": structural_status,
"max_deflection_mm": round(max_deflection_mm, 4),
"max_stress_ratio": round(max_stress_ratio, 4),
"failed_members": failed_members_list,
"total_mass_kg": round(total_mass, 2),
"cost_inr": round(total_cost, 2),
"member_count": len(members),
"visualization_url": f"/static/{viz_filename}" if viz_path else "",
}