File size: 12,286 Bytes
76e7430 dace1db 0d2eceb 76e7430 0d2eceb 76e7430 0d2eceb dace1db 76e7430 0d2eceb dace1db 76e7430 | 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 | /* Apache-2.0. Reference browser assembler for solid-derived component ROMs. */
(function(root){
const lib=typeof require==='function'?require('./vendor/matrix.umd.js'):root.mlMatrix;
const {Matrix,CholeskyDecomposition,EigenvalueDecomposition}=lib;
const zeros=(n,m=n)=>Array.from({length:n},()=>Array(m).fill(0));
const eye=n=>zeros(n).map((r,i)=>(r[i]=1,r));
const tr=A=>A[0].map((_,j)=>A.map(r=>r[j]));
const mul=(A,B)=>{let C=zeros(A.length,B[0].length);for(let i=0;i<A.length;i++)for(let k=0;k<B.length;k++){let v=A[i][k];if(v!==0)for(let j=0;j<B[0].length;j++)C[i][j]+=v*B[k][j];}return C;};
const mv=(A,v)=>A.map(r=>r.reduce((s,x,j)=>s+x*v[j],0));
const add=(a,b)=>a.map((v,i)=>v+b[i]);
const rx=a=>[[1,0,0],[0,Math.cos(a),-Math.sin(a)],[0,Math.sin(a),Math.cos(a)]];
const rz=a=>[[Math.cos(a),-Math.sin(a),0],[Math.sin(a),Math.cos(a),0],[0,0,1]];
const block=(R,n)=>{const D=zeros(3*n);for(let k=0;k<n;k++)for(let i=0;i<3;i++)for(let j=0;j<3;j++)D[3*k+i][3*k+j]=R[i][j];return D;};
const sub=(A,ix)=>ix.map(i=>ix.map(j=>A[i][j]));
const sym=A=>A.map((r,i)=>r.map((v,j)=>(v+A[j][i])/2));
const norm=v=>Math.hypot(...v);
function linear(K,F){
const d=K.map((r,i)=>1/Math.sqrt(Math.max(r[i],1e-30)));
const A=new Matrix(sym(K.map((r,i)=>r.map((v,j)=>v*d[i]*d[j]))));
const c=new CholeskyDecomposition(A);
if(!c.isPositiveDefinite())throw Error('UNRESTRAINED');
const q=c.solve(Matrix.columnVector(F.map((v,i)=>v*d[i]))).to1DArray();
return q.map((v,i)=>v*d[i]);
}
function modes(K,M,count=3){
// Scale mixed physical/normal coordinates before whitening the mass.
let d=M.map((r,i)=>1/Math.sqrt(r[i]));
const ms=M.map((r,i)=>r.map((v,j)=>v*d[i]*d[j]));
const ks=K.map((r,i)=>r.map((v,j)=>v*d[i]*d[j]));
const C=new CholeskyDecomposition(new Matrix(sym(ms)));
if(!C.isPositiveDefinite())throw Error('MASS');
const L=C.lowerTriangularMatrix.to2DArray(),n=L.length,inv=zeros(n);
for(let j=0;j<n;j++)for(let i=0;i<n;i++){let v=i===j?1:0;for(let k=0;k<i;k++)v-=L[i][k]*inv[k][j];inv[i][j]=v/L[i][i];}
const A=sym(mul(mul(inv,ks),tr(inv)));
const e=new EigenvalueDecomposition(new Matrix(A),{assumeSymmetric:true});
const vals=e.realEigenvalues,vectors=e.eigenvectorMatrix.to2DArray();
let order=vals.map((v,i)=>i).sort((a,b)=>vals[a]-vals[b]);
if(vals[order[0]]<=0)throw Error('UNRESTRAINED');
order=order.slice(0,count);
const V=mul(tr(inv),vectors.map(r=>order.map(j=>r[j]))).map((r,i)=>r.map(v=>v*d[i]));
return {frequency:order.map(j=>Math.sqrt(vals[j])/(2*Math.PI)),vectors:V};
}
function solve(request){
const start=typeof performance==='undefined'?Date.now():performance.now();
const {components,rolls=[],supports=[],deltaT=100,gravity=[0,0,-9.81]}=request;
const n=components.length,nq=6*(n+1),nm=components.map(c=>c.Kr.length-12),nc=nq+nm.reduce((a,b)=>a+b,0);
let K=zeros(nq),KC=zeros(nc),MC=zeros(nc),F=Array(nq).fill(0),places=[],origin=[0,0,0],R=eye(3),offset=nq;
const accumulate=(A,B,ix)=>{for(let i=0;i<ix.length;i++)for(let j=0;j<ix.length;j++)A[ix[i]][ix[j]]+=B[i][j];};
for(let a=0;a<n;a++){
let c=components[a];R=mul(R,rx(rolls[a]||0));let D=block(tr(R),4),ix=Array.from({length:12},(_,j)=>6*a+j);
accumulate(K,mul(mul(tr(D),c.S),D),ix);
const fg=mv(c.g,mv(tr(R),gravity)),fl=mv(tr(D),fg.map((v,j)=>v+c.fT[j]*deltaT));
ix.forEach((id,j)=>F[id]+=fl[j]);
let Dc=eye(12+nm[a]);for(let i=0;i<12;i++)for(let j=0;j<12;j++)Dc[i][j]=D[i][j];
let ic=ix.concat(Array.from({length:nm[a]},(_,j)=>offset+j));
accumulate(KC,mul(mul(tr(Dc),c.Kr),Dc),ic);accumulate(MC,mul(mul(tr(Dc),c.Mr),Dc),ic);
places.push({origin:[...origin],rotation:R.map(r=>[...r]),D,Dc,ix,ic});
origin=add(origin,mv(R,c.ports[1]));if(c.spec.kind==='elbow')R=mul(R,rz(Math.PI/2));offset+=nm[a];
}
const fixed=new Set();let spring=Array(nq).fill(0);
supports.forEach(s=>{(s.fixed||[]).forEach(j=>fixed.add(6*s.node+j));(s.spring||[]).forEach((v,j)=>{let i=6*s.node+j;K[i][i]+=v;KC[i][i]+=v;spring[i]+=v;});});
let free=Array.from({length:nq},(_,i)=>i).filter(i=>!fixed.has(i)),q=Array(nq).fill(0);
const sol=linear(sym(sub(K,free)),free.map(i=>F[i]));free.forEach((v,i)=>q[v]=sol[i]);
let reaction=mv(K,q).map((v,i)=>v-F[i]);
let residual=norm(free.map(i=>reaction[i]))/Math.max(1,norm(F));
const fC=Array.from({length:nc},(_,i)=>i).filter(i=>!fixed.has(i));
const count=(request.dynamic||request.harmonic)?Math.min((request.dynamic||request.harmonic).modeCount||12,fC.length):3;
const eig=modes(sub(KC,fC),sub(MC,fC),count),V=zeros(nc,count);fC.forEach((v,i)=>V[v]=eig.vectors[i]);
let paths=[],displacements=[],modeDisplacements=[],nodes=[],mass=0;
for(let a=0;a<n;a++){
const c=components[a],p=places[a],local=mv(p.D,p.ix.map(j=>q[j])),gg=mv(tr(p.rotation),gravity),lm=mul(p.Dc,p.ic.map(j=>V[j]));
paths.push(c.path.map(x=>add(p.origin,mv(p.rotation,x))));nodes.push(paths[a][0]);
displacements.push(c.path.map((_,j)=>mv(p.rotation,add(add(mv(c.recovery[j],local),c.thermal_recovery[j].map(v=>v*deltaT)),mv(c.gravity_recovery[j],gg)))));
modeDisplacements.push(c.path.map((_,j)=>mul(p.rotation,mul(c.recovery[j].map((r,k)=>r.concat(c.mode_recovery[j][k])),lm))));
mass+=c.metrics.mass_kg;
}
nodes.push(paths[n-1].at(-1));
let maxDisplacement=0,maxLocation={component:0,sample:0,position:paths[0][0]};
displacements.forEach((p,a)=>p.forEach((u,j)=>{if(norm(u)>maxDisplacement){maxDisplacement=norm(u);maxLocation={component:a,sample:j,position:paths[a][j]};}}));
let supportForces=supports.map(s=>Array.from({length:3},(_,j)=>fixed.has(6*s.node+j)?reaction[6*s.node+j]:-spring[6*s.node+j]*q[6*s.node+j]));
if(!Number.isFinite(maxDisplacement)||residual>1e-6)throw Error('RESIDUAL');
let dynamics=null;
if(request.dynamic){
const d=request.dynamic,node=d.node??n,axis=d.axis??2,c=d.c??10000;
if(!Number.isInteger(node)||node<0||node>n||![0,1,2].includes(axis)||!Number.isFinite(c)||c<0)throw Error('DAMPER');
let peak=0,probe=null;
modeDisplacements.forEach((path,a)=>path.forEach((m,j)=>m.forEach((r,k)=>{if(Math.abs(r[0])>peak){peak=Math.abs(r[0]);probe={component:a,sample:j,axis:k};}})));
const amplitude=d.amplitude??.001;
if(!(amplitude>0&&litude<=.01))throw Error('AMPLITUDE');
const z0=Array(count).fill(0);z0[0]=amplitude/modeDisplacements[probe.component][probe.sample][probe.axis][0];
const b=V[node*6+axis],C=b.map(x=>b.map(y=>c*x*y)),omega=eig.frequency.map(f=>2*Math.PI*f);
dynamics=decay(omega,C,z0,d.duration??6/eig.frequency[0],d.stepFactor||1);
const probeVector=modeDisplacements[probe.component][probe.sample][probe.axis];
dynamics.signal=dynamics.coordinates.map(z=>z.reduce((s,v,i)=>s+v*probeVector[i],0));
dynamics.reference=dynamics.time.map(t=>z0[0]*probeVector[0]*Math.cos(omega[0]*t));
dynamics.force=dynamics.velocities.map(v=>-c*v.reduce((s,x,i)=>s+x*b[i],0));
dynamics.peakForce=Math.max(...dynamics.force.map(Math.abs));
Object.assign(dynamics,{node,axis,c,probe,amplitude,modeCount:count,frequency:eig.frequency,blocked:fixed.has(node*6+axis)});
delete dynamics.velocities;
}
const harmonic=request.harmonic?frequencyResponse(eig.frequency,V,modeDisplacements,request.harmonic,n,fixed):null;
return {q,reaction,paths,nodes,displacements,modeDisplacements,frequency:eig.frequency.slice(0,3),mass,maxDisplacement,maxLocation,supportForces,supportNodes:supports.map(s=>s.node),residual,dynamics,harmonic,
elapsedMs:(typeof performance==='undefined'?Date.now():performance.now())-start};
}
// Average-acceleration Newmark, mass-normalized modal coordinates.
// Physical non-proportional damping C=c*b*b^T is kept in full.
function decay(omega,C,z0,duration,stepFactor=1){
const n=omega.length,frames=600,stride=Math.ceil(Math.max(2400,80*Math.max(...omega)/(2*Math.PI)*duration)*stepFactor/frames),steps=stride*frames;
if(steps>240000||!(duration>0)||!Number.isFinite(duration))throw Error('DYNAMIC_RANGE');
const h=duration/steps,w2=omega.map(w=>w*w),H=C.map((r,i)=>r.map((v,j)=>h*.5*v+(i===j?1+h*h*.25*w2[i]:0)));
const factor=new CholeskyDecomposition(new Matrix(sym(H))).lowerTriangularMatrix.to2DArray();
const solveH=rhs=>{const y=Array(n),x=Array(n);for(let i=0;i<n;i++){let s=rhs[i];for(let j=0;j<i;j++)s-=factor[i][j]*y[j];y[i]=s/factor[i][i];}for(let i=n-1;i>=0;i--){let s=y[i];for(let j=i+1;j<n;j++)s-=factor[j][i]*x[j];x[i]=s/factor[i][i];}return x;};
let z=[...z0],v=Array(n).fill(0),a=z.map((x,i)=>-w2[i]*x),diss=0,peakEnergyRise=0;
const energy=()=>z.reduce((s,x,i)=>s+.5*(v[i]*v[i]+w2[i]*x*x),0),E0=energy();
const time=[],coordinates=[],velocities=[],energies=[],dissipated=[];
const record=t=>{time.push(t);coordinates.push([...z]);velocities.push([...v]);energies.push(energy());dissipated.push(diss);};record(0);
for(let step=1;step<=steps;step++){
const zp=z.map((x,i)=>x+h*v[i]+h*h*.25*a[i]),vp=v.map((x,i)=>x+h*.5*a[i]);
const an=solveH(mv(C,vp).map((x,i)=>-x-w2[i]*zp[i]));
const vn=vp.map((x,i)=>x+h*.5*an[i]),mid=v.map((x,i)=>(x+vn[i])*.5),prior=energy();
const cmid=mv(C,mid);diss+=h*mid.reduce((s,x,i)=>s+x*cmid[i],0);
z=zp.map((x,i)=>x+h*h*.25*an[i]);v=vn;a=an;
peakEnergyRise=Math.max(peakEnergyRise,(energy()-prior)/Math.max(E0,1e-30));
if(step%stride===0)record(step*h);
}
return {time,coordinates,velocities,energy:energies,dissipated,dt:h,initialEnergy:E0,energyBalanceRelative:Math.abs(energy()+diss-E0)/Math.max(E0,1e-30),peakEnergyRise};
}
// Complex steady response, exp(i*w*t). Positive background modal damping is explicit.
function harmonicCoordinates(frequencies,b,load,c,zeta,f){
const n=frequencies.length,w=2*Math.PI*f,A=zeros(2*n),rhs=load.concat(Array(n).fill(0));
for(let i=0;i<n;i++)for(let j=0;j<n;j++){
const real=i===j?(2*Math.PI*frequencies[i])**2-w*w:0;
const imag=w*(c*b[i]*b[j]+(i===j?2*zeta*2*Math.PI*frequencies[i]:0));
A[i][j]=A[i+n][j+n]=real;A[i][j+n]=-imag;A[i+n][j]=imag;
}
const x=new lib.LuDecomposition(new Matrix(A)).solve(Matrix.columnVector(rhs)).to1DArray();
if(!x.every(Number.isFinite))throw Error('HARMONIC_SINGULAR');
return {real:x.slice(0,n),imag:x.slice(n)};
}
function frequencyResponse(frequencies,V,recovery,h,n,fixed){
const {node=n,axis=2,force=100,frequency=10,zeta=.02,damper={node:n,axis:2,c:10000}}=h;
if(!Number.isInteger(node)||node<1||node>n||![0,1,2].includes(axis)||!(force>0&&force<=10000)||!(frequency>0&&frequency<=2000)||!(zeta>=.001&&zeta<=.1)||!Number.isInteger(damper.node)||damper.node<1||damper.node>n||![0,1,2].includes(damper.axis)||!(damper.c>=0&&damper.c<=100000))throw Error('HARMONIC_INPUT');
const b=V[6*damper.node+damper.axis],load=V[6*node+axis].map(v=>v*force),probe=V[6*node+axis];
const evaluate=(f,c)=>{const q=harmonicCoordinates(frequencies,b,load,c,zeta,f),re=probe.reduce((s,x,i)=>s+x*q.real[i],0),im=probe.reduce((s,x,i)=>s+x*q.imag[i],0);return {...q,amplitude:Math.hypot(re,im),phase:Math.atan2(im,re)};};
const current=evaluate(frequency,damper.c),reference=evaluate(frequency,0);
const upper=frequencies[0]*2;
const sweep=Array.from({length:161},(_,i)=>{const f=frequencies[0]*(.2+1.8*i/160);return {frequency:f,withDamper:evaluate(f,damper.c).amplitude,withoutDamper:evaluate(f,0).amplitude};});
const field=q=>recovery.map(p=>p.map(m=>({real:m.map(r=>r.reduce((s,x,i)=>s+x*q.real[i],0)),imag:m.map(r=>r.reduce((s,x,i)=>s+x*q.imag[i],0))})));
const power=2*Math.PI*frequency,dr=b.reduce((s,x,i)=>s+x*current.real[i],0),di=b.reduce((s,x,i)=>s+x*current.imag[i],0);
return {node,axis,force,frequency,zeta,damper,modeCount:frequencies.length,modalFrequencies:frequencies,current,reference,field:field(current),referenceField:field(reference),sweep,sweepUpper:upper,damperForce:damper.c*power*Math.hypot(dr,di),blocked:fixed.has(6*node+axis)};
}
if(typeof module!=='undefined')module.exports={solve,decay,harmonicCoordinates};else root.PipeSolver={solve,decay,harmonicCoordinates};
})(typeof globalThis==='undefined'?self:globalThis);
|