Manufacturing-MoE / runtime /robot_cell.py
rasitcesur's picture
Sync from rasitcesur/Manufacturing-MoE via hub-sync
547ac30 verified
Raw History Blame Contribute Delete
10.4 kB
"""Manufacturing-MoE shared robot-cell inverse dynamics and energy accounting.
Not a qualified RSW/TIG WPS or thermal-metallurgical solver.
"""
from __future__ import annotations
import sys,math
from pathlib import Path
import numpy as np
ROOT=Path(__file__).resolve().parents[1]
from arc_welding_physics import timed_robot_motion,duration_for_joint_motion,external_axis_energy,external_idle_W,external_trajectory,quintic_trajectory,torch_trajectory
COMPONENTS=('robot_joint_drives_Wh','robot_controller_Wh','process_power_source_Wh','process_actuator_Wh','cooling_Wh','fume_extraction_Wh','gas_valve_Wh','source_standby_Wh','external_axes_Wh')
def machine_source(rng,process,ood=False):
spot=process=='rsw'
return dict(source_nominal_efficiency=.84 if spot else .87,
source_nameplate_W=120000. if spot else 9000.,
process_actuator_nominal_W=420. if spot else 34.,
cooling_nominal_W=420. if spot else 170.,
fume_extraction_nominal_W=90. if spot else 120.,
gas_valve_nominal_W=0. if spot else 8.,
source_standby_nominal_W=85. if spot else 64.,
private_source_efficiency=float(rng.uniform(.67,.76) if ood else rng.uniform(.80,.91)),
private_actuator_scale=float(rng.uniform(1.24,1.50) if ood else rng.uniform(.83,1.17)),
private_cooling_scale=float(rng.uniform(1.19,1.46) if ood else rng.uniform(.80,1.20)),
private_fume_scale=float(rng.uniform(1.16,1.43) if ood else rng.uniform(.83,1.17)),
private_valve_scale=float(rng.uniform(.85,1.18)),
private_contact_scale=float(rng.uniform(1.18,1.40) if ood else rng.uniform(.86,1.14)),
private_transfer_eff=float(rng.uniform(.91,.97) if ood else rng.uniform(.965,.994)))
def public_source(src):return {k:v for k,v in src.items() if not k.startswith('private_')}
def current_setting(rng,process,material,thickness):
if process=='rsw':
amp=float(rng.uniform(5500,13000) if material!='aluminum' else rng.uniform(18000,27500))
force=float(rng.uniform(1500,5600));res=float(rng.uniform(.000055,.00017))
return dict(current_A=amp,electrode_force_N=force,contact_resistance_nominal_ohm=res,
squeeze_s=float(rng.uniform(.12,.40)),pulse_s=float(rng.uniform(.08,.34)),
hold_s=float(rng.uniform(.10,.34)),pulses=int(rng.integers(1,4)),
inter_pulse_s=float(rng.uniform(.025,.075)),gas_flow_L_min=0.,filler_diameter_mm=0.,
filler_feed_m_min=0.,travel_speed_mm_s=0.,ac_mode=0.,pulse_duty=float(rng.uniform(.55,.95)))
ac=int(material=='aluminum' and rng.random()<.80)
amp=float(rng.uniform(70,190));volts=float(rng.uniform(9,16))
filler=int(rng.random()<.70);dia=float(rng.choice([1.0,1.2,1.6,2.0])) if filler else 0.
feed=float(rng.uniform(.35,1.5)) if filler else 0.
return dict(current_A=amp,voltage_V=volts,gas_flow_L_min=float(rng.uniform(8,17)),
filler_diameter_mm=dia,filler_feed_m_min=feed,travel_speed_mm_s=float(rng.uniform(1.7,5.4)),
ac_mode=ac,pulse_duty=float(rng.uniform(.55,.95)),squeeze_s=0.,pulse_s=0.,hold_s=0.,pulses=0,
inter_pulse_s=0.,electrode_force_N=0.,contact_resistance_nominal_ohm=0.)
def probes(source,process,rng):
# SIX prior independent load tests, not the predicted welding job.
levels=np.array([0.,.25,.40,.55,.72,.90]);vals=[]
for x in levels:
if process=='rsw':
I=12000*x
# prior gauge: circuit equivalent resistance under a standardized electrode force
nominal_power=(I*I*.000110)
actual_power=nominal_power*source['private_contact_scale']/source['private_source_efficiency']
else:
nominal_power=(180*x)*(12.5)
actual_power=nominal_power/source['private_source_efficiency']
p=source['source_standby_nominal_W']+source['cooling_nominal_W']*source['private_cooling_scale']+actual_power
vals.append(float(p*(1+rng.normal(0,.002))))
return np.asarray(vals,np.float32)
def robot_segment(cfg,seg,T):
r=timed_robot_motion(cfg,np.asarray(seg['q0']),np.asarray(seg['q1']),T,float(seg.get('payload_kg',1.7)),steps=12)
e,nom=external_axis_energy(cfg,seg,T,12)
return r,e,nom
def totals_common(cfg,source,program,process):
comp={k:0. for k in COMPONENTS}
nom={k:0. for k in COMPONENTS}
source_w=source['source_standby_nominal_W'];cool=source['cooling_nominal_W'];fume=source['fume_extraction_nominal_W']
ctrl_w=cfg['electronic_power_W']+16*cfg['n']
total_t=active_t=0.;arc_heat=0.;deposit=0.;consumed=0.;gas=0.;spots=0
spot_detail=[]
for seg in program['segments']:
q0=np.asarray(seg['q0']);q1=np.asarray(seg['q1'])
motion_T=duration_for_joint_motion(cfg,q0,q1,.7)
if seg['kind']=='travel' or process=='rsw':T=motion_T
else:
qseq,_,_=quintic_trajectory(q0,q1,1.,12)
tcp=external_trajectory(cfg,seg,torch_trajectory(cfg,qseq,seg['tip_index']))
path_mm=float(np.linalg.norm(np.diff(tcp,axis=0),axis=1).sum()*1000)
T=max(motion_T,path_mm/max(program['setting']['travel_speed_mm_s'],.001))
r,e,enom=robot_segment(cfg,seg,T)
comp['robot_joint_drives_Wh']+=r['robot_joint_drives_Wh'];nom['robot_joint_drives_Wh']+=r['nominal_robot_joint_drives_Wh']
comp['robot_controller_Wh']+=r['robot_controller_Wh'];nom['robot_controller_Wh']+=r['robot_controller_Wh']
comp['external_axes_Wh']+=e;nom['external_axes_Wh']+=enom
t=T
if seg['kind'] in ('arc','spot'):
if process=='rsw':
s=program['setting'];nd=s['pulses'];tw=s['pulse_s'];wait=s['inter_pulse_s']*(nd-1)
t_hold=s['squeeze_s']+nd*tw+wait+s['hold_s']
t+=t_hold
# dynamic resistance proxy: electrode force contact compression, interface heating and cooling.
timegrid=np.linspace(0,1,12)
resistance=s['contact_resistance_nominal_ohm']*(.7+.3*np.exp(-1.2*timegrid))*np.power(2800/s['electrode_force_N'],.18)
resistance=np.clip(resistance,1e-6,.001)
i_rms=s['current_A']*math.sqrt(s['pulse_duty'])
Q_nom=i_rms**2*float(resistance.mean())*nd*tw
Q_actual=Q_nom*source['private_contact_scale']
comp['process_power_source_Wh']+=Q_actual/source['private_source_efficiency']/3600
nom['process_power_source_Wh']+=Q_nom/source['source_nominal_efficiency']/3600
heat=Q_actual*.66/1000;arc_heat+=heat
spots+=1;active_t+=nd*tw
nominal_act=source['process_actuator_nominal_W']*(.35+s['electrode_force_N']/6500)
comp['process_actuator_Wh']+=nominal_act*source['private_actuator_scale']*t_hold/3600
nom['process_actuator_Wh']+=nominal_act*t_hold/3600
spot_detail.append(dict(heat_kJ=heat,electrode_force_N=s['electrode_force_N'],contact_resistance_ohm=float(resistance.mean())))
else:
s=program['setting'];active_t+=T
arc_W=s['current_A']*s['voltage_V']*(s['pulse_duty']*(1.08 if s['ac_mode'] else 1.0)+(1-s['pulse_duty'])*.68)
nom['process_power_source_Wh']+=arc_W/source['source_nominal_efficiency']*T/3600
comp['process_power_source_Wh']+=arc_W/source['private_source_efficiency']*T/3600
arc_heat+=arc_W*T*(.66 if s['ac_mode'] else .73)/1000
if s['filler_diameter_mm']>0:
rho=.00270 if program['material']=='aluminum' else .0079
consumed_t=math.pi*s['filler_diameter_mm']**2/4*(s['filler_feed_m_min']*1000*T/60)*rho
consumed+=consumed_t;deposit+=consumed_t*source['private_transfer_eff']
act=source['process_actuator_nominal_W']*(.2+s['filler_feed_m_min'])
nom['process_actuator_Wh']+=act*T/3600
comp['process_actuator_Wh']+=act*source['private_actuator_scale']*T/3600
# Source standby, controller/servo holding and auxiliaries across ALL phases.
nom['source_standby_Wh']+=source_w*t/3600;comp['source_standby_Wh']+=source_w*t/3600
nom['cooling_Wh']+=cool*t/3600;comp['cooling_Wh']+=cool*source['private_cooling_scale']*t/3600
nom['fume_extraction_Wh']+=fume*t/3600;comp['fume_extraction_Wh']+=fume*source['private_fume_scale']*t/3600
extra=t-T
if extra>0:
nom['robot_controller_Wh']+=ctrl_w*extra/3600;comp['robot_controller_Wh']+=ctrl_w*extra/3600
hold=float(np.sum((2+.35*np.asarray(cfg['mass']))))
nom['robot_joint_drives_Wh']+=hold*extra/3600
comp['robot_joint_drives_Wh']+=hold*cfg['private_driver_loss']*extra/3600
en=external_idle_W(cfg,nominal=False);en0=external_idle_W(cfg,nominal=True)
comp['external_axes_Wh']+=en*extra/3600;nom['external_axes_Wh']+=en0*extra/3600
total_t+=t
s=program['setting']
if process=='tig':
gas_t=active_t+program['shielding_preflow_s']+program['shielding_postflow_s']
gas=gas_t*s['gas_flow_L_min']/60
nom['gas_valve_Wh']+=source['gas_valve_nominal_W']*gas_t/3600
comp['gas_valve_Wh']+=source['gas_valve_nominal_W']*source['private_valve_scale']*gas_t/3600
tail=program['shielding_preflow_s']+program['shielding_postflow_s']+program['cooling_tail_s']
else:tail=program['cooling_tail_s']
total_t+=tail
for k,wat in [('robot_controller_Wh',ctrl_w),('source_standby_Wh',source_w),('cooling_Wh',cool),('fume_extraction_Wh',fume)]:
nom[k]+=wat*tail/3600
mult={'cooling_Wh':source['private_cooling_scale'],'fume_extraction_Wh':source['private_fume_scale']}.get(k,1.)
comp[k]+=wat*mult*tail/3600
hold=sum(2+.35*float(m) for m in cfg['mass'])
comp['robot_joint_drives_Wh']+=hold*cfg['private_driver_loss']*tail/3600
nom['robot_joint_drives_Wh']+=hold*tail/3600
nom['external_axes_Wh']+=external_idle_W(cfg,nominal=True)*tail/3600
comp['external_axes_Wh']+=external_idle_W(cfg,nominal=False)*tail/3600
return dict(time_s=total_t,energy_Wh=sum(comp.values()),material_added_g=deposit,wire_consumed_g=consumed,
gas_consumed_L=gas,spot_count=spots,heat_to_workpiece_kJ=arc_heat,active_time_s=active_t,
components=comp,nominal_components=nom,spot_detail=spot_detail)