Files

120 lines
4.3 KiB
Python
Raw Permalink Normal View History

2026-08-03 00:18:00 +02:00
# 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)