Manufacturing-MoE / src /welding_specialists.py
rasitcesur's picture
Sync from rasitcesur/Manufacturing-MoE via hub-sync
547ac30 verified
Raw History Blame Contribute Delete
5.08 kB
"""current separately trained process-specific welding MoEs.
Shared welding-cell robot physics and topology; NO trained weight sharing among
GMAW, RSW and TIG. MCE uses exclusively previous measured sensor data.
"""
import numpy as np
import torch
from torch import nn
class WeldingSet:
def __init__(self,arrays,norm=None):
self.y=np.asarray(arrays['y'],np.float32)
self.c=np.asarray(arrays['components'],np.float32)
self.prior=np.asarray(arrays['prior'],np.float32)
self.comp_prior=np.asarray(arrays['component_prior'],np.float32)
self.mask=np.asarray(arrays['mask'],np.float32)
self.ids=np.asarray(arrays['robot_ids']);self.pids=np.asarray(arrays['program_ids'])
self.topology=np.asarray(arrays['topology']);self.axes=np.asarray(arrays['n_axes'])
joint=np.asarray(arrays['joint'],np.float32)
g=np.asarray(arrays['global_features'],np.float32).copy()
# log compression only for strictly non-negative continuous variables;
# categorical flags and joint angles retain their signed coordinates.
for col in (4,5,6,7,8,9,10,11,12,13,15,16,17,18,20,21,22,24,25,26,27,30,33,34,35,36,37):
g[:,col]=np.log1p(np.maximum(g[:,col],0))
h=np.asarray(arrays['history'],np.float32).copy()
h[:,:12]=np.log1p(np.maximum(h[:,:12],0))
cp=np.maximum(self.comp_prior,1e-9)
x=np.concatenate([g,np.log1p(cp),cp/np.maximum(cp.sum(1,keepdims=True),1e-8),
np.log1p(np.maximum(self.prior[:,1:2],0))],axis=1)
if norm is None:
active=joint[self.mask>0]
norm={'jm':active.mean(0),'js':np.maximum(active.std(0),.04),
'gm':x.mean(0),'gs':np.maximum(x.std(0),.04),
'hm':h.mean(0),'hs':np.maximum(h.std(0),.04)}
self.norm=norm
self.joint=(np.clip((joint-norm['jm'])/norm['js'],-7,7)*self.mask[:,:,None]).astype(np.float32)
self.global_x=np.clip((x-norm['gm'])/norm['gs'],-7,7).astype(np.float32)
self.history=np.clip((h-norm['hm'])/norm['hs'],-7,7).astype(np.float32)
self.y_logratio=np.log(np.maximum(self.c,1e-9)/np.maximum(self.comp_prior,1e-9)).astype(np.float32)
# Only TIG with filler has material-addition target; RSW 0 is physically correct.
self.material_ratio=(self.y[:,2]/np.maximum(self.prior[:,2],1e-8)).astype(np.float32)
def tensors(self):return tuple(torch.from_numpy(x) for x in (self.joint,self.mask,self.global_x,self.history))
class WeldingMoE55(nn.Module):
def __init__(self,global_dim,history_dim,process):
super().__init__();self.process=process
from config import choice
jw=choice(24,48,72);tw=choice(16,32,48);mw=choice(16,32,48)
th=choice(32,64,96);mh=choice(32,64,96)
rw=choice(32,64,96);rr=choice(16,32,48)
pw=choice(40,80,120);pp=choice(20,40,60);ew=choice(20,40,60)
self.joint_dynamics=nn.Sequential(nn.Linear(15,jw),nn.GELU(),nn.Linear(jw,jw),nn.GELU())
self.topology_expert=nn.Sequential(nn.Linear(jw*3+global_dim,th),nn.GELU(),nn.Linear(th,tw),nn.GELU())
self.machine_characterization=nn.Sequential(nn.Linear(history_dim,mh),nn.GELU(),nn.Linear(mh,mw),nn.GELU())
sd=tw+mw+global_dim
self.robot_energy=nn.Sequential(nn.Linear(sd,rw),nn.GELU(),nn.Linear(rw,rr),nn.GELU(),nn.Linear(rr,2))
self.process_energy=nn.Sequential(nn.Linear(sd,pw),nn.GELU(),nn.Linear(pw,pp),nn.GELU(),nn.Linear(pp,6))
self.external_energy=nn.Sequential(nn.Linear(sd,ew),nn.GELU(),nn.Linear(ew,1))
# RSW never deposits filler; remove its redundant material head in Small only.
self.material_expert=None if (choice(True,False,False) and process=='rsw') else nn.Sequential(nn.Linear(sd,ew),nn.GELU(),nn.Linear(ew,1))
for head in (self.robot_energy,self.process_energy,self.external_energy,self.material_expert):
if head is not None:nn.init.zeros_(head[-1].weight);nn.init.zeros_(head[-1].bias)
def forward(self,j,m,g,h):
z=self.joint_dynamics(j)*m.unsqueeze(-1);n=m.sum(1,keepdim=True).clamp_min(1)
pooled=z.sum(1)/n;mx=z.masked_fill(m.unsqueeze(-1)==0,-1e4).max(1).values;ss=z.sum(1)/n.sqrt()
topo=self.topology_expert(torch.cat([pooled,mx,ss,g],1));machine=self.machine_characterization(h)
f=torch.cat([topo,machine,g],1)
comp=torch.cat([self.robot_energy(f),self.process_energy(f),self.external_energy(f)],dim=1)
transfer=torch.ones(len(j),dtype=j.dtype,device=j.device) if self.material_expert is None else .98+.08*torch.tanh(self.material_expert(f).flatten())
return comp,transfer
def predict(model,ds,blend=1.,no_history=False,batch=512):
model.eval();device=next(model.parameters()).device;j,m,g,h=[t.to(device) for t in ds.tensors()]
if no_history:h=torch.zeros_like(h)
comps=[];mr=[]
with torch.no_grad():
for k in range(0,len(j),batch):
z,t=model(j[k:k+batch],m[k:k+batch],g[k:k+batch],h[k:k+batch]);comps.append(z.detach().cpu().numpy());mr.append(t.detach().cpu().numpy())
residual=np.clip(np.concatenate(comps),-1.,1.)*blend
predicted_components=ds.comp_prior*np.exp(residual)
if no_history:
# Remove automatic prior-history calibration when evaluating genuine cold start.
pass
out=ds.prior.copy();out[:,1]=predicted_components.sum(1)
if model.process=='tig':out[:,2]=out[:,3]*np.maximum(.0,np.concatenate(mr))
return out,predicted_components