rasitcesur's picture
Sync from rasitcesur/Manufacturing-MoE-Demo via hub-sync
e95ffc8 verified
Raw History Blame Contribute Delete
8.23 kB
"""Inference for original Physical Transformer-MoE extended to 8 operations including surface grinding.
For hole operations, G17/Z and G19/X use identical canonical axial/lateral
features for the shared Hole Processing Expert. Machine orientation affects
hardware power prior only; old FDM/milling/turning parsers remain unchanged.
Research simulator only; CNC code is NEVER executed on machine.
"""
from __future__ import annotations
import argparse,csv,json,math,re
from pathlib import Path
import numpy as np,torch
from train import (TimeMoE,EnergyMoE,MODE,WORDS,COMMENTS,FEATURE_DIM,
cnc_features,turning_features,drilling_features,compute_energy_case,energy_features,apply_energy_calibration,
predict_time as _predict_time,machine_adjusted_case)
from machine_characterization import MachineCharacterizationExpert, SENSOR_NAMES, MACHINE_GROUPS
from hole_physics import simulate_program
from physics import state_features
from train import grinding_features,sawing_features,thermal_features
def predict(checkpoint,gcode,profile,mode,operation=None,device='cpu',machine_history=None):
state=torch.load(checkpoint,map_location='cpu',weights_only=False)
if device=='auto':device='cuda' if torch.cuda.is_available() else 'cpu'
if mode=='milling':mode='cnc_milling'
if mode=='turning':mode='cnc_turning'
if mode=='drilling':mode='cnc_drilling'
if mode=='grinding':mode='cnc_grinding'
if mode=='sawing':mode='cnc_sawing'
if mode=='laser':mode='cnc_laser'
if mode=='plasma':mode='cnc_plasma'
if mode not in MODE:raise ValueError('Mode must be fdm, milling, turning, drilling, grinding, sawing, laser or plasma')
profilej=Path(profile).read_text(encoding='utf-8');meta=json.loads(profilej)
ops=('drilling','boring','reaming','tapping')
if operation in ops:mode='cnc_drilling'
if operation is None:operation=meta.get('operation','drilling' if mode=='cnc_drilling' else None)
rows=[]
for ln in Path(gcode).read_text(encoding='utf-8-sig').splitlines():
line=COMMENTS.sub('',ln).strip().upper()
if line and WORDS.search(line):
rows.append({'program_id':'inference','step_index':str(len(rows)),'gcode':line,'initial_profile':profilej,
'time_s':0.,'energy_Wh':0.,'material_g':0.})
if not rows:raise ValueError('No G-code parsed')
# The 180 original vertical drilling jobs use the original time parser
# but the upgraded 12-component hardware-energy prior.
legacy_hole=(operation=='drilling' and meta.get('firmware')=='analytic_drilling_surrogate_not_LinuxCNC')
is_new_hole=(operation in ops and 'hidden_diameter_mm' in meta and not legacy_hole)
if is_new_hole:
codes,feat,prior,geom,energycase=simulate_program(rows,FEATURE_DIM)
group=f'{operation}_'+('horizontal' if any(re.search(r'\bG19\b',r['gcode']) for r in rows) else 'vertical')
elif mode=='fdm':
codes,ff,prior=state_features(rows);feat=np.zeros((len(rows),FEATURE_DIM),np.float32)
feat[:,:38]=ff;feat[:,38]=1.;geom=np.asarray(prior[:,2],float);group='fdm'
elif mode=='cnc_milling':codes,feat,prior,geom=cnc_features(rows);group='cnc_milling'
elif mode=='cnc_grinding':codes,feat,prior,geom=grinding_features(rows);group='grinding_surface'
elif mode=='cnc_sawing':codes,feat,prior,geom=sawing_features(rows);group='sawing_band'
elif mode in ('cnc_laser','cnc_plasma'):
codes,feat,prior,geom=thermal_features(rows);group=mode.split('_')[-1]+'_sheet'
elif mode=='cnc_turning':codes,feat,prior,geom=turning_features(rows);group='cnc_turning'
elif mode=='cnc_drilling':codes,feat,prior,geom=drilling_features(rows);group='drilling_vertical_old'
else:raise ValueError('Unknown mode')
p={'id':'inference','mode':mode,'group':group,'rows':rows,'codes':np.asarray(codes,dtype=np.int64),
'feat':np.asarray(feat,dtype=np.float32),'prior':np.asarray(prior,dtype=np.float32),
'geometry_g':np.asarray(geom,dtype=float),'target':np.zeros((len(rows),3),np.float32)}
if is_new_hole or legacy_hole:p['hole_new']=True
if is_new_hole:p['hole_energy_physics']=energycase
tm=TimeMoE().to(device);tm.load_state_dict(state['time']);tm.eval()
em=EnergyMoE(state['components']).to(device);em.load_state_dict(state['energy']);em.eval()
mean,std=state['time_scaler'];nt=_predict_time(tm,[p],mean,std,device)['inference']
alpha=state['time_blends'].get(group,state['time_blends'].get('drilling_vertical_old',0.))
pred_time=(1-alpha)*prior[:,0]+alpha*nt
case=apply_energy_calibration(compute_energy_case(p,pred_time),group,
state.get('milling_energy_calibration'),state.get('drilling_energy_calibration'))
# Machine encoder receives watts observed BEFORE the target job; hidden gain ignored.
if group in MACHINE_GROUPS and machine_history is not None:
if isinstance(machine_history,(str,Path)):
data=json.loads(Path(machine_history).read_text(encoding='utf-8'))
observed=data['measurements_W']
else:
observed=machine_history['measurements_W'] if isinstance(machine_history,dict) else machine_history
if 'machine_expert' not in state:raise ValueError('Checkpoint has no machine-history expert')
mce=MachineCharacterizationExpert().to(device)
mce.load_state_dict(state['machine_expert'])
case=machine_adjusted_case(case,observed,mce,group)
mn,sd=state['energy_scaler'];components=[]
for name,b in zip(case['components'],case['base']):
ix=state['components'].index((mode,name));ft=energy_features(case,name)
xx=torch.from_numpy(np.clip((ft-mn)/sd,-8,8)).reshape(1,-1).to(device)
with torch.inference_mode():learned=em(xx,torch.tensor([ix],device=device),
torch.tensor([MODE[mode]],device=device),torch.tensor([float(b)],device=device)).item()
blend=state['energy_blends'].get(group,state['energy_blends'].get('drilling_vertical_old',0))
components.append({'component':name,'energy_Wh':float((1-blend)*b+blend*learned)})
total_time=float(np.sum(pred_time));total_energy=float(sum(x['energy_Wh'] for x in components));total_material=float(np.sum(geom))
out={'operation':operation or mode,'mode':mode,'orientation':group.split('_')[-1] if (is_new_hole or legacy_hole) else None,
'program_group':group,'time_s':total_time,'energy_Wh':total_energy,'material_g':total_material,
'material_meaning':'filament' if mode=='fdm' else 'kerf_removed' if mode in ('cnc_laser','cnc_plasma') else 'chips_removed',
'energy_components_Wh':components,'command_count':len(rows),
'synthetic_only':True,'runtime_verified_on_LinuxCNC':False,
'machine_characterization_used':bool(case.get('machine_history_used',False)),
'machine_characterization_supported':group in MACHINE_GROUPS}
return out,rows,pred_time
def main():
pa=argparse.ArgumentParser();pa.add_argument('--checkpoint',default='reports/final/model.pt')
pa.add_argument('--gcode',required=True);pa.add_argument('--profile',required=True)
pa.add_argument('--mode',required=True,choices=['fdm','milling','turning','drilling','grinding','sawing','cnc_sawing','cnc_milling','cnc_turning','cnc_grinding','laser','plasma','cnc_laser','cnc_plasma'])
pa.add_argument('--operation',choices=['drilling','boring','reaming','tapping'])
pa.add_argument('--machine-history',default=None,help='Prior machine power measurements JSON (six readings in W), not hidden machine gain')
pa.add_argument('--output',default='outputs/prediction.json');pa.add_argument('--device',choices=['cpu','cuda','auto'],default='cpu')
a=pa.parse_args();res,rows,pred=predict(a.checkpoint,a.gcode,a.profile,a.mode,a.operation,a.device,a.machine_history)
out=Path(a.output);out.parent.mkdir(parents=True,exist_ok=True);out.write_text(json.dumps(res,ensure_ascii=False,indent=2))
with out.with_suffix('.csv').open('w',newline='',encoding='utf-8') as f:
w=csv.writer(f);w.writerow(['step','gcode','pred_time_s']);w.writerows((i,r['gcode'],float(v)) for i,(r,v) in enumerate(zip(rows,pred)))
print(json.dumps(res,ensure_ascii=False,indent=2))
if __name__=='__main__':main()