Created
September 26, 2025 23:15
-
-
Save lastforkbender/a3c7cd413923c4c63d80e5a0e430f60d to your computer and use it in GitHub Desktop.
Diffusion Chained Tunneling Coupler Modal
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # dctm.py - Diffusion Chained Tunneling Coupler Modal | |
| import math | |
| CNST_EP = 2.718281828459045 | |
| #___________________________________________________________________________________ | |
| def mat_mult_vec(M, v): | |
| return [sum(M[i][j]*v[j] for j in range(len(v))) for i in range(len(M))] | |
| #___________________________________________________________________________________ | |
| def zero_matrix(r, c): | |
| return [[0.0]*c for _ in range(r)] | |
| #___________________________________________________________________________________ | |
| class DiffusionChainModes: | |
| # Diffusion chain with M-modes per spatial point. Each segment a per-mode | |
| # D and a lists of len @M(mu_a). The transmission per-interface is setup | |
| # per-mode or a scalar broadcast. Tunneling provides a coupling matrix K | |
| # between the 2 interfaces; left bound(prescribed modal phi_left) & right | |
| # bounded, Dirichlet phi_right, also of length M*... optionals. | |
| def __init__(self, segments, M, T_between=None, phi_right=None, | |
| phi_right_dirichlet=True, tunnel=None, eps_mu=0.0, Tmin=0.0): | |
| self.M, self.eps_mu, self.Tmin = int(M), float(eps_mu), float(Tmin) | |
| # Validate & copy the segments | |
| segs = [] | |
| for s in segments: | |
| L = float(s['L']) | |
| Dlist = [float(x) for x in s['D_list']] | |
| mulist = [max(float(x), self.eps_mu) for x in s['mu_list']] | |
| if len(Dlist) != self.M or len(mulist) != self.M: | |
| raise ValueError('D_list & mu_list must have a same length M') | |
| segs.append({'L': L, 'D_list': Dlist, 'mu_list': mulist}) | |
| N = len(segs) | |
| # Transmissions, list of N-1 entries each either list length M or scalar | |
| if T_between is None: T_between = [[1.0]*self.M for _ in range(max(0, N-1))] | |
| else: | |
| if len(T_between) != max(0, N-1): | |
| raise ValueError('@T_between length must be N-1') | |
| tmp = [] | |
| for t in T_between: | |
| if isinstance(t, (int,float)): | |
| tmp.append([max(float(t), self.Tmin) for _ in range(self.M)]) | |
| else: | |
| if len(t) != self.M: | |
| raise ValueError('each @T_between entry must be list of length M or scalar') | |
| tmp.append([max(float(x), self.Tmin) for x in t]) | |
| T_between = tmp | |
| # Insert tunnel if applied; allows K(MxM) optionals | |
| if tunnel is not None: | |
| tparams, pos = tunnel.get('params'), int(tunnel.get('pos', 0)) | |
| if pos < 0 or pos > N: | |
| raise ValueError('tunnel pos must be in 0..N') | |
| tL = float(tparams['L']) | |
| tD = [float(x) for x in tparams['D_list']] | |
| tmu = [max(float(x), self.eps_mu) for x in tparams['mu_list']] | |
| if len(tD) != self.M or len(tmu) != self.M: | |
| raise ValueError('tunnel D_list and mu_list must have a length M') | |
| tunnel_seg = {'L': tL, 'D_list': tD, 'mu_list': tmu} | |
| # Build the new segments with tunnel | |
| segs = segs[:pos]+[tunnel_seg]+segs[pos:] | |
| # Rebuild T_between so to match the new length; defaults 1.0 per-mode | |
| newT = [[1.0]*self.M for _ in range(len(segs)-1)] | |
| # Map original @T_between into newT | |
| for i in range(len(T_between)): | |
| if i < pos: newT[i] = T_between[i] | |
| else: newT[i+1] = T_between[i] | |
| # From your pos-controller override tunnel-specs provided | |
| if 'T_to_left' in tunnel and pos>0: | |
| tleft = tunnel['T_to_left'] | |
| if isinstance(tleft, (int, float)): | |
| newT[pos-1] = [max(float(tleft), self.Tmin)]*self.M | |
| else: | |
| if len(tleft)!=self.M: | |
| raise ValueError('mismatch T-Left length, check grid format') | |
| newT[pos-1] = [max(float(x), self.Tmin) for x in tleft] | |
| if 'T_to_right' in tunnel and pos < N: | |
| tright = tunnel['T_to_right'] | |
| if isinstance(tright, (int, float)): | |
| newT[pos] = [max(float(tright), self.Tmin)]*self.M | |
| else: | |
| if len(tright)!=self.M: | |
| raise ValueError('mismatch T-Right length, check grid format') | |
| newT[pos] = [max(float(x), self.Tmin) for x in tright] | |
| self.tunnel_pos, self.K, T_between = pos, tunnel.get('K'), newT | |
| else: | |
| self.tunnel_pos, self.K = None, None | |
| self.segments, self.N, self.T = segs, len(segs), T_between | |
| self.phi_right = [0.0]*self.M if phi_right is None else [float(x) for x in phi_right] | |
| self.phi_right_dirichlet = bool(phi_right_dirichlet) | |
| # Precompute all per-segment modal alphas | |
| self.alpha = [] | |
| for seg in self.segments: | |
| a_list = [] | |
| for m in range(self.M): | |
| Dm = seg['D_list'][m]; mum = seg['mu_list'][m] | |
| a = math.sqrt(mum/Dm) if mum > 0 else 0.0 | |
| a_list.append(a) | |
| self.alpha.append(a_list) | |
| #___________________________________________________________________________________ | |
| # Basis functions per-segment/per-mode, same @ scalar cases with ext M optional | |
| def _phi_basis_val(self, i: int, m: int, s: float, AB: tuple) -> float: | |
| a = self.alpha[i][m] | |
| A, B = AB | |
| if a > 0.0: | |
| return A*(CNST_EP**(a*s))+B*(CNST_EP**(-a*s)) | |
| else: | |
| return A+B*s | |
| def _dphi_basis_val(self, i: int, m: int, s: float, AB: tuple) -> float: | |
| a = self.alpha[i][m] | |
| A, B = AB | |
| if a > 0.0: | |
| return a*(A*(CNST_EP**(a*s))-B*(CNST_EP**(-a*s))) | |
| else: | |
| return B | |
| #___________________________________________________________________________________ | |
| def solve(self, phi_left: list) -> list: | |
| # @phi_left; list length M-modal values @ known left boundary | |
| # Solves linear system for all modal coefficients. Returns an | |
| # coefficents: Per-segment lists of (A_m,B_m) for m in 0..M-1 | |
| M, N = self.M, self.N | |
| nvar = 2*N*M | |
| # Build a dense matrix & rhs | |
| A, b = [[0.0]*nvar for _ in range(nvar)], [0.0]*nvar | |
| def var_index(isA: bool, seg_i: int, mode_m: int) -> int: | |
| return ((seg_i*M+mode_m)*2)+(0 if isA else 1) | |
| # Left boundary, for each mode m, phi_0(0)_m == phi_left[m] | |
| for m in range(M): | |
| row = m | |
| # phi_0(0) with per-basis AB coefficients | |
| A[row][var_index(True, 0, m)] = self._phi_basis_val(0, m, 0.0, (1.0, 0.0)) | |
| A[row][var_index(False, 0, m)] = self._phi_basis_val(0, m, 0.0, (0.0, 1.0)) | |
| b[row] = float(phi_left[m]) | |
| # Fill interface equations for each interface and each mode | |
| # *right boundary rows reserved at end, rows nvar-M..nvar-1 | |
| row = M | |
| for i in range(N-1): | |
| L_i, T_im = float(self.segments[i]['L']), self.T[i] | |
| for m in range(M): | |
| # phi_i(L_i)_m*T_im[m]-phi_{i+1}(0)_m = 0 | |
| Ai, Bi = var_index(True, i, m), var_index(False, i, m) | |
| Aj, Bj = var_index(True, i+1, m), var_index(False, i+1, m) | |
| A[row][Ai] = T_im[m]*self._phi_basis_val(i, m, L_i, (1.0, 0.0)) | |
| A[row][Bi] = T_im[m]*self._phi_basis_val(i, m, L_i, (0.0, 1.0)) | |
| A[row][Aj] = -self._phi_basis_val(i+1, m, 0.0, (1.0, 0.0)) | |
| A[row][Bj] = -self._phi_basis_val(i+1, m, 0.0, (0.0, 1.0)) | |
| b[row] = 0.0 | |
| row+=1 | |
| # And current continuity: D_i, m*dphi_i(L)-D_{i+1, m}*dphi_{i+1}(0) = 0 | |
| A[row][Ai] = float(self.segments[i]['D_list'][m])*self._dphi_basis_val(i, m, L_i, (1.0, 0.0)) | |
| A[row][Bi] = float(self.segments[i]['D_list'][m])*self._dphi_basis_val(i, m, L_i, (0.0, 1.0)) | |
| A[row][Aj] = -float(self.segments[i+1]['D_list'][m])*self._dphi_basis_val(i+1, m, 0.0, (1.0, 0.0)) | |
| A[row][Bj] = -float(self.segments[i+1]['D_list'][m])*self._dphi_basis_val(i+1, m, 0.0, (0.0, 1.0)) | |
| b[row] = 0.0 | |
| row+=1 | |
| # If tunnel coupling K is provided, replace the continuity equation @ | |
| # the tunnel interfaces with the coupled versions. Assume @tunnel_pos | |
| # is an index where tunnel segment was inserted. | |
| if self.tunnel_pos is not None and self.K is not None: | |
| p = self.tunnel_pos | |
| # Continuity eq between p-1 & p(left interface of tunnel) already set | |
| # above for each m. Need to replace that set with phi_{p-1}(L)*T_left | |
| # equals sum_n K_mn*phi_p(0)_n; find starting row corresponding to an | |
| # interface @i=p-1, compute where rows are. Each interface contribute | |
| # 2*M rows optional and/or interfaces are then in order i=0..N-2. Row | |
| # index for interface i optional will begin @ M+i*(2*M): | |
| start_row = M+(p-1)*(2*M) | |
| # Overwrite the optional 2*M rows starting at @start_row | |
| for rm in range(2*M): | |
| for col in range(nvar): A[start_row+rm][col] = 0.0 | |
| b[start_row+rm] = 0.0 | |
| # Fill coupled continuity(first M-rows) --> for each m in 0..M-1 | |
| T_left = self.T[p-1] | |
| for m in range(M): | |
| r0 = start_row+m | |
| # phi_{p-1}(L)*T_left[m] | |
| Ai, Bi = var_index(True, p-1, m), var_index(False, p-1, m) | |
| A[r0][Ai] = T_left[m]*self._phi_basis_val(p-1, m, self.segments[p-1]['L'], (1.0, 0.0)) | |
| A[r0][Bi] = T_left[m]*self._phi_basis_val(p-1, m, self.segments[p-1]['L'], (0.0, 1.0)) | |
| # right sum_n K_mn*phi_p(0)_n | |
| for n in range(M): | |
| # *right here Job'e(not job) does love you, semi answered->rO|《k_mn,I+[n^rO],..M-n | |
| Aj, Bj = var_index(True, p, n), var_index(False, p, n) | |
| k_mn = float(self.K[m][n]) | |
| A[r0][Aj]+=-k_mn*self._phi_basis_val(p, n, 0.0, (1.0, 0.0)) | |
| A[r0][Bj]+=-k_mn*self._phi_basis_val(p, n, 0.0, (0.0, 1.0)) | |
| # And current continuity; next M rows, keep @ scalar per-mode continuity | |
| # Eliminates cross-coupling with currents from any optional M*? squaring | |
| for m in range(M): | |
| r1 = start_row+M+m | |
| Ai, Bi = var_index(True, p-1, m), var_index(False, p-1, m) | |
| Aj, Bj = var_index(True, p, m), var_index(False, p, m) | |
| A[r1][Ai] = float(self.segments[p-1]['D_list'][m])*self._dphi_basis_val(p-1, m, self.segments[p-1]['L'], (1.0, 0.0)) | |
| A[r1][Bi] = float(self.segments[p-1]['D_list'][m])*self._dphi_basis_val(p-1, m, self.segments[p-1]['L'], (0.0, 1.0)) | |
| A[r1][Aj] = -float(self.segments[p]['D_list'][m])*self._dphi_basis_val(p, m, 0.0, (1.0, 0.0)) | |
| A[r1][Bj] = -float(self.segments[p]['D_list'][m])*self._dphi_basis_val(p, m, 0.0, (0.0, 1.0)) | |
| b[r1] = 0.0 | |
| # The current R-boundary rows, place Dirichlet | Neumann per-mode @ interface | |
| for m in range(M): | |
| r = nvar-M+m | |
| Ai, Bi = var_index(True, N-1, m), var_index(False, N-1, m) | |
| if self.phi_right_dirichlet: | |
| A[r][Ai] = self._phi_basis_val(N-1, m, self.segments[N-1]['L'], (1.0, 0.0)) | |
| A[r][Bi] = self._phi_basis_val(N-1, m, self.segments[N-1]['L'], (0.0, 1.0)) | |
| b[r] = float(self.phi_right[m]) | |
| else: | |
| A[r][Ai] = float(self.segments[N-1]['D_list'][m])*self._dphi_basis_val(N-1, m, self.segments[N-1]['L'], (1.0, 0.0)) | |
| A[r][Bi] = float(self.segments[N-1]['D_list'][m])*self._dphi_basis_val(N-1, m, self.segments[N-1]['L'], (0.0, 1.0)) | |
| b[r] = 0.0 | |
| x = self._solve_linear_system(A, b) | |
| # First, unpack the coefficients | |
| coeffs = [] | |
| for i in range(N): | |
| seg_coeffs = [] | |
| for m in range(M): | |
| seg_coeffs.append((x[var_index(True, i, m)], x[var_index(False, i, m)])) | |
| coeffs.append(seg_coeffs) | |
| self.coeffs = coeffs | |
| # Returns the modal current density into y: j_m = -D_{N-1, m}*dphi/ds @ s=L | |
| j_modal = [] | |
| for m in range(M): | |
| AB = coeffs[N-1][m] | |
| dphi_at_L = self._dphi_basis_val(N-1, m, self.segments[N-1]['L'], AB) | |
| j = -float(self.segments[N-1]['D_list'][m])*dphi_at_L | |
| j_modal.append(j) | |
| self.j_modal = j_modal | |
| return coeffs | |
| #___________________________________________________________________________________ | |
| def _solve_linear_system(self, A: list, b: list) -> list: | |
| n = len(A) | |
| M, rhs = [row[:] for row in A], b[:] | |
| for k in range(n): | |
| piv, maxv = k, abs(M[k][k]) | |
| for i in range(k+1, n): | |
| if abs(M[i][k]) > maxv: | |
| maxv, piv = abs(M[i][k]), i | |
| if maxv == 0.0: | |
| raise RuntimeError('singular matrix') | |
| if piv != k: | |
| M[k], M[piv] = M[piv], M[k] | |
| rhs[k], rhs[piv] = rhs[piv], rhs[k] | |
| pv = M[k][k] | |
| for j in range(k, n): M[k][j]/=pv | |
| rhs[k]/=pv | |
| for i in range(k+1, n): | |
| fac = M[i][k] | |
| if fac == 0.0: | |
| continue | |
| for j in range(k, n): M[i][j]-=fac*M[k][j] | |
| rhs[i]-=fac*rhs[k] | |
| x = [0.0]*n | |
| for i in range(n-1, -1, -1): | |
| s = rhs[i] | |
| for j in range(i+1, n): s-=M[i][j]*x[j] | |
| x[i] = s/M[i][i] if M[i][i] != 0 else s | |
| return x | |
| #___________________________________________________________________________________ | |
| def _apply_timed_tunnel(self, t, tau_func=None, omega_list=None, r_min=0.0, r_local=None, s_shape=8.0): | |
| # *If used to build the phased K&T --> done so before calling solve() | |
| # @t; an cascade progress in [0, 1] | |
| # @tau_func; function t->tau(default tau(t)=t) | |
| # @omega_list; list length M of the angular rates(rad per-unit tau) | |
| # @r_min; cutoff distance: if r_local < r_min then coupling suppressed | |
| # @r_local; list length N-1 giving local refraction distances per interface | |
| # @s_shape; steepness of smooth gate g(r) = sigmoid((r-r_min)*s_shape) | |
| # *Builds self._T_eff(list of per-interface lists length M or 2M if phases used) | |
| # *Builds self._K_eff(M×M real matrix or 2M×2M if phases used) tunneling | |
| # *If phases used, expands self.M -> 2*M temporarily for the solve | |
| if tau_func is None: tau_func = lambda tt: tt | |
| if omega_list is None: omega_list = [0.0]*self.M | |
| if r_local is None: r_local = [1e6]*(max(0, self.N-1)) | |
| def gate(r): | |
| z = (r-r_min)*s_shape | |
| return 1.0/(1.0+CNST_EP**(-z)) | |
| # Blend function for transmissions/tunnel's strength | |
| blend = lambda a, b: (1.0-t)*a+t*b | |
| self._T_eff = [] | |
| for i in range(len(self.T)): | |
| # The list length @ M | |
| base = self.T[i] | |
| # If tunnel present & interface is at tunnel pos, use tunnel-specific target if given | |
| target = base[:] | |
| if self.tunnel_pos is not None and (i == self.tunnel_pos-1 or i == self.tunnel_pos): | |
| # *self.T already contains tunnel entries; assume target is final grids stored | |
| target = self.T[i] | |
| # Apply bridged-gate based on @r_local | |
| g = gate(r_local[i]) if i < len(r_local) else 1.0 | |
| self._T_eff.append([g*blend(base[m], target[m]) for m in range(self.M)]) | |
| # Merge/build @K_eff if tunnel & K is current present | |
| self._K_eff = None | |
| if self.tunnel_pos is not None and self.K is not None: | |
| g = gate(r_local[self.tunnel_pos-1] if self.tunnel_pos-1 < len(r_local) else 1.0) | |
| K_final = self.K | |
| K_blend = [[(1.0-t)*(1.0 if i==j else 0.0)+t*K_final[i][j] for j in range(self.M)] for i in range(self.M)] | |
| K_gated = [[g*K_blend[i][j] for j in range(self.M)] for i in range(self.M)] | |
| # Apply phase, diagonal(exp(i -> omega -> tau)) | |
| tau, use_phase = tau_func(t), any(abs(w) > 0.0 for w in omega_list) | |
| if not use_phase: self._K_eff = K_gated | |
| else: | |
| # Expand to a real 2M×2M, K_complex = diagonal(e^{iωτ}) @ K_gated | |
| # Complex multiplication on it's vector [Re;Im] by real block matrix | |
| # If phase p_m= cos+i->sin -> [[Re(Kp), -Im(Kp)], [Im(Kp), Re(Kp)]] | |
| import math | |
| P = [complex(math.cos(w*tau), math.sin(w*tau)) for w in omega_list] | |
| # Compute Kp = K_gated*diag(P), apply phase on the 2nd factor | |
| Kp = [[ K_gated[i][j]*P[j] for j in range(self.M)] for i in range(self.M)] | |
| # Build the 2M×2M real matrix | |
| M2 = 2*self.M | |
| Keff = [[0.0]*M2 for _ in range(M2)] | |
| for i in range(self.M): | |
| for j in range(self.M): | |
| re, im = Kp[i][j].real, Kp[i][j].imag | |
| Keff[i][j] = re | |
| Keff[i][j+self.M] = -im | |
| Keff[i+self.M][j] = im | |
| Keff[i+self.M][j+self.M] = re | |
| self._K_eff = Keff | |
| # Need to expand @T_eff & an modal counts; set flag for solver to use | |
| # doubled M, expand @T_eff per-interface -> each modal scalar becomes | |
| # two identical entries yet with phase mixing applied in K only | |
| self._T_eff = [sum([[tval, tval] for tval in row ], []) for row in self._T_eff ] | |
| self._use_phase_expansion = True | |
| self._omega_list = omega_list | |
| else: self._use_phase_expansion = False | |
| # If no phase expansion, solver will use direct @T_eff & @K_eff | |
| self.T_work = getattr(self, '_T_eff', self.T) | |
| self.K_work = getattr(self, '_K_eff', self.K) | |
| return | |
| #___________________________________________________________________________________ | |
| def test(): | |
| # Two-mode chain: left seg, right seg | |
| segs = [{'L':1.0, 'D_list':[0.5, 0.3], 'mu_list':[1e-6, 0.02]}, | |
| {'L':1.2, 'D_list':[0.4, 0.25], 'mu_list':[0.01, 0.03]}] | |
| # Insert tunnel between @segs pos=1 with mixed K | |
| tunnel = {'params': {'L':0.05, 'D_list':[0.2, 0.2], 'mu_list':[1e-4, 1e-4]}, | |
| 'pos': 1, | |
| 'K': [[0.9, 0.1], [0.2, 0.8]], | |
| 'T_to_left': [0.8, 0.7], | |
| 'T_to_right': [0.7, 0.6]} | |
| M1 = 2 | |
| M2 = M1*2 | |
| chain = DiffusionChainModes(segs, M=M1, T_between=None, phi_right=[0.0, 0.0], | |
| phi_right_dirichlet=True, tunnel=tunnel, eps_mu=1e-8, | |
| Tmin=1e-9) | |
| # Tunnel params: | |
| t = 0.7 | |
| tau_func = lambda tt: tt*0.8 | |
| omega = [2.75, 4.75] # per-mode angular rates | |
| r_local = [0.58, 0.72] # interface distance(one) | |
| r_min = 1.21 | |
| s_shape = 42.4 | |
| chain._apply_timed_tunnel(t, tau_func, omega, r_min, r_local, s_shape) | |
| chain.T = chain.T_work | |
| chain.K = chain.K_work | |
| coeffs = chain.solve(phi_left=[1.45, 0.45]) | |
| print(f'\nModal currents into Y: {chain.j_modal}') | |
| # *To use in phase expansion. Above temporary, segments duplicated in | |
| # modal dimension -> @D_list & mu_list then T_between = self.T_work, | |
| # tunnel K = self.K_work(real 2M×2M), whereof phi_left duplicated as | |
| # [Re,Im]=[phi_left, 0] from using the same class repeated solves. | |
| test() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment