def reset(self): self._count = -1 self._functional = dolfin_adjoint.Function(self._V, name="functional {}".format( self.model)) self.model_function = dolfin_adjoint.Function( self.model._V, name="model function {}".format(self.model)) self.data_function = dolfin_adjoint.Function( self.model._V, name="data function {}".format(self.model))
def __call__(self, u=None): if u is None: u = dolfin.Function( self._displacement_space, name="Zero displacement from strain observation", ) u_int = map_displacement( u, self._displacement_space, self._interpolation_space, self._approx ) # We need to correct for th reference deformation F = pulse.kinematics.DeformationGradient(u_int) * dolfin.inv(self._F_ref) # Compute the strains if self._tensor == "gradu": tensor = pulse.kinematics.EngineeringStrain(F, isochoric=self._isochoric) elif self._tensor == "E": tensor = pulse.kinematics.GreenLagrangeStrain(F, isochoric=self._isochoric) elif self._tensor == "almansi": tensor = pulse.kinematics.EulerAlmansiStrain(F, isochoric=self._isochoric) form = dolfin.inner(tensor * self.field, self.field) strain = dolfin_adjoint.Function(self._V, name="Simulated Strain") dolfin_adjoint.solve( dolfin.inner(self._trial, self._test) / self._vol * self._dmu == dolfin.inner(self._test, form) * self._dmu, strain, solver_parameters={"linear_solver": "gmres"}, ) return strain
def __call__(self, u=None): """ Arguments --------- u : :py:class:`dolfin.Function` The displacement """ if u is None: volume_form = (-1.0 / 3.0) * dolfin.dot(self._X, self._N) else: u_int = map_displacement( u, self._displacement_space, self._interpolation_space, self._approx ) # Compute volume F = pulse.kinematics.DeformationGradient(u_int) J = pulse.kinematics.Jacobian(F) volume_form = (-1.0 / 3.0) * dolfin.dot( self._X + u_int, J * dolfin.inv(F).T * self._N ) volume = dolfin_adjoint.Function(self._V, name="Simulated volume") # Make a project for dolfin-adjoint recording dolfin_adjoint.solve( dolfin.inner(self._trial, self._test) / self._endoarea * self._dmu == dolfin.inner(volume_form, self._test) * self._dmu, volume, ) return volume
def assign_control(self, value, annotate=True): """ Assign value to control parameter """ control_new = dolfin_adjoint.Function(self.control.function_space(), name="new control") if isinstance( value, ( dolfin.Function, dolfin_adjoint.Function, RegionalParameter, MixedParameter, ), ): control_new.assign(value) elif isinstance(value, float) or isinstance(value, int): numpy_mpi.assign_to_vector(control_new.vector(), np.array([value])) elif isinstance(value, pyadjoint.enlisting.Enlist): val_delisted = dolfin_adjoint.delist(value, self.controls) control_new.assign(val_delisted) else: numpy_mpi.assign_to_vector(control_new.vector(), numpy_mpi.gather_broadcast(value)) self.control.assign(control_new, annotate=annotate)
def test_volume(volumes): geo = pulse.HeartGeometry.from_file(pulse.mesh_paths["simple_ellipsoid"]) V_cg2 = dolfin.VectorFunctionSpace(geo.mesh, "CG", 2) u = dolfin_adjoint.Function(V_cg2) volume_obs = VolumeObservation(mesh=geo.mesh, dmu=geo.ds(geo.markers["ENDO"]), description="Test LV volume") model_volume = volume_obs(u).vector().get_local()[0] target = OptimizationTarget(volumes, volume_obs, collect=True) if np.isscalar(volumes): volumes = (volumes, ) fs = [] for t, v in zip(target, volumes): fun = dolfin.project(t.assign(u), t._V) f = fun.vector().get_local()[0] fs.append(f) g = (model_volume - v)**2 assert abs(f - g) < 1e-12 # We collect the data assert np.all( np.subtract( np.squeeze([v.get_local() for v in target.collector["data"]]), volumes) < 1e-12) assert np.all( np.subtract( np.squeeze([v.get_local() for v in target.collector["model"]]), model_volume) < 1e-12) assert np.all(np.subtract(target.collector["functional"], fs) < 1e-12)
def solve_problem_variational_form(self): u = da.Function(self.V) du = fa.TrialFunction(self.V) v = fa.TestFunction(self.V) E = self._energy_density(u) * fa.dx dE = fa.derivative(E, u, v) jacE = fa.derivative(dE, u, du) da.solve(dE == 0, u, self.bcs, J=jacE) return u
def debug(self): _, B_np = self.compute_operators() dof_data = np.random.rand(self.num_dofs) u = da.Function(self.V) u.vector()[:] = dof_data L1 = da.assemble(u * u * fa.dx) L2 = (np.matmul(B_np, dof_data) * dof_data).sum() print(L1) print(L2)
def _obj(self, x): f = da.Function(self.poisson.V) f.vector()[:] = x self.poisson.source = f u = self.poisson.solve_problem_variational_form() L_tape = da.assemble( (0.5 * fa.inner(u - self.target_u, u - self.target_u) + 0.5 * self.alpha * fa.inner(f, f)) * fa.dx) L = float(L_tape) return L, L_tape, f
def test_volume_CG2(approx): u = dolfin_adjoint.Function(V_cg2) volume_obs = VolumeObservation( mesh=geo.mesh, dmu=geo.ds(geo.markers["ENDO"]), approx=approx, description="Test LV volume", ) v1 = volume_obs() assert norm(v1.vector().get_local() - 2.51130402) < 1e-8 v2 = volume_obs(u) assert norm(v2.vector().get_local() - 2.51130402) < 1e-8
def __init__(self, bcs, solver_parameters, pressure, params, annotate=False): passive_filling_duration = solver_parameters[ "passive_filling_duration"] self.acin = params["active_contraction_iteration_number"] fname = "active_state_{}.h5".format(self.acin) if os.path.isfile(fname): if dolfin.mpi_comm_world().rank == 0: os.remove(fname) BasicHeartProblem.__init__(self, bcs, solver_parameters, pressure) # Load the state from the previous iteration w_temp = dolfin_adjoint.Function(self.solver.state_space, name="w_temp") with dolfin.HDF5File(dolfin.mpi_comm_world(), params["sim_file"], "r") as h5file: # Get previous state if params["active_contraction_iteration_number"] == 0: it = (passive_filling_duration if params["unload"] else passive_filling_duration - 1) group = "/".join([ params["h5group"], PASSIVE_INFLATION_GROUP, "states", str(it) ]) else: group = "/".join([ params["h5group"], ACTIVE_CONTRACTION_GROUP.format( params["active_contraction_iteration_number"] - 1), "states", "0", ]) h5file.read(w_temp, group) self.solver.reinit(w_temp, annotate=annotate) self.solver.solve()
W = fa.FunctionSpace(mesh, "DG", 0) # g is the ground truth for source term # f is the control variable if case_flag == 0: g = da.interpolate( da.Expression("1/(1+alpha*4*pow(pi, 4))*w", w=w, alpha=alpha, degree=3), W) f = da.interpolate( da.Expression("1/(1+alpha*4*pow(pi, 4))*w", w=w, alpha=alpha, degree=3), W) else: g = da.interpolate(da.Expression(("sin(2*pi*x[0])"), degree=3), W) f = da.interpolate(da.Expression(("sin(2*pi*x[0])"), degree=3), W) u = da.Function(V, name='State') v = fa.TestFunction(V) F = (fa.inner(fa.grad(u), fa.grad(v)) - f * v) * fa.dx bc = da.DirichletBC(V, 0.0, "on_boundary") da.solve(F == 0, u, bc) d = da.Function(V) d.vector()[:] = u.vector()[:] J = da.assemble((0.5 * fa.inner(u - d, u - d)) * fa.dx + alpha / 2 * f**2 * fa.dx) control = da.Control(f) rf = da.ReducedFunctional(J, control) # set the initial value to be zero for optimization
def __call__(self, value): logger.debug("\nEvaluate functional...") # annotation.annotate = True # Start recording # dolfin_adjoint.adj_reset() tape = dolfin_adjoint.get_working_tape() tape.reset_blocks() self.collector["count"] += 1 new_control = dolfin_adjoint.Function(self.control.function_space(), name="new_control") # self.assign_control(value, annotate=annotation.annotate) # annotation.annotate = False if isinstance( value, ( dolfin_adjoint.Function, dolfin.Function, dolfin_adjoint.Constant, dolfin.Constant, RegionalParameter, MixedParameter, ), ): new_control.assign(value) elif isinstance(value, float) or isinstance(value, int): numpy_mpi.assign_to_vector(new_control.vector(), np.array([value])) # elif isinstance(value, pyadjoint.enlisting.Enlist): else: numpy_mpi.assign_to_vector(new_control.vector(), numpy_mpi.gather_broadcast(value)) if self.verbose: arr = numpy_mpi.gather_broadcast(new_control.vector().array()) msg = ("\nCurrent value of control:" "\n\t{:>8}\t{:>8}\t{:>8}\t{:>8}\t{:>8}" "\n\t{:>8.2f}\t{:>8.2f}\t{:>8.2f}\t{:>8d}\t{:>8d}").format( "Min", "Mean", "Max", "argmin", "argmax", np.min(arr), np.mean(arr), np.max(arr), np.argmin(arr), np.argmax(arr), ) logger.debug(msg) # Change loglevel to avoid too much printing change_log_level = (self.log_level == logging.INFO) and not self.verbose if change_log_level: logger.setLevel(logging.WARNING) t = dolfin.Timer("Forward run") t.start() logger.debug("\nEvaluate forward model") # annotation.annotate = True crash = False try: self.forward_result = self.forward_model(new_control, annotate=True) except SolverDidNotConverge: crash = True forward_time = t.stop() self.collector["forward_times"].append(forward_time) logger.debug(("Evaluating forward model done. " "Time to evaluate = {} seconds".format(forward_time))) if change_log_level: logger.setLevel(self.log_level) if self.first_call: # Store initial results self.collector["initial_results"] = self.forward_result self.first_call = False # Some printing # logger.info(utils.print_head(self.for_res)) control = dolfin_adjoint.Control(self.control) # dolfin_adjoint.ReducedFunctional.__init__( # self, dolfin_adjoint.Functional(self.forward_result.functional), control # ) super().__init__(self.forward_result.functional, control) if crash: # This exection is thrown if the solver uses more than x steps. # The solver is stuck, return a large value so it does not get # stuck again logger.warning( Text.red(("Iteration limit exceeded. " "Return a large value of the functional"))) # Return a big value, and make sure to increment the big value # so the the next big value is different from the current one. func_value = np.inf self.collector["nr_crashes"] += 1 else: func_value = self.forward_result.functional # grad_norm = None if len(self.grad_norm_scaled) == 0 \ # else self.grad_norm_scaled[-1] self.collector["functional_values"].append( float(func_value) * self.scale) self.collector["controls"].append(dolfin.Vector(self.control.vector())) logger.debug(Text.yellow("Stop annotating")) annotation.annotate = False self.print_line() return self.scale * func_value
def main(): N = 5 mesh = da.UnitCubeMesh(N, N, N) W = df.FunctionSpace(mesh, "CG", 1) active = da.Function(W) problem = create_forward_problem(mesh, active, active_value=0.1) u, _ = problem.state.split() V = df.VectorFunctionSpace(mesh, "CG", 2) u_synthetic = da.project(u, V) active_ctrl = da.Function(W) problem = create_forward_problem(mesh, active_ctrl, active_value=0.0) u_model, _ = problem.state.split() J = cost_function( u_model, u_synthetic, ) cost_func_values = [] control_values = [] def eval_cb(j, m): """Callback function""" cost_func_values.append(j) control_values.append(m) reduced_functional = da.ReducedFunctional( J, da.Control(active_ctrl), eval_cb_post=eval_cb, ) problem = da.MinimizationProblem(reduced_functional, bounds=[(0, 0.4)]) parameters = { "limited_memory_initialization": "scalar2", "maximum_iterations": 10, } solver = da.IPOPTSolver(problem, parameters=parameters) optimal_control = solver.solve() control_error = [ df.assemble((c - active)**2 * df.dx) for c in control_values ] fig, ax = plt.subplots(2, 1, sharex=True) ax[0].semilogy(cost_func_values) ax[0].set_xlabel("#iterations") ax[0].set_ylabel("cost function") ax[1].semilogy(control_error) ax[1].set_xlabel("#iterations") ax[1].set_ylabel(r"$\int (\gamma - \gamma^*) \mathrm{d}x$") fig.tight_layout() fig.savefig("results") mean = optimal_control.vector().get_local().mean() std = optimal_control.vector().get_local().std() print(f"Optimal control is {mean} +/- {std}")
def create_problem(): geometry = pulse.HeartGeometry.from_file( pulse.mesh_paths["simple_ellipsoid"]) activation = dolfin_adjoint.Function(dolfin.FunctionSpace( geometry.mesh, "R", 0), name="activation") activation.assign(dolfin_adjoint.Constant(0.0)) matparams = pulse.HolzapfelOgden.default_parameters() V = dolfin.FunctionSpace(geometry.mesh, "R", 0) control = dolfin_adjoint.Function(V) control.assign(dolfin_adjoint.Constant(1.0)) # control = dolfin_adjoint.Constant(1.0, name="matparam control (a)") matparams["a"] = control f0 = dolfin_adjoint.Function(geometry.f0.function_space()) f0.assign(geometry.f0) s0 = dolfin_adjoint.Function(geometry.s0.function_space()) s0.assign(geometry.s0) n0 = dolfin_adjoint.Function(geometry.n0.function_space()) n0.assign(geometry.n0) material = pulse.HolzapfelOgden(activation=activation, parameters=matparams, f0=f0, s0=s0, n0=n0) # LV Pressure lvp = dolfin_adjoint.Constant(0.0, name="lvp") lv_marker = geometry.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 robin_bc = [ pulse.RobinBC( value=dolfin_adjoint.Constant(base_spring, name="base_spring"), marker=geometry.markers["BASE"][0], ) ] # Fix the basal plane in the longitudinal direction # 0 in V.sub(0) refers to x-direction, which is the longitudinal direction def fix_basal_plane(W): V = W if W.sub(0).num_sub_spaces() == 0 else W.sub(0) bc = dolfin_adjoint.DirichletBC( V.sub(0), dolfin.Constant(0.0, name="fix_base"), geometry.ffun, geometry.markers["BASE"][0], ) return bc dirichlet_bc = [fix_basal_plane] # Collect boundary conditions bcs = pulse.BoundaryConditions(dirichlet=dirichlet_bc, neumann=neumann_bc, robin=robin_bc) # Create the problem problem = pulse.MechanicsProblem(geometry, material, bcs) return problem, control
def set_control_variable(self, dof_data): self.source = da.Function(self.V) self.source.vector()[:] = dof_data