Download engine/geometry.py from will-taggart/AFFECTOR-ARC: direct link, hf CLI and curl.
- Browser
- Download file 5.64 kB
-
https://huggingface.co/will-taggart/AFFECTOR-ARC/resolve/main/engine/geometry.py
- Command line
-
hf download hf://will-taggart/AFFECTOR-ARC/engine/geometry.py
-
curl -L -o geometry.py https://huggingface.co/will-taggart/AFFECTOR-ARC/resolve/main/engine/geometry.py
5.64 kB
| """Portable emotional trajectories and editable activation-module settings.""" | |
| import copy,json,math,uuid,os,tempfile | |
| from pathlib import Path | |
| from datetime import datetime,timezone | |
| FORMAT='affector-geometry-v1' | |
| def now():return datetime.now(timezone.utc).isoformat() | |
| def finite(v,lo=-1,hi=1):return type(v) in (int,float) and math.isfinite(v) and lo<=v<=hi | |
| def new(name='Untitled geometry'): | |
| return {'format':FORMAT,'id':str(uuid.uuid4()),'name':name,'created_at':now(),'updated_at':now(), | |
| 'points':[{'t':0.,'v':-.6,'d':-.3,'i':.35},{'t':.5,'v':0.,'d':0.,'i':.75},{'t':1.,'v':.7,'d':.6,'i':.5}], | |
| 'layers':{'8':1.,'16':1.,'23':.5},'controls':{'strength':.12,'inertia':.2,'axis_top_k':3,'axis_mass':1.,'axis_temperature':1.,'budget':.2}, | |
| 'basis':None,'provenance':'User-authored trajectory; uncalibrated activation map.'} | |
| def validate(g): | |
| if not isinstance(g,dict) or g.get('format')!=FORMAT:raise ValueError('Not an AFFECTOR geometry.') | |
| uuid.UUID(g['id']) | |
| if not isinstance(g['name'],str) or not g['name'].strip():raise ValueError('A geometry needs a name.') | |
| pts=g['points'] | |
| if not isinstance(pts,list) or not 1<=len(pts)<=100000:raise ValueError('Use 1–100,000 trajectory points.') | |
| last=-1. | |
| for p in pts: | |
| if not all(finite(p[k],0 if k in ('t','i') else -1,1) for k in ('t','v','d','i')) or p['t']<=last:raise ValueError('Points need increasing t in [0,1], valence/direction in [-1,1], intensity in [0,1].') | |
| last=p['t'] | |
| if not isinstance(g['layers'],dict) or not g['layers']:raise ValueError('Select at least one transformer layer.') | |
| for k,v in g['layers'].items(): | |
| if not k.isdigit() or not 0<=int(k)<24 or not finite(v,-2,2):raise ValueError('Layer IDs must be 0–23 and weights -2…2.') | |
| c=g['controls'] | |
| for k,lo,hi in [('strength',0,2),('inertia',0,.99),('axis_top_k',1,3),('axis_mass',.01,1),('axis_temperature',.1,3),('budget',0,1)]: | |
| if not finite(c[k],lo,hi):raise ValueError('Invalid activation control: '+k) | |
| if type(c['axis_top_k']) is not int:raise ValueError('Axis top-k must be an integer.') | |
| b=g.get('basis') | |
| if b is not None: | |
| if not isinstance(b,dict) or not isinstance(b.get('vectors'),dict):raise ValueError('Invalid steering basis.') | |
| for k,rows in b['vectors'].items(): | |
| if not k.isdigit() or not 0<=int(k)<24 or len(rows)!=3 or any(len(r)!=1024 or any(not finite(x,-1000,1000) for x in r) for r in rows):raise ValueError('Steering weights must be finite 3 × 1024 arrays per layer.') | |
| return copy.deepcopy(g) | |
| def point(g,t): | |
| pts=g['points'];p=pts[0] | |
| if t<=p['t']:return [p['v'],p['d'],2*p['i']-1] | |
| for q in pts[1:]: | |
| if t<=q['t']: | |
| a=(t-p['t'])/(q['t']-p['t']);return [(1-a)*p[k]+a*q[k] if k!='i' else 2*((1-a)*p[k]+a*q[k])-1 for k in ('v','d','i')] | |
| p=q | |
| return [p['v'],p['d'],2*p['i']-1] | |
| def import_atlas(data,name): | |
| if data.get('format')==FORMAT:return validate(data) | |
| g=new(name);rows=[];total=0 | |
| if data.get('format')=='emotion-atlas-v1':rows=data['results'];total=len(data['paragraphs']) | |
| elif data.get('format') in ('emotional-atlas-object-v1','emotional-atlas-evolution-v2'): | |
| meta=data.get('metadata',{});rows=meta.get('snapshot_results',meta.get('analysis',[]));total=meta.get('total',len(rows)) | |
| if not rows:raise ValueError('This object has no emotional analysis snapshot. Import the Atlas analysis session instead; mesh/PCA coordinates alone cannot identify emotional axes.') | |
| elif data.get('format') in ('emotional-atlas-transfer-v1','emotional-atlas-transfer-session-v1'): | |
| source=data['source'];total=source.get('total_units',source.get('total',0));rows=source.get('rows',[]) | |
| if not rows: | |
| for k in source.get('knots',[]):rows.append({'id':k['id'],'valence':k['point'][0],'direction':k['point'][1],'intensity':(k['point'][2]+1)/2}) | |
| else:raise ValueError('Open an AFFECTOR geometry or an Emotional Atlas session/export with emotional scores.') | |
| pts=[] | |
| for r in sorted(rows,key=lambda r:r['id']): | |
| if not all(finite(r.get(k),0 if k=='intensity' else -1,1) for k in ('valence','direction','intensity')):continue | |
| pts.append({'t':(r['id']-1)/max(1,total-1),'v':r['valence'],'d':r['direction'],'i':r['intensity']}) | |
| if not pts:raise ValueError('No complete emotional coordinates; unknown direction is not imputed.') | |
| g['points']=pts;g['provenance']=f"Imported from {data['format']}; {len(pts)}/{total} complete points. Missing positions interpolated, endpoints clamped." | |
| return validate(g) | |
| def atomic(path,data): | |
| path=Path(path);path.parent.mkdir(parents=True,exist_ok=True);temp=None | |
| try: | |
| with tempfile.NamedTemporaryFile('w',dir=path.parent,delete=False,encoding='utf-8') as f: | |
| temp=f.name;json.dump(data,f,ensure_ascii=False,allow_nan=False,indent=2);f.flush();os.fsync(f.fileno()) | |
| os.replace(temp,path);temp=None | |
| finally: | |
| if temp:Path(temp).unlink(missing_ok=True) | |
| class Library: | |
| def __init__(self,path):self.path=Path(path);self.path.mkdir(parents=True,exist_ok=True) | |
| def save(self,g): | |
| g=validate(g);g['updated_at']=now();atomic(self.path/(g['id']+'.json'),g);return g | |
| def list(self): | |
| result=[] | |
| for p in self.path.glob('*.json'): | |
| try:result.append(validate(json.loads(p.read_text()))) | |
| except (ValueError,KeyError,TypeError):continue | |
| return sorted(result,key=lambda g:g['updated_at'],reverse=True) | |
| def delete(self,identity): | |
| identity=str(uuid.UUID(identity));(self.path/(identity+'.json')).unlink() | |