def assign_sub(self, f, i): """ Assign subfunction :param f: The function you want to assign :param int i: The subspace number """ f_ = Function(self.basespace) f_.assign(f) self.function_assigner[i].assign(self.split()[i], f_)
class MechanicsProblem(object): """ Base class for mechanics problem """ def __init__( self, geometry: Geometry, material: Material, bcs=None, bcs_parameters=None, solver_parameters=None, ): logger.debug("Initialize mechanics problem") self.geometry = geometry self.material = material self._handle_bcs(bcs=bcs, bcs_parameters=bcs_parameters) # Make sure that the material has microstructure information for attr in ("f0", "s0", "n0"): setattr(self.material, attr, getattr(self.geometry, attr)) self.solver_parameters = NonlinearSolver.default_solver_parameters() if solver_parameters is not None: self.solver_parameters.update(**solver_parameters) self._init_spaces() self._init_forms() def _handle_bcs(self, bcs, bcs_parameters): if bcs is None: if isinstance(self.geometry, HeartGeometry): self.bcs_parameters = MechanicsProblem.default_bcs_parameters() if bcs_parameters is not None: self.bcs_parameters.update(**bcs_parameters) else: raise ValueError(("Please provive boundary conditions " "to MechanicsProblem"), ) self.bcs = cardiac_boundary_conditions(self.geometry, **self.bcs_parameters) else: self.bcs = bcs # TODO: FIX THIS or require this # Just store this as well in case both is provided self.bcs_parameters = MechanicsProblem.default_bcs_parameters() if bcs_parameters is not None: self.bcs_parameters.update(**bcs_parameters) @staticmethod def default_bcs_parameters(): return dict(pericardium_spring=0.0, base_spring=0.0, base_bc="fixed") def _init_spaces(self): logger.debug("Initialize spaces for mechanics problem") mesh = self.geometry.mesh P2 = dolfin.VectorElement("Lagrange", mesh.ufl_cell(), 2) P1 = dolfin.FiniteElement("Lagrange", mesh.ufl_cell(), 1) # P2_space = FunctionSpace(mesh, P2) # P1_space = FunctionSpace(mesh, P1) self.state_space = dolfin.FunctionSpace(mesh, P2 * P1) self.state = Function(self.state_space, name="state") self.state_test = dolfin.TestFunction(self.state_space) def _init_forms(self): logger.debug("Initialize forms mechanics problem") # Displacement and hydrostatic_pressure u, p = dolfin.split(self.state) v, q = dolfin.split(self.state_test) # Some mechanical quantities F = dolfin.variable(kinematics.DeformationGradient(u)) J = kinematics.Jacobian(F) dx = self.geometry.dx internal_energy = (self.material.strain_energy(F, ) + self.material.compressibility(p, J)) self._virtual_work = dolfin.derivative( internal_energy * dx, self.state, self.state_test, ) external_work = self._external_work(u, v) if external_work is not None: self._virtual_work += external_work self._set_dirichlet_bc() self._jacobian = dolfin.derivative( self._virtual_work, self.state, dolfin.TrialFunction(self.state_space), ) self._init_solver() def _init_solver(self): if has_dolfin_adjoint: self._problem = NonlinearVariationalProblem( J=self._jacobian, F=self._virtual_work, u=self.state, bcs=self._dirichlet_bc, ) self.solver = NonlinearVariationalSolver(self._problem) else: self._problem = NonlinearProblem( J=self._jacobian, F=self._virtual_work, bcs=self._dirichlet_bc, ) self.solver = NonlinearSolver( self._problem, self.state, parameters=self.solver_parameters, ) def _set_dirichlet_bc(self): # DirichletBC for dirichlet_bc in self.bcs.dirichlet: msg = ("DirichletBC only implemented for as " "callable. Please provide DirichletBC " "as a callable with argument being the " "state space only") if hasattr(dirichlet_bc, "__call__"): try: self._dirichlet_bc = dirichlet_bc(self.state_space) except Exception as ex: logger.error(msg) raise ex else: raise NotImplementedError(msg) def _external_work(self, u, v): F = dolfin.variable(kinematics.DeformationGradient(u)) N = self.geometry.facet_normal ds = self.geometry.ds dx = self.geometry.dx external_work = [] for neumann in self.bcs.neumann: n = neumann.traction * ufl.cofac(F) * N external_work.append(dolfin.inner(v, n) * ds(neumann.marker)) for robin in self.bcs.robin: external_work.append( dolfin.inner(robin.value * u, v) * ds(robin.marker)) for body_force in self.bcs.body_force: external_work.append( -dolfin.derivative(dolfin.inner(body_force, u) * dx, u, v), ) if len(external_work) > 0: return list_sum(external_work) return None def reinit(self, state, annotate=False): """Reinitialze state""" if has_dolfin_adjoint: try: self.state.assign(state, annotate=annotate) except Exception as ex: print(ex) self.state.assign(state) else: self.state.assign(state) self._init_forms() @staticmethod def default_solver_parameters(): return NonlinearSolver.default_solver_parameters() def solve(self): r""" Solve the variational problem .. math:: \delta W = 0 """ logger.debug("Solving variational problem") # Get old state in case of non-convergence if has_dolfin_adjoint: try: old_state = self.state.copy(deepcopy=True, name="Old state (pulse)") except TypeError: # In case this is a dolfin fuction and not a # dolfin-adjoint function old_state = self.state.copy(deepcopy=True) else: old_state = self.state.copy(deepcopy=True) try: logger.debug("Try to solve") nliter, nlconv = self.solver.solve() if not nlconv: logger.debug("Failed") raise SolverDidNotConverge("Solver did not converge...") except RuntimeError as ex: logger.debug("Failed") logger.debug("Reintialize old state and raise exception") self.reinit(old_state) raise SolverDidNotConverge(ex) from ex else: logger.debug("Sucess") return nliter, nlconv def get_displacement(self, annotate=False): D = self.state_space.sub(0) V = D.collapse() fa = FunctionAssigner(V, D) u = Function(V, name="displacement") if has_dolfin_adjoint: fa.assign(u, self.state.split()[0], annotate=annotate) else: fa.assign(u, self.state.split()[0]) return u def SecondPiolaStress(self): u, p = self.state.split(deepcopy=True) F = kinematics.DeformationGradient(u) return self.material.SecondPiolaStress(F, p) def FirstPiolaStress(self): u, p = self.state.split(deepcopy=True) F = kinematics.DeformationGradient(u) return self.material.FirstPiolaStress(F, p) def ChachyStress(self): u, p = self.state.split(deepcopy=True) F = kinematics.DeformationGradient(u) return self.material.CauchyStress(F, p) def GreenLagrangeStrain(self): u, p = self.state.split(deepcopy=True) F = kinematics.DeformationGradient(u) return kinematics.GreenLagrangeStrain(F)
) fiber_space = dolfin.FunctionSpace(mesh, fiber_element) fiber = Function(fiber_space, "data/fiber.xml") microstructure = pulse.Microstructure(f0=fiber) # Create the geometry geometry = pulse.HeartGeometry( mesh=mesh, markers=markers, marker_functions=marker_functions, microstructure=microstructure, ) activation = Function(dolfin.FunctionSpace(geometry.mesh, "R", 0)) activation.assign(Constant(0.0)) matparams = pulse.HolzapfelOgden.default_parameters() material = pulse.HolzapfelOgden( activation=activation, parameters=matparams, f0=geometry.f0, ) # LV Pressure lvp = Constant(0.0) lv_marker = markers["ENDO"][0] lv_pressure = pulse.NeumannBC(traction=lvp, marker=lv_marker, name="lv") neumann_bc = [lv_pressure] # Add spring term at the base with stiffness 1.0 kPa/cm^2 base_spring = 1.0
# Loop over all modes and collect the stress values modes = ["fs", "fn", "sf", "sn", "nf", "ns"] stress: dict[str, dict[str, list[float]]] = { "cauchy": {m: [] for m in modes}, "pk1": {m: [] for m in modes}, } shear_values = np.linspace(0, 0.6, 10) recompute = True # Solve problem results_file = Path("result.npy") if recompute or not results_file.is_file(): for mode in modes: x.assign(zero) experiment = create_experiment(mode) for shear in shear_values: pulse.iterate.iterate( experiment.problem, x, experiment.increment(shear), reinit_each_step=True, ) stress["cauchy"][mode].append( experiment.shear_component( experiment.problem.ChachyStress()), ) stress["pk1"][mode].append( experiment.shear_component( experiment.problem.FirstPiolaStress()), )