def test_spatial_convergence_is_two(self, sw_linear_problem_parameters, sin_ic): # Compute the errors errors = [] levels = 3 finish_time = 5. for l in range(levels): mesh_x = 4 * 2**l mesh_y = 2 time_step = 0.25 model = setup_model(sw_linear_problem_parameters, sin_ic, time_step, finish_time, mesh_x, mesh_y) error = compute_error(*model) errors.append(error) # Compute the orders of convergence conv = [] for i in range(len(errors)-1): convergence_order = -math.log(errors[i+1]/errors[i], 2) conv.append(convergence_order) # Check the minimum convergence order and exit log(INFO, "Spatial order of convergence (expecting 2.0): %s." % conv) assert min(conv) > 1.9
def test_derivative(self): turbine = BumpTurbine(diameter=10.0, friction=1.0) domain = RectangularDomain(0, 0, 3000, 1000, 20, 3) farm = Farm(domain, turbine) farm.add_turbine((1500, 500)) ieq = farm.minimum_distance_constraints() # Only test the correctness of the first inequality constraint for simplicity ieqcons_J = lambda m: ieq.function(m)[0] ieqcons_dJ = lambda m, forget=False: ieq.jacobian(m)[0] x = numpy.array([1., 2., 3., 4., 7., 1., 6., 9.]) minconv = helpers.test_gradient_array(ieqcons_J, ieqcons_dJ, x) assert minconv > 1.99 ieq = ConvexPolygonSiteConstraint(farm, [[0, 0], [10, 0], [10, 10]]) # Only test the correctness of the first inequality constraint for # simplicity. ieqcons_J = lambda m: ieq.function(m)[0] ieqcons_dJ = lambda m, forget=False: ieq.jacobian(m)[0] # This will raise a RunetimeWarning, which is fine because we expect # division by zero x = numpy.array([0, 0, 10, 4, 20, 8]) minconv = helpers.test_gradient_array(ieqcons_J, ieqcons_dJ, x=x, seed=0.1) # These constraints are linear so we expect no convergence at all. # Let's check that the tolerance is not above a threshold log(INFO, "Expecting a Nan convergence order") assert math.isnan(minconv)
def test_spatial_convergence_is_two(self, sw_linear_problem_parameters, sin_ic): # Compute the errors errors = [] levels = 3 finish_time = 5. for l in range(levels): mesh_x = 4 * 2**l mesh_y = 2 time_step = 0.25 model = setup_model(sw_linear_problem_parameters, sin_ic, time_step, finish_time, mesh_x, mesh_y) error = compute_error(*model) errors.append(error) # Compute the orders of convergence conv = [] for i in range(len(errors) - 1): convergence_order = -math.log(errors[i + 1] / errors[i], 2) conv.append(convergence_order) # Check the minimum convergence order and exit log(INFO, "Spatial order of convergence (expecting 2.0): %s." % conv) assert min(conv) > 1.9
def load_checkpoint(self, filename): try: self.memo = pickle.load(open(filename, "rb")) except IOError: log(WARNING, "Warning: Checkpoint file '%s' not found." % filename) except ValueError: log(WARNING, "Error: Checkpoint file '%s' is invalid." % filename)
def function(self, m): """Return an object which must be zero for the point to be feasible. :param m: The serialized paramaterisation of the turbines. :tpye m: numpy.ndarray. :returns: numpy.ndarray -- each entry must be zero for a poinst to be feasible. """ dolfin.log(dolfin.PROGRESS, "Calculating minimum distance constraints.") inequality_constraints = [] for i in range(len(m)/2): for j in range(len(m)/2): if i <= j: continue inequality_constraints.append(self._sl2norm([m[2*i]-m[2*j], m[2*i+1]-m[2*j+1]]) - self._minimum_distance**2) inequality_constraints = numpy.array(inequality_constraints) if any(inequality_constraints <= 0): dolfin.log(dolfin.WARNING, "Minimum distance inequality constraints (should all " "be > 0): %s" % inequality_constraints) return inequality_constraints
def get_distance_function(config, domains): V = dolfin.FunctionSpace(config.domain.mesh, "CG", 1) v = dolfin.TestFunction(V) d = dolfin.TrialFunction(V) sol = dolfin.Function(V) s = dolfin.interpolate(Constant(1.0), V) domains_func = dolfin.Function(dolfin.FunctionSpace(config.domain.mesh, "DG", 0)) domains_func.vector().set_local(domains.array().astype(numpy.float)) def boundary(x): eps_x = config.params["turbine_x"] eps_y = config.params["turbine_y"] min_val = 1 for e_x, e_y in [(-eps_x, 0), (eps_x, 0), (0, -eps_y), (0, eps_y)]: try: min_val = min(min_val, domains_func((x[0] + e_x, x[1] + e_y))) except RuntimeError: pass return min_val == 1.0 bc = dolfin.DirichletBC(V, 0.0, boundary) # Solve the diffusion problem with a constant source term log(INFO, "Solving diffusion problem to identify feasible area ...") a = dolfin.inner(dolfin.grad(d), dolfin.grad(v)) * dolfin.dx L = dolfin.inner(s, v) * dolfin.dx dolfin.solve(a == L, sol, bc) return sol
def load_checkpoint(self, filename): try: self.memo = cPickle.load(open(filename, "rb")) except IOError: log(WARNING, "Warning: Checkpoint file '%s' not found." % filename) except ValueError: log(WARNING, "Error: Checkpoint file '%s' is invalid." % filename)
def function(self, m): """Return an object which must be >0 for the point to be feasible. :param m: The serialized paramaterisation of the turbines. :tpye m: numpy.ndarray. :returns: numpy.ndarray -- each entry must be zero for a poinst to be feasible. """ dolfin.log(dolfin.PROGRESS, "Calculating minimum distance constraints.") if self._controls.position and self._controls.friction: friction_length = len(m)/3 m = m[friction_length:] value = 0 for i in range(len(m)/2): for j in range(len(m)/2): if i <= j: continue dist_sq = self._sl2norm([m[2*i]-m[2*j], m[2*i+1]-m[2*j+1]]) value += self._penalty(dist_sq) if value <= 0: dolfin.log(dolfin.WARNING, "Minimum distance inequality constraints (should all " "be => 0): %s" % value) return numpy.array([value])
def __init__(self, config, feasible_area, attraction_center): ''' Generates the inequality constraints to enforce the turbines in the feasible area. If the turbine is outside the domain, the constraints is equal to the distance between the turbine and the attraction center. ''' self.config = config self.feasible_area = feasible_area # Compute the gradient of the feasible area fs = dolfin.FunctionSpace(feasible_area.function_space().mesh(), "DG", feasible_area.function_space().ufl_element().degree() - 1) feasible_area_grad = (dolfin.Function(fs), dolfin.Function(fs)) t = dolfin.TestFunction(fs) log(INFO, "Solving for gradient of feasible area") for i in range(2): form = dolfin.inner(feasible_area_grad[i], t) * dolfin.dx - dolfin.inner(feasible_area.dx(i), t) * dolfin.dx if dolfin.NonlinearVariationalSolver.default_parameters().has_parameter("linear_solver"): dolfin.solve(form == 0, feasible_area_grad[i], solver_parameters={"linear_solver": "cg", "preconditioner": "amg"}) else: dolfin.solve(form == 0, feasible_area_grad[i], solver_parameters={"newton_solver": {"linear_solver": "cg", "preconditioner": "amg"}}) self.feasible_area_grad = feasible_area_grad self.attraction_center = attraction_center
def test_optimisation_recovers_optimal_position(self): problem = self.default_problem() farm = problem.parameters.tidal_farm solver = DummySolver(problem) functional = PowerFunctional(problem) control = TurbineFarmControl(farm) rf_params = ReducedFunctionalParameters() rf_params.automatic_scaling = 5. rf = ReducedFunctional(functional, control, solver, rf_params) m0 = farm.control_array p = numpy.random.rand(len(m0)) minconv = helpers.test_gradient_array(rf.__call__, rf.derivative, m0, seed=0.005, perturbation_direction=p) assert minconv > 1.9 bounds = [[Constant(0), Constant(0)], [Constant(3000), Constant(1000)]] maximize(rf, bounds=bounds, method="SLSQP") m = farm._parameters["position"][0] log(INFO, "Solution of the primal variables: m=" + repr(m) + "\n") assert abs(m[0]-1500) < 40 assert abs(m[1]-500) < 0.4
def function(self, m): ieqcons = [] if len(self.config.params['controls']) == 2: # If the controls consists of the the friction and the positions, then we need to first extract the position part assert (len(m) % 3 == 0) m_pos = m[len(m) / 3:] else: m_pos = m for i in range(len(m_pos) / 2): x = m_pos[2 * i] y = m_pos[2 * i + 1] try: ieqcons.append(function_eval(self.feasible_area, (x, y))) except RuntimeError: print "Warning: a turbine is outside the domain" ieqcons.append((x - self.attraction_center[0])**2 + (y - self.attraction_center[1])** 2) # Point is outside domain arr = -numpy.array(ieqcons) if any(arr <= 0): log( INFO, "Domain restriction inequality constraints (should be >= 0): %s" % arr) return arr
def test_optimisation_recovers_optimal_position(self): problem = self.default_problem() farm = problem.parameters.tidal_farm solver = DummySolver(problem) functional = PowerFunctional(problem) control = TurbineFarmControl(farm) rf_params = ReducedFunctionalParameters() rf_params.automatic_scaling = 5. rf = ReducedFunctional(functional, control, solver, rf_params) m0 = farm.control_array p = numpy.random.rand(len(m0)) minconv = helpers.test_gradient_array(rf.__call__, rf.derivative, m0, seed=0.005, perturbation_direction=p) assert minconv > 1.9 bounds = [[Constant(0), Constant(0)], [Constant(3000), Constant(1000)]] maximize(rf, bounds=bounds, method="SLSQP") m = farm._parameters["position"][0] log(INFO, "Solution of the primal variables: m=" + repr(m) + "\n") assert abs(m[0] - 1500) < 40 assert abs(m[1] - 500) < 0.4
def __call__(self, *args, **kwds): h = self.get_key(args, kwds) if h not in self.memo: self.memo[h] = self.fn(*args, **kwds) else: log(INFO, "Use checkpoint value.") return self.memo[h]
def derivative(self, forget=False, **kwargs): """ Computes the first derivative of the functional with respect to its controls by solving the adjoint equations. """ log(INFO, 'Start evaluation of dj') timer = Timer("dj evaluation") self.functional = self.time_integrator.dolfin_adjoint_functional(self.solver.state) dj = compute_gradient(self.functional, self.controls, forget=forget, **kwargs) parameters["adjoint"]["stop_annotating"] = False log(INFO, "Runtime: " + str(timer.stop()) + " s") return enlisting.enlist(dj)
def test_temporal_convergence_is_two(self, sw_linear_problem_parameters, sin_ic): errors = [] tests = 4 for refinement_level in range(tests): error = self.compute_temporal_error(sw_linear_problem_parameters, refinement_level, sin_ic) errors.append(error) # Compute the order of convergence conv = [] for i in range(len(errors) - 1): conv.append(-math.log(errors[i + 1] / errors[i], 2)) log(INFO, "Temporal Taylor remainders: %s" % errors) log(INFO, "Temporal order of convergence (expecting 2.0): %s" % conv) assert min(conv) > 1.8
def read(self, filename="muflon-parameters.xml"): """ Read parameters from XML file. :param filename: name of the input XML file (can be relative or absolute path) :type filename: str """ filename += ".xml" if filename[-4:] != ".xml" else "" if os.path.isfile(filename) and os.access(filename, os.R_OK): log(DEBUG, "Reading default values from '%s'" % filename) with File(filename) as xmlfile: xmlfile >> self else: log(DEBUG, "File '%s' is missing or not readable." % filename)
def test_spatial_convergence_is_two(self, sw_linear_problem_parameters, sin_ic): errors = [] tests = 4 for refinement_level in range(tests): errors.append(self.compute_spatial_error(sw_linear_problem_parameters, refinement_level, sin_ic)) # Compute the order of convergence conv = [] for i in range(len(errors)-1): conv.append(-math.log(errors[i+1]/errors[i], 2)) log(INFO, "Errors: %s" % errors) log(INFO, "Spatial order of convergence (expecting 2.0): %s" % conv) assert min(conv) > 1.8
def test_spatial_convergence_is_first_order(self, sw_nonlinear_problem_parameters): errors = [] tests = 4 for refinement_level in range(tests): error = self.compute_spatial_error(sw_nonlinear_problem_parameters, refinement_level) errors.append(error) # Compute the order of convergence conv = [] for i in range(len(errors)-1): conv.append(-math.log(errors[i+1] / errors[i], 2)) log(INFO, "Spatial Taylor remainders are : %s" % str(errors)) log(INFO, "Spatial order of convergence (expecting 1.0): %s" % str(conv)) assert min(conv) > 1.0
def matvec(self, b): from time import time from block.block_vec import block_vec from dolfin import log, info, Progress TRACE = 13 # dolfin.TRACE T = time() # If x and initial_guess are block_vecs, some of the blocks may be # scalars (although they are normally converted to vectors by bc # application). To be sure, call allocate() on them. if isinstance(b, block_vec): # Create a shallow copy to call allocate() on, to avoid changing the caller's copy of b b = block_vec(len(b), b.blocks) b.allocate(self.A, dim=0) if self.initial_guess: # Most (all?) solvers modify x, so make a copy to avoid changing # the caller's copy of x from block.block_util import copy x = copy(self.initial_guess) if isinstance(x, block_vec): x.allocate(self.A, dim=1) else: x = self.A.create_vec(dim=1) x.zero() try: log(TRACE, self.__class__.__name__+' solve of '+str(self.A)) if self.B != 1.0: log(TRACE, 'Using preconditioner: '+str(self.B)) progress = Progress(self.name, self.maxiter) if self.tolerance < 0: tolerance = -self.tolerance relative = True else: tolerance = self.tolerance relative = False x = self.method(self.B, self.AR, x, b, tolerance=tolerance, relativeconv=self.relativeconv, maxiter=self.maxiter, progress=progress, callback=self.callback, **self.kwargs) del progress # trigger final printout except Exception, e: from dolfin import warning warning("Error solving " + self.name) raise
def test_temporal_convergence_is_two(self, sw_linear_problem_parameters): errors = [] tests = 4 for refinement_level in range(tests): error = self.compute_temporal_error(sw_linear_problem_parameters, refinement_level) errors.append(error) # Compute the order of convergence conv = [] for i in range(len(errors) - 1): conv.append(-math.log(errors[i + 1] / errors[i], 2)) log(INFO, "Temporal Taylor remainders are : %s" % str(errors)) log(INFO, "Temporal order of convergence (expecting 2.0): %s" % str(conv)) assert min(conv) > 1.8
def jacobian(self, m): """Returns the gradient of the constraint function. Return a list of vector-like objects representing the gradient of the constraint function with respect to the parameter m. :param m: The serialized paramaterisation of the turbines. :tpye m: numpy.ndarray. :returns: numpy.ndarray -- the gradient of the constraint function with respect to each input parameter m. """ dolfin.log( dolfin.PROGRESS, "Calculating the jacobian of minimum " "distance constraints function.") inequality_constraints = [] if self._controls.position and self._controls.friction: friction_length = len(m) / 3 m = m[friction_length:] else: friction_length = 0 p_ineq_c = numpy.zeros(friction_length + len(m)) for i in range(len(m) / 2): for j in range(len(m) / 2): if i <= j: continue dist_sq = self._sl2norm( [m[2 * i] - m[2 * j], m[2 * i + 1] - m[2 * j + 1]]) dvalue = self._dpenalty(dist_sq) # The control vector contains the friction coefficients first, # so we need to shift here p_ineq_c[friction_length + 2 * i] += dvalue * 2 * (m[2 * i] - m[2 * j]) p_ineq_c[friction_length + 2 * j] += dvalue * (-2 * (m[2 * i] - m[2 * j])) p_ineq_c[friction_length + 2 * i + 1] += dvalue * 2 * (m[2 * i + 1] - m[2 * j + 1]) p_ineq_c[friction_length + 2 * j + 1] += dvalue * (-2 * (m[2 * i + 1] - m[2 * j + 1])) return numpy.array([p_ineq_c])
def jacobian(self, m): """Returns the gradient of the constraint function. Return a list of vector-like objects representing the gradient of the constraint function with respect to the parameter m. :param m: The serialized paramaterisation of the turbines. :tpye m: numpy.ndarray. :returns: numpy.ndarray -- the gradient of the constraint function with respect to each input parameter m. """ dolfin.log( dolfin.PROGRESS, "Calculating the jacobian of minimum " "distance constraints function.") inequality_constraints = [] for i in range(len(m) / 2): for j in range(len(m) / 2): if i <= j: continue # Need to add space for zeros for the friction if self._controls.position and self._controls.friction: prime_inequality_constraints = numpy.zeros(len(m) * 3 / 2) friction_length = len(m) / 2 else: prime_inequality_constraints = numpy.zeros(len(m)) friction_length = 0 # Provide a shorter handle p_ineq_c = prime_inequality_constraints # The control vector contains the friction coefficients first, # so we need to shift here p_ineq_c[friction_length + 2 * i] = 2 * (m[2 * i] - m[2 * j]) p_ineq_c[friction_length + 2 * j] = -2 * (m[2 * i] - m[2 * j]) p_ineq_c[friction_length + 2 * i + 1] = 2 * (m[2 * i + 1] - m[2 * j + 1]) p_ineq_c[friction_length + 2 * j + 1] = -2 * (m[2 * i + 1] - m[2 * j + 1]) inequality_constraints.append(p_ineq_c) return numpy.array(inequality_constraints)
def function(self, m): ieqcons = [] controlled_by = self.farm.turbine_specification.controls if (controlled_by.position and (controlled_by.friction or controlled_by.dynamic_friction)): # If the controls consists of the the friction and the positions, then we need to first extract the position part assert(len(m) % 3 == 0) m_pos = m[len(m) / 3:] else: m_pos = m for i in range(len(m_pos) / 2): pos = numpy.array([m_pos[2 * i], m_pos[2 * i + 1]]) c = self.b - numpy.dot(self.A, pos) ieqcons = ieqcons + list(c) arr = numpy.array(ieqcons) if any(arr < 0): log(INFO, "Convex site position constraints (should be >= 0): %s" % arr) return arr
def derivative(self, forget=False, **kwargs): """ Computes the first derivative of the functional with respect to its controls by solving the adjoint equations. """ log(INFO, 'Start evaluation of dj') timer = Timer("dj evaluation") if not hasattr(self, "time_integrator"): self.evaluate() self.functional = self.time_integrator.dolfin_adjoint_functional( self.solver.state) dj = compute_gradient(self.functional, self.controls, forget=forget, **kwargs) parameters["adjoint"]["stop_annotating"] = False log(INFO, "Runtime: " + str(timer.stop()) + " s") return enlisting.enlist(dj)
def jacobian(self, m): """Returns the gradient of the constraint function. Return a list of vector-like objects representing the gradient of the constraint function with respect to the parameter m. :param m: The serialized paramaterisation of the turbines. :tpye m: numpy.ndarray. :returns: numpy.ndarray -- the gradient of the constraint function with respect to each input parameter m. """ dolfin.log(dolfin.PROGRESS, "Calculating the jacobian of minimum " "distance constraints function.") inequality_constraints = [] if self._controls.position and self._controls.friction: friction_length = len(m)/3 m = m[friction_length:] else: friction_length = 0 p_ineq_c = numpy.zeros(friction_length + len(m)) for i in range(len(m)/2): for j in range(len(m)/2): if i <= j: continue dist_sq = self._sl2norm([m[2*i]-m[2*j], m[2*i+1]-m[2*j+1]]) dvalue = self._dpenalty(dist_sq) # The control vector contains the friction coefficients first, # so we need to shift here p_ineq_c[friction_length+2*i] += dvalue * 2*(m[2*i] - m[2*j]) p_ineq_c[friction_length+2*j] += dvalue * (-2*(m[2*i] - m[2*j])) p_ineq_c[friction_length+2*i+1] += dvalue * 2*(m[2*i+1] - m[2*j+1]) p_ineq_c[friction_length+2*j+1] += dvalue * (-2*(m[2*i+1] - m[2*j+1])) return numpy.array([p_ineq_c])
def jacobian(self, m): """Returns the gradient of the constraint function. Return a list of vector-like objects representing the gradient of the constraint function with respect to the parameter m. :param m: The serialized paramaterisation of the turbines. :tpye m: numpy.ndarray. :returns: numpy.ndarray -- the gradient of the constraint function with respect to each input parameter m. """ dolfin.log(dolfin.PROGRESS, "Calculating the jacobian of minimum " "distance constraints function.") inequality_constraints = [] for i in range(len(m)/2): for j in range(len(m)/2): if i <= j: continue # Need to add space for zeros for the friction if self._controls.position and self._controls.friction: prime_inequality_constraints = numpy.zeros(len(m)*3/2) friction_length = len(m)/2 else: prime_inequality_constraints = numpy.zeros(len(m)) friction_length = 0 # Provide a shorter handle p_ineq_c = prime_inequality_constraints # The control vector contains the friction coefficients first, # so we need to shift here p_ineq_c[friction_length+2*i] = 2*(m[2*i] - m[2*j]) p_ineq_c[friction_length+2*j] = -2*(m[2*i] - m[2*j]) p_ineq_c[friction_length+2*i+1] = 2*(m[2*i+1] - m[2*j+1]) p_ineq_c[friction_length+2*j+1] = -2*(m[2*i+1] - m[2*j+1]) inequality_constraints.append(p_ineq_c) return numpy.array(inequality_constraints)
def test_site_constraint(self): farm = self.get_farm() ieq = ConvexPolygonSiteConstraint(farm, [[0, 0], [10, 0], [10, 10]]) # Only test the correctness of the first inequality constraint for # simplicity. ieqcons_J = lambda m: ieq.function(m)[0] ieqcons_dJ = lambda m, forget=False: ieq.jacobian(m)[0] # This will raise a RunetimeWarning, which is fine because we expect # division by zero x = numpy.array([0, 0, 10, 4, 20, 8]) minconv = helpers.test_gradient_array(ieqcons_J, ieqcons_dJ, x=x, seed=0.1) # These constraints are linear so we expect no convergence at all. # Let's check that the tolerance is not above a threshold log(INFO, "Expecting a Nan convergence order") assert math.isnan(minconv)
def write(self, comm, filename="muflon-parameters.xml"): """ Write parameters to XML file. :param comm: MPI communicator :type comm: :py:class:`dolfin.MPI_Comm` :param filename: name of the output XML file (can be relative or absolute path) :type filename: str """ filename += ".xml" if filename[-4:] != ".xml" else "" log(DEBUG, "Writing current parameter values into '%s'." % filename) from dolfin import DOLFIN_VERSION_MAJOR, DOLFIN_VERSION_MINOR if DOLFIN_VERSION_MAJOR >= 2017 and DOLFIN_VERSION_MINOR >= 2: with File(filename) as xmlfile: xmlfile << self elif MPI.rank(comm) == 0: # FIXME: remove this backporting with File(filename) as xmlfile: xmlfile << self else: return
def evaluate(self, annotate=True): """ Computes the functional value by running the forward model. """ log(INFO, 'Start evaluation of j') timer = Timer("j evaluation") farm = self.solver.problem.parameters.tidal_farm # Configure dolfin-adjoint adj_reset() parameters["adjoint"]["record_all"] = True # Solve the shallow water system and integrate the functional of # interest. final_only = (not self.solver.problem._is_transient or self._problem_params.functional_final_time_only) self.time_integrator = TimeIntegrator(self.solver.problem, self._functional, final_only) for sol in self.solver.solve(annotate=annotate): self.time_integrator.add(sol["time"], sol["state"], sol["tf"], sol["is_final"]) j = self.time_integrator.integrate() timer.stop() log(INFO, 'Runtime: %f s.' % timer.elapsed()[0]) log(INFO, 'j = %e.' % float(j)) return j
def function(self, m): ieqcons = [] if len(self.config.params['controls']) == 2: # If the controls consists of the the friction and the positions, then we need to first extract the position part assert(len(m) % 3 == 0) m_pos = m[len(m) / 3:] else: m_pos = m for i in range(len(m_pos) / 2): x = m_pos[2 * i] y = m_pos[2 * i + 1] try: ieqcons.append(function_eval(self.feasible_area, (x, y))) except RuntimeError: print("Warning: a turbine is outside the domain") ieqcons.append((x - self.attraction_center[0]) ** 2 + (y - self.attraction_center[1]) ** 2) # Point is outside domain arr = -numpy.array(ieqcons) if any(arr <= 0): log(INFO, "Domain restriction inequality constraints (should be >= 0): %s" % arr) return arr
def cbc_log(level, msg): "Log on master process." if on_master_process(): log(level, msg)