File size: 10,364 Bytes
547ac30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""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)