# Generazione della mesh a elementi shell quadrilateri sulla superficie # cilindrica della fascetta. # # La superficie è parametrizzata da (x, theta): x è la coordinata assiale, # theta quella circonferenziale. Al posto di theta si usa quasi ovunque # l'ascissa curvilinea arco = raggio_medio * theta, così la shell diventa un # rettangolo lunghezza × circonferenza e gli elementi sono tutti identici. # # I nodi formano una griglia (n_elementi_x + 1) × n_elementi_theta: in # direzione circonferenziale non c'è un nodo finale distinto perché theta = 0 # e theta = 2*pi sono lo stesso nodo. La periodicità è quindi strutturale, # nasce dalla connettività e non da vincoli imposti a posteriori. # # Ordine dei nodi locali di ogni elemento (antiorario nel piano x-arco): # # 4 ---- 3 arco # | | ^ # | | | # 1 ---- 2 +---> x import numpy as np def costruisci_mesh_cilindrica( lunghezza_m: float, raggio_medio_m: float, n_elementi_x: int, n_elementi_theta: int, ) -> dict: # Costruisce nodi e connettività della shell cilindrica. n_nodi_x = n_elementi_x + 1 n_nodi = n_nodi_x * n_elementi_theta n_elementi = n_elementi_x * n_elementi_theta lato_x_m = lunghezza_m / n_elementi_x circonferenza_m = 2.0 * np.pi * raggio_medio_m lato_arco_m = circonferenza_m / n_elementi_theta x_nodi_m = np.arange(n_nodi_x) * lato_x_m theta_nodi_rad = np.arange(n_elementi_theta) * (2.0 * np.pi / n_elementi_theta) arco_nodi_m = theta_nodi_rad * raggio_medio_m # Connettività: elemento (i, j) collega i nodi (i, j), (i+1, j), # (i+1, j+1), (i, j+1), con j+1 riavvolto sulla circonferenza. i_elem = np.repeat(np.arange(n_elementi_x), n_elementi_theta) j_elem = np.tile(np.arange(n_elementi_theta), n_elementi_x) j_succ = (j_elem + 1) % n_elementi_theta def indice(i, j): return i * n_elementi_theta + j connettivita = np.stack( [ indice(i_elem, j_elem), indice(i_elem + 1, j_elem), indice(i_elem + 1, j_succ), indice(i_elem, j_succ), ], axis=1, ) # Segmenti dei due bordi assiali (x = 0 e x = lunghezza): anelli chiusi di # n_elementi_theta segmenti, usati per la convezione sullo spessore. j = np.arange(n_elementi_theta) j_dopo = (j + 1) % n_elementi_theta segmenti_bordo = np.concatenate( [ np.stack([indice(0, j), indice(0, j_dopo)], axis=1), np.stack( [indice(n_elementi_x, j), indice(n_elementi_x, j_dopo)], axis=1 ), ] ) return { "n_elementi_x": n_elementi_x, "n_elementi_theta": n_elementi_theta, "n_elementi": n_elementi, "n_nodi_x": n_nodi_x, "n_nodi": n_nodi, "lunghezza_m": lunghezza_m, "raggio_medio_m": raggio_medio_m, "circonferenza_m": circonferenza_m, "lato_x_m": lato_x_m, "lato_arco_m": lato_arco_m, "x_nodi_m": x_nodi_m, "theta_nodi_rad": theta_nodi_rad, "arco_nodi_m": arco_nodi_m, "connettivita": connettivita, "segmenti_bordo": segmenti_bordo, "area_totale_m2": lunghezza_m * circonferenza_m, } def campo_su_griglia(mesh: dict, T: np.ndarray) -> np.ndarray: # Rimappa il vettore nodale sulla griglia (n_nodi_x, n_elementi_theta), # comoda per le mappe sviluppate e per le superfici 3D. return T.reshape(mesh["n_nodi_x"], mesh["n_elementi_theta"]) def coordinate_3d(mesh: dict, chiudi_circonferenza: bool = True): # Coordinate cartesiane dei nodi per la vista 3D: x lungo l'asse del # cilindro, y e z sulla sezione circolare. # # Con chiudi_circonferenza si ripete la prima colonna in coda, così la # superficie disegnata non mostra una fessura in theta = 0. È solo una # necessità di disegno: il nodo ripetuto non è un grado di libertà. theta = mesh["theta_nodi_rad"] if chiudi_circonferenza: theta = np.append(theta, 2.0 * np.pi) X, Theta = np.meshgrid(mesh["x_nodi_m"], theta, indexing="ij") R = mesh["raggio_medio_m"] return X, R * np.sin(Theta), R * np.cos(Theta) def chiudi_campo(campo: np.ndarray) -> np.ndarray: # Ripete la prima colonna circonferenziale in coda, in accordo con # coordinate_3d(chiudi_circonferenza=True). return np.concatenate([campo, campo[:, :1]], axis=1)