Hi, I’m new to FenicsX, so excuse me if this is too basic. I’m coming from this tutorial trying to simulate the elastic response of a wind turbine rotor, so I’m dealing with a transient elastic problem.
The problem involves a two-blade rotor, where I want to consider gravity, centrifugal force, and wind speed load. The rotor rotates around the X-axis. Currently, I’m using a fixed vector as a wind load, but eventually, this load will be derived from real data or CFD computations. For now, it’s a single vector.
Right now, I have two questions:
- How do I apply the wind load only over the rotor surface? (The mesh and geometry are created with Gmsh and read from the .msh file.), At this point I discover how to apply it over all the surface, but I want to apply over the selected surface marked with the tag
GMSH_TAG_SURFACE
. Searching on the forum I found several answers about using subdomains like this:
ds = fe.Measure('ds', domain=mesh, subdomain_data=subdomains)
but I can’t find how to create the subdomain in my example, with the FenicsX
- I’m not sure if I’m heading in the right direction by handling the rotation through applying geometry transformations in the solve loop. I’m uncertain if this breaks the continuity of the system. I think that I have to apply the rotation also to the displacment field, Im really lost here
Here is my code at the moment:
Mesh can be downloaded here: Very Coarse Mesh File
I have modified my example code to give a working example and fix some errors…
import numpy as np
from mpi4py import MPI
import ufl
import dolfinx.fem.petsc
from dolfinx import fem, io, default_scalar_type
from scipy.spatial.transform import Rotation
comm = MPI.COMM_WORLD
# ------------- #
# Problem Setup #
# ------------- #
# material properties
E = 210e3
nu = 0.3
rho = 2e-3
# fixed rotation speed
rpm = -72 # rpm
omega = (rpm / 60) * 2 * ufl.pi
# loads
gravity = (0, 9.81, 0)
wind_load = (10, 0, 0)
# Timing
startTime = 0.0
endTime = 30.0
deltaT = 0.001
writeInterval = 1 # timestep
# Newmark parameters
# which ensures unconditional stability,
# energy conservation and second-order accuracy
beta_ = 0.25
gamma_ = 0.5
# ------- #
# MESHING #
# ------- #
GMSH_TAG_FIX = 1003
GMSH_TAG_SURFACE = 1004
mesh_file = "NREL_Phase_VI_rotor.msh" # attached file url
domain, cell_markers, facet_markers = io.gmshio.read_from_msh(mesh_file, comm, gdim=3)
# -------------- #
# Function Space #
# -------------- #
dim = domain.geometry.dim
degree = 2
V = fem.functionspace(domain, ("Lagrange", degree, (dim,)))
u = fem.Function(V, name="Displacement")
# Here how to select only the GMSH_TAG_SURFACE as subdomain
# for surface integration ?
ds = ufl.Measure("ds", domain=domain)
# ------------------- #
# Boundary conditions #
# ------------------- #
# Wind Load
# rotor_facets = facet_markers.find(GMSH_TAG_SURFACE)
# rotor_surf = fem.locate_dofs_topological(V, 2, rotor_facets)
# f_w = fem.Constant(domain, default_scalar_type(wind_load)) # <== how apply only to the rotor_facets?
# Gravitational load
f_g = fem.Constant(domain, default_scalar_type(gravity))
# Centrifugal acceleration (-rho*omega^2*x_i) load
# x = ufl.SpatialCoordinate(domain)
# f_c = ufl.as_vector((0, -rho * omega**2 * x[1], -rho * omega**2 * x[2]))
# Rotation angle
theta = fem.Constant(domain, 0.0)
# Fixed BC
fixed_facets = facet_markers.find(GMSH_TAG_FIX)
fixed_surface = fem.locate_dofs_topological(V, 2, fixed_facets)
fixed_bc = fem.dirichletbc(np.array([0.0, 0.0, 0.0]), fixed_surface, V)
# ------------------------ #
# linear elastic equations #
# ------------------------ #
E = fem.Constant(domain, E)
nu = fem.Constant(domain, nu)
rho = fem.Constant(domain, rho)
lmbda = E * nu / (1 + nu) / (1 - 2 * nu)
mu = E / 2 / (1 + nu)
def epsilon(v):
return ufl.sym(ufl.grad(v))
def sigma(v):
return lmbda * ufl.tr(epsilon(v)) * ufl.Identity(dim) + 2 * mu * epsilon(v)
# ------------------- #
# Time discretization #
# ------------------- #
# prev time step
u_old = fem.Function(V)
v_old = fem.Function(V)
a_old = fem.Function(V)
# current time step
a_new = fem.Function(V)
v_new = fem.Function(V)
beta = fem.Constant(domain, beta_)
gamma = fem.Constant(domain, gamma_)
dt = fem.Constant(domain, 0.0)
a = 1 / beta / dt**2 * (u - u_old - dt * v_old) + a_old * (1 - 1 / 2 / beta)
a_expr = fem.Expression(a, V.element.interpolation_points())
v = v_old + dt * ((1 - gamma) * a_old + gamma * a)
v_expr = fem.Expression(v, V.element.interpolation_points())
# ------------------------------- #
# mass, a stiffness and a damping #
# ------------------------------- #
u_ = ufl.TestFunction(V)
du = ufl.TrialFunction(V)
eta_M = fem.Constant(domain, 1e-4)
eta_K = fem.Constant(domain, 1e-4)
def mass(u, u_):
return rho * ufl.dot(u, u_) * ufl.dx
def stiffness(u, u_):
return ufl.inner(sigma(u), epsilon(u_)) * ufl.dx
def damping(u, u_):
return eta_M * mass(u, u_) + eta_K * stiffness(u, u_)
L = ufl.dot(f_g, u_) * ufl.dx
# L += ufl.dot(f_c, u_) * ufl.dx # include centrigual force
# L += ufl.dot(f_w, u_) * ufl.ds # include wind loads over surface
Residual = mass(a, u_) + damping(v, u_) + stiffness(u, u_) - L
Residual_du = ufl.replace(Residual, {u: du})
a_form = ufl.lhs(Residual_du)
L_form = ufl.rhs(Residual_du)
bcs = [fixed_bc]
problem = fem.petsc.LinearProblem(
a_form, L_form, u=u, bcs=bcs, petsc_options={"ksp_type": "preonly", "pc_type": "lu"}
)
# ---------- #
# Solve loop #
# ---------- #
time_steps = int(endTime / deltaT)
t = startTime
i = 0
current_theta = 0
vtk = io.VTKFile(domain.comm, "test_results/elastodynamics.pvd", "w")
running = True
while running:
if t > 0:
# start rotation arown X axis, this is correct?
theta.value = omega * deltaT
rotation = Rotation.from_euler("x", theta.value, degrees=False)
domain.geometry.x[:, :] = rotation.apply(domain.geometry.x)
current_theta += theta.value
if i % writeInterval == 0:
vtk.write_function(u, t)
dt.value = deltaT
t += deltaT
# do I have to re-apply the loads here due to the domain rotatio?
f_g.value = np.array(gravity)
# f_w.value = np.array(wind_load)
problem.solve()
u.x.scatter_forward() # updates ghost values for parallel computations
# compute new velocity v_n+1
v_new.interpolate(v_expr)
# compute new acceleration a_n+1
a_new.interpolate(a_expr)
# update u_n with u_n+1
u.vector.copy(u_old.vector)
# update v_n with v_n+1
v_new.vector.copy(v_old.vector)
# update a_n with a_n+1
a_new.vector.copy(a_old.vector)
if comm.rank == 0:
print(f"Time: {t:.0e}s, Current Angle: {np.rad2deg(current_theta)%360:.2f}º")
running = t <= endTime
i += 1
vtk.close()