diff --git a/Snakefile b/Snakefile index 2d1b95adfefafbc5cf442a7f75c2ef2145de5a04..d66ead605992637c30dad60575c7fac8e54508ac 100644 --- a/Snakefile +++ b/Snakefile @@ -21,9 +21,18 @@ TODO: Discuss name for quadrature mesh (now: grid) TODO: Contemplate using lambdify for basis TODO: Contemplate allowing vector input for ICs TODO: Discuss how wavelet details should be plotted +TODO: Ask whether we are dealing with inviscid Burgers only +TODO: Ask whether it is helpful to enforce boundary at every SSPRK3 step +TODO: Ask why we limit the initial time step +TODO: ASk whether cfl number should be absolute value Urgent: -TODO: Add Burger class +TODO: Add Burgers class completely +TODO: Add 'update_time_step()' to Burgers -> Done +TODO: Add 'solve_exactly()' to Burgers +TODO: Add 'update_right_hand_side()' to Burgers +TODO: Add initialization to Burgers +TODO: Add Dirichlet boundary TODO: Use cfl_number for updating, not just time (equation-related?) Critical, but not urgent: diff --git a/scripts/tcd/Equation.py b/scripts/tcd/Equation.py index e2765d25e7f1831dd68588c53f4fa2c3978adb80..d0bd5ceb8ad76ebc9dc6b07a44ceb21398aca9ab 100644 --- a/scripts/tcd/Equation.py +++ b/scripts/tcd/Equation.py @@ -131,12 +131,14 @@ class Equation(ABC): } @abstractmethod - def update_time_step(self, current_time: float, + def update_time_step(self, projection: ndarray, current_time: float, time_step: float) -> Tuple[float, float]: """Update time step. Parameters ---------- + projection : ndarray + Current projection during approximation. current_time : float Current time during approximation. time_step : float @@ -242,12 +244,14 @@ class LinearAdvection(Equation): mesh=self._mesh, basis=self._basis, quadrature=self._quadrature) - def update_time_step(self, current_time: float, + def update_time_step(self, projection: ndarray, current_time: float, time_step: float) -> Tuple[float, float]: """Update time step. Parameters ---------- + projection : ndarray + Current projection during approximation. current_time : float Current time during approximation. time_step : float @@ -261,9 +265,12 @@ class LinearAdvection(Equation): """ cfl_number = self._cfl_number + + # Adjust for final time-step if current_time+time_step > self.final_time: time_step = self.final_time-current_time cfl_number = self.wave_speed * time_step / self._mesh.cell_len + return cfl_number, time_step def solve_exactly(self, mesh: Mesh) -> Tuple[ndarray, ndarray]: @@ -341,3 +348,171 @@ class LinearAdvection(Equation): -self._mesh.num_ghost_cells]) return right_hand_side + + +class Burgers(Equation): + """Class for Burgers' equation. + + Attributes + ---------- + volume_integral_matrix : ndarray + Volume integral matrix. + flux_matrix : ndarray + Flux matrix. + + Methods + ------- + update_right_hand_side(projection) + Update right-hand side. + + Notes + ----- + .. math:: u_t + u*u_x = 0 + + """ + + def _reset(self) -> None: + """Reset instance variables.""" + pass + # matrix_shape = (self._basis.polynomial_degree+1, + # self._basis.polynomial_degree+1) + # root_vector = np.array([np.sqrt(degree+0.5) for degree in range( + # self._basis.polynomial_degree+1)]) + # degree_matrix = np.matmul(root_vector[:, np.newaxis], + # root_vector[:, np.newaxis].T) + # + # # Set volume integral matrix + # matrix = np.ones(matrix_shape) + # if self._wave_speed > 0: + # matrix[np.fromfunction( + # lambda i, j: (j >= i) | ((i+j) % 2 == 0), matrix_shape)] = -1.0 + # else: + # matrix[np.fromfunction( + # lambda i, j: (j > i) & ((i+j) % 2 == 1), matrix_shape)] = -1.0 + # self._volume_integral_matrix = matrix * degree_matrix + # + # # Set flux matrix + # matrix = np.fromfunction(lambda i, j: (-1.0)**i, matrix_shape) \ + # if self._wave_speed > 0 \ + # else np.fromfunction(lambda i, j: (-1.0)**j, matrix_shape) + # self._flux_matrix = matrix * degree_matrix + + @enforce_boundary() + def _initialize_projection(self) -> ndarray: + """Initialize projection.""" + return do_initial_projection(init_cond=self._init_cond, + mesh=self._mesh, basis=self._basis, + quadrature=self._quadrature) + + def update_time_step(self, projection: ndarray, current_time: float, + time_step: float) -> Tuple[float, float]: + """Update time step. + + Adapt CFL number to ensure right-hand side is multiplied with dt/dx. + + Parameters + ---------- + projection : ndarray + Current projection during approximation. + current_time : float + Current time during approximation. + time_step : float + Length of time step during approximation. + Returns + ------- + cfl_number : float + Updated CFL number to ensure stability. + time_step : float + Updated time step for approximation. + + """ + cfl_number = self._cfl_number + + max_velocity = max(abs(projection[0, :] * np.sqrt(0.5))) + time_step = cfl_number * self._mesh.cell_len / max_velocity + cfl_number = time_step / self._mesh.cell_len + + # Adjust for final time-step + if current_time+time_step > self._final_time: + time_step = self._final_time-current_time + cfl_number = time_step / self._mesh.cell_len + + return cfl_number, time_step + + def solve_exactly(self, mesh: Mesh) -> Tuple[ndarray, ndarray]: + """Calculate exact solution. + + Parameters + ---------- + mesh : Mesh + Mesh for evaluation. + + Returns + ------- + grid : ndarray + Array containing evaluation grid for a function. + exact : ndarray + Array containing exact evaluation of a function. + + """ + pass + # num_periods = np.floor(self.wave_speed * self.final_time / + # mesh.interval_len) + # + # grid = np.repeat(mesh.non_ghost_cells, self._quadrature.num_nodes) + \ + # mesh.cell_len / 2 * np.tile(self._quadrature.nodes, mesh.num_cells) + # + # # Project points into correct periodic interval + # points = np.array([point-self.wave_speed * + # self.final_time+num_periods * mesh.interval_len + # for point in grid]) + # left_bound, right_bound = mesh.bounds + # while np.any(points < left_bound): + # points[points < left_bound] += mesh.interval_len + # while np.any(points) > right_bound: + # points[points > right_bound] -= mesh.interval_len + # + # exact = np.array([self._init_cond.calculate(mesh=mesh, x=point) for + # point in points]) + # + # grid = np.reshape(grid, (1, grid.size)) + # exact = np.reshape(exact, (1, exact.size)) + # + # return grid, exact + + @enforce_boundary() + def update_right_hand_side(self, projection: ndarray) -> ndarray: + """Update right-hand side. + + Parameter + --------- + projection : ndarray + Matrix of projection for each polynomial degree. + + Returns + ------- + ndarray + Matrix of right-hand side. + + """ + pass + # right_hand_side = np.zeros_like(projection) + # if self._wave_speed > 0: + # right_hand_side[:, self._mesh.num_ghost_cells: + # -self._mesh.num_ghost_cells] = \ + # 2 * (self._flux_matrix @ + # projection[:, self._mesh.num_ghost_cells-1: + # -self._mesh.num_ghost_cells-1] + + # self._volume_integral_matrix @ + # projection[:, self._mesh.num_ghost_cells: + # -self._mesh.num_ghost_cells]) + # else: + # right_hand_side[:, self._mesh.num_ghost_cells: + # -self._mesh.num_ghost_cells] = \ + # 2 * (self._flux_matrix @ + # projection[:, self._mesh.num_ghost_cells+1:] + + # self._volume_integral_matrix @ + # projection[:, self._mesh.num_ghost_cells: + # -self._mesh.num_ghost_cells]) + # + # return right_hand_side