On Wed, Oct 12, 2016 at 9:07 PM, David Knezevic <david.kneze...@akselos.com>
wrote:
> On Wed, Oct 12, 2016 at 5:04 PM, Roy Stogner <royst...@ices.utexas.edu>
> wrote:
>
>>
>> On Sun, 9 Oct 2016, David Knezevic wrote:
>>
>> I've attached a modified version of misc_ex9 that attaches
>>> constraints on one side of the "crack" and checks if the dof
>>> constraints are present during assembly. This passes in serial but
>>> fails in parallel because constraints are not communicated on the
>>> GhostingFunctor-coupled-dofs.
>>>
>>
>> I had to make a couple fixes to the test: switching mesh_1 and mesh_2
>> to SerialMesh, and using
>>
>> dof_id_type neighbor_node_id = neighbor->node_ref(neighbor_no
>> de_index).dof_number(0,0,0);
>>
>> to handle the case where node id isn't node dof id.
>>
>> The extra constraints I added mean that the problem doesn't make
>>> physical sense anymore unfortunately, but at least it tests for the
>>> constraint issue. Roy: I'm not sure if this is appropriate for a
>>> unit test, but it should at least be helpful for when you want to
>>> check your implementation.
>>>
>>
>> It was, thanks! Here's hoping #1120 fixes the real problem too.
>>
>> The modified ex9 is too big for a unit test, and too contrived for an
>> example, and I can't think of any easy way to fix that while
>> maintaining the same level of test coverage. But if you can come up
>> with anything that doesn't have both those problems I'd really love to
>> get this case into continuous integration.
>>
>> If you can't come up with anything either... I suppose I could combine
>> an extra-ghost-layers test case with a Dirichlet boundary and test a
>> wide stencil? That should hit the same code paths. Plus, it's about
>> time libMesh expanded into the cutting edge world of finite difference
>> methods.
>>
>
>
> I just tried my real problem with your PR and it's still not working,
> unfortunately. I'll have to look into that in more detail. I'll get back to
> you when I have more info.
>
Roy, I've attached an updated version of the misc_ex9 test. The test now
prints out has a Dirichlet boundary on one side of the domain (boundary ids
1 and 11), and it prints out the dof IDs on the "crack" that have
constraints on them. With this I get the following output:
1 MPI process:
*./example-opt*
*constrained upper dofs: 1025 1026 *
*constrained lower dofs: 0 1 *
*constrained upper dofs: 1026 1029 *
*constrained lower dofs: 1 8 *
*constrained upper dofs: 1029 1031 *
*constrained lower dofs: 8 12 *
*constrained upper dofs: 1031 1033 *
*constrained lower dofs: 12 16 *
2 MPI processes:
*mpirun -np 2 ./example-opt --keep-cout*
*constrained upper dofs: 500 501 502 503 *
*constrained lower dofs: 525 526 *
*constrained upper dofs: 501 504 505 502 *
*constrained lower dofs: 526 533 *
*constrained upper dofs: 504 506 507 505 *
*constrained lower dofs: 533 537 *
*constrained upper dofs: 506 508 509 507 *
*constrained lower dofs: 537 541 *
*constrained upper dofs: 503 502 510 511 *
*constrained lower dofs: *
*constrained upper dofs: 502 505 512 510 *
*constrained lower dofs: *
*constrained upper dofs: 505 507 513 512 *
*constrained lower dofs: *
*constrained upper dofs: 507 509 514 513 *
*constrained lower dofs: *
*constrained upper dofs: 511 510 515 516 *
*constrained lower dofs: *
*constrained upper dofs: 510 512 517 515 *
*constrained lower dofs: *
*constrained upper dofs: 512 513 518 517 *
*constrained lower dofs: *
*constrained upper dofs: 513 514 519 518 *
*constrained lower dofs: *
*constrained upper dofs: 516 515 520 521 *
*constrained lower dofs: *
*constrained upper dofs: 515 517 522 520 *
*constrained lower dofs: *
*constrained upper dofs: 517 518 523 522 *
*constrained lower dofs: *
*constrained upper dofs: 518 519 524 523 *
*constrained lower dofs:*
The 1 process output makes sense to me: there should be only five nodes on
top and bottom that have a constraint. I don't understand the 2 process
output, there seems to be many extra constraints. Do you think this
indicates a bug in the constraint scattering?
David
// The libMesh Finite Element Library.
// Copyright (C) 2002-2016 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// <h1>Miscellaneous Example 9 - Implement an interface term to model
// a thermal "film resistance"</h1>
// \author David Knezevic
// \date 2013
//
// In this example we solve a Poisson problem, -\Laplacian u = f, with
// a non-standard interface condition on the domain interior which
// models a thermal "film resistance". The interface condition
// requires continuity of flux, and a jump in temperature proportional
// to the flux:
// \nabla u_1 \cdot n = \nabla u_2 \cdot n,
// u_1 - u_2 = R * \nabla u \cdot n
//
// To implement this PDE, we use two mesh subdomains, \Omega_1 and
// \Omega_2, with coincident boundaries, but which are not connected
// in the FE sense. Let \Gamma denote the coincident boundary. The
// term on \Gamma takes the form:
//
// 1/R * \int_\Gamma (u_1 - u_2) (v_1 - v_2) ds,
//
// where u_1, u_2 (resp. v_1, v_2) are the trial (resp. test)
// functions on either side of \Gamma. We implement this condition
// using C0 basis functions, but the "crack" in the mesh at \Gamma
// permits a discontinuity in the solution. We also impose a heat flux
// on the bottom surface of the mesh, and a zero Dirichlet condition
// on the top surface.
//
// In order to implement the interface condition, we need to augment
// the matrix sparsity pattern, which is handled by the class
// AugmentSparsityPatternOnInterface.
// C++ include files that we need
#include <iostream>
#include <limits>
// libMesh includes
#include "libmesh/libmesh.h"
#include "libmesh/mesh.h"
#include "libmesh/mesh_generation.h"
#include "libmesh/mesh_modification.h"
#include "libmesh/mesh_refinement.h"
#include "libmesh/exodusII_io.h"
#include "libmesh/equation_systems.h"
#include "libmesh/fe.h"
#include "libmesh/quadrature_gauss.h"
#include "libmesh/dof_map.h"
#include "libmesh/sparse_matrix.h"
#include "libmesh/numeric_vector.h"
#include "libmesh/dense_matrix.h"
#include "libmesh/dense_vector.h"
#include "libmesh/getpot.h"
#include "libmesh/elem.h"
#include "libmesh/fe_interface.h"
#include "libmesh/boundary_info.h"
#include "libmesh/linear_implicit_system.h"
#include "libmesh/zero_function.h"
#include "libmesh/dirichlet_boundaries.h"
// local includes
#include "augment_sparsity_on_interface.h"
// define the boundary IDs in the mesh
#define MIN_Z_BOUNDARY 15
#define MAX_Z_BOUNDARY 0
#define CRACK_BOUNDARY_LOWER 10
#define CRACK_BOUNDARY_UPPER 5
// Bring in everything from the libMesh namespace
using namespace libMesh;
/**
* Assemble the system matrix and rhs vector.
*/
void assemble_poisson(EquationSystems & es,
const ElementSideMap & lower_to_upper);
// The main program.
int main (int argc, char ** argv)
{
// Initialize libMesh.
LibMeshInit init (argc, argv);
// This example uses an ExodusII input file
#ifndef LIBMESH_HAVE_EXODUS_API
libmesh_example_requires(false, "--enable-exodus");
#endif
// The sparsity augmentation code requires PETSc
libmesh_example_requires(libMesh::default_solver_package() == PETSC_SOLVERS, "--enable-petsc");
// Skip this 3D example if libMesh was compiled as 1D or 2D-only.
libmesh_example_requires(3 <= LIBMESH_DIM, "3D support");
GetPot command_line (argc, argv);
Real R = 2.;
if (command_line.search(1, "-R"))
R = command_line.next(R);
Mesh mesh(init.comm());
EquationSystems equation_systems (mesh);
LinearImplicitSystem & system =
equation_systems.add_system<LinearImplicitSystem> ("Poisson");
system.add_variable("u", FIRST, LAGRANGE);
// We want to call assemble_poisson "manually" so that we can pass in
// lower_to_upper, hence set assemble_before_solve = false
system.assemble_before_solve = false;
// Impose zero Dirichlet boundary condition
std::set<boundary_id_type> boundary_ids;
boundary_ids.insert(MAX_Z_BOUNDARY);
boundary_ids.insert(1); // Add constraints on one side
boundary_ids.insert(11); // Add constraints on one side
std::vector<unsigned int> variables;
variables.push_back(0);
ZeroFunction<> zf;
DirichletBoundary dirichlet_bc(boundary_ids,
variables,
&zf);
system.get_dof_map().add_dirichlet_boundary(dirichlet_bc);
// Attach an object to the DofMap that will augment the sparsity pattern
// due to the degrees-of-freedom on the "crack"
//
// By attaching this object *before* reading our mesh, we also
// ensure that the connected elements will not be deleted on a
// distributed mesh.
AugmentSparsityOnInterface augment_sparsity
(mesh, CRACK_BOUNDARY_LOWER, CRACK_BOUNDARY_UPPER);
system.get_dof_map().add_coupling_functor(augment_sparsity);
// This example code has not been written to cope with a distributed mesh
{
SerialMesh mesh_1 (init.comm(), 3);
MeshTools::Generation::build_cube (mesh_1,
4,
4,
20,
0., 4.,
0., 4.,
0., 20.,
HEX8);
SerialMesh mesh_2 (init.comm(), 3);
MeshTools::Generation::build_cube (mesh_2,
4,
4,
20,
0., 4.,
0., 4.,
20., 40.,
HEX8);
for(boundary_id_type bc_id = 0; bc_id < 6; bc_id++)
{
// Convert the BC IDs in one of the compnents to prevent "overlap" of
// the boundary IDs.
MeshTools::Modification::change_boundary_id(mesh_2,
bc_id,
bc_id+10);
}
// Call stitch_meshes to merge the two meshes, but pass invalid_id
// so that we do not do any actuall stitching.
mesh_2.stitch_meshes(mesh_1, BoundaryInfo::invalid_id, BoundaryInfo::invalid_id);
mesh_2.write("two_beams.exo");
}
mesh.read("two_beams.exo");
equation_systems.init();
equation_systems.print_info();
// Set the jump term coefficient, it will be used in assemble_poisson
equation_systems.parameters.set<Real>("R") = R;
// Assemble and then solve
assemble_poisson(equation_systems,
augment_sparsity.get_lower_to_upper());
system.solve();
#ifdef LIBMESH_HAVE_EXODUS_API
// Plot the solution
ExodusII_IO (mesh).write_equation_systems ("solution.exo",
equation_systems);
#endif
#ifdef LIBMESH_ENABLE_AMR
// Possibly solve on a refined mesh next.
MeshRefinement mesh_refinement (mesh);
unsigned int n_refinements = 0;
if (command_line.search("-n_refinements"))
n_refinements = command_line.next(0);
for (unsigned int r = 0; r != n_refinements; ++r)
{
std::cout << "Refining the mesh" << std::endl;
mesh_refinement.uniformly_refine ();
equation_systems.reinit();
assemble_poisson(equation_systems,
augment_sparsity.get_lower_to_upper());
system.solve();
#ifdef LIBMESH_HAVE_EXODUS_API
// Plot the refined solution
std::ostringstream out;
out << "solution_" << r << ".exo";
ExodusII_IO (mesh).write_equation_systems (out.str(),
equation_systems);
#endif
}
#endif
return 0;
}
void assemble_poisson(EquationSystems & es,
const ElementSideMap & lower_to_upper)
{
const MeshBase & mesh = es.get_mesh();
const unsigned int dim = mesh.mesh_dimension();
Real R = es.parameters.get<Real>("R");
LinearImplicitSystem & system = es.get_system<LinearImplicitSystem>("Poisson");
const DofMap & dof_map = system.get_dof_map();
FEType fe_type = dof_map.variable_type(0);
UniquePtr<FEBase> fe (FEBase::build(dim, fe_type));
UniquePtr<FEBase> fe_elem_face (FEBase::build(dim, fe_type));
UniquePtr<FEBase> fe_neighbor_face (FEBase::build(dim, fe_type));
QGauss qrule (dim, fe_type.default_quadrature_order());
QGauss qface(dim-1, fe_type.default_quadrature_order());
fe->attach_quadrature_rule (&qrule);
fe_elem_face->attach_quadrature_rule (&qface);
fe_neighbor_face->attach_quadrature_rule (&qface);
const std::vector<Real> & JxW = fe->get_JxW();
const std::vector<std::vector<Real> > & phi = fe->get_phi();
const std::vector<std::vector<RealGradient> > & dphi = fe->get_dphi();
const std::vector<Real> & JxW_face = fe_elem_face->get_JxW();
const std::vector<Point> & qface_points = fe_elem_face->get_xyz();
const std::vector<std::vector<Real> > & phi_face = fe_elem_face->get_phi();
const std::vector<std::vector<Real> > & phi_neighbor_face = fe_neighbor_face->get_phi();
DenseMatrix<Number> Ke;
DenseVector<Number> Fe;
DenseMatrix<Number> Kne;
DenseMatrix<Number> Ken;
DenseMatrix<Number> Kee;
DenseMatrix<Number> Knn;
std::vector<dof_id_type> dof_indices;
MeshBase::const_element_iterator el = mesh.active_local_elements_begin();
const MeshBase::const_element_iterator end_el = mesh.active_local_elements_end();
for ( ; el != end_el; ++el)
{
const Elem * elem = *el;
dof_map.dof_indices (elem, dof_indices);
const unsigned int n_dofs = dof_indices.size();
fe->reinit (elem);
Ke.resize (n_dofs, n_dofs);
Fe.resize (n_dofs);
// Assemble element interior terms for the matrix
for (unsigned int qp=0; qp<qrule.n_points(); qp++)
for (unsigned int i=0; i<n_dofs; i++)
for (unsigned int j=0; j<n_dofs; j++)
Ke(i,j) += JxW[qp]*(dphi[i][qp]*dphi[j][qp]);
// Boundary flux provides forcing in this example
{
for (unsigned int side=0; side<elem->n_sides(); side++)
if (elem->neighbor_ptr(side) == libmesh_nullptr)
{
if (mesh.get_boundary_info().has_boundary_id (elem, side, MIN_Z_BOUNDARY))
{
fe_elem_face->reinit(elem, side);
for (unsigned int qp=0; qp<qface.n_points(); qp++)
for (unsigned int i=0; i<phi.size(); i++)
Fe(i) += JxW_face[qp] * phi_face[i][qp];
}
}
}
std::vector<dof_id_type> constrained_lower_dofs;
std::vector<dof_id_type> constrained_upper_dofs;
// Add boundary terms on the crack
{
for (unsigned int side=0; side<elem->n_sides(); side++)
if (elem->neighbor_ptr(side) == libmesh_nullptr)
{
// Found the lower side of the crack. Assemble terms due to lower and upper in here.
if (mesh.get_boundary_info().has_boundary_id (elem, side, CRACK_BOUNDARY_LOWER))
{
fe_elem_face->reinit(elem, side);
ElementSideMap::const_iterator ltu_it =
lower_to_upper.find(std::make_pair(elem, side));
libmesh_assert(ltu_it != lower_to_upper.end());
const Elem * neighbor = ltu_it->second;
std::vector<Point> qface_neighbor_points;
FEInterface::inverse_map (elem->dim(), fe->get_fe_type(),
neighbor, qface_points, qface_neighbor_points);
fe_neighbor_face->reinit(neighbor, &qface_neighbor_points);
std::vector<dof_id_type> neighbor_dof_indices;
dof_map.dof_indices (neighbor, neighbor_dof_indices);
const unsigned int n_neighbor_dofs = neighbor_dof_indices.size();
UniquePtr<Elem> lower_side = elem->build_side(side);
for(unsigned int i=0; i<lower_side->n_nodes(); i++)
{
Node& node_ref = lower_side->node_ref(i);
dof_id_type lower_dof_id = node_ref.dof_number(0,0,0);
if(dof_map.is_constrained_dof (lower_dof_id))
{
constrained_lower_dofs.push_back(lower_dof_id);
}
}
Kne.resize (n_neighbor_dofs, n_dofs);
Ken.resize (n_dofs, n_neighbor_dofs);
Kee.resize (n_dofs, n_dofs);
Knn.resize (n_neighbor_dofs, n_neighbor_dofs);
// Lower-to-lower coupling term
for (unsigned int qp=0; qp<qface.n_points(); qp++)
for (unsigned int i=0; i<n_dofs; i++)
for (unsigned int j=0; j<n_dofs; j++)
Kee(i,j) -= JxW_face[qp] * (1./R)*(phi_face[i][qp] * phi_face[j][qp]);
// Lower-to-upper coupling term
for (unsigned int qp=0; qp<qface.n_points(); qp++)
for (unsigned int i=0; i<n_dofs; i++)
for (unsigned int j=0; j<n_neighbor_dofs; j++)
Ken(i,j) += JxW_face[qp] * (1./R)*(phi_face[i][qp] * phi_neighbor_face[j][qp]);
// Upper-to-upper coupling term
for (unsigned int qp=0; qp<qface.n_points(); qp++)
for (unsigned int i=0; i<n_neighbor_dofs; i++)
for (unsigned int j=0; j<n_neighbor_dofs; j++)
Knn(i,j) -= JxW_face[qp] * (1./R)*(phi_neighbor_face[i][qp] * phi_neighbor_face[j][qp]);
// Upper-to-lower coupling term
for (unsigned int qp=0; qp<qface.n_points(); qp++)
for (unsigned int i=0; i<n_neighbor_dofs; i++)
for (unsigned int j=0; j<n_dofs; j++)
Kne(i,j) += JxW_face[qp] * (1./R)*(phi_neighbor_face[i][qp] * phi_face[j][qp]);
system.matrix->add_matrix(Kne, neighbor_dof_indices, dof_indices);
system.matrix->add_matrix(Ken, dof_indices, neighbor_dof_indices);
system.matrix->add_matrix(Kee, dof_indices);
system.matrix->add_matrix(Knn, neighbor_dof_indices);
// Find the dofs on neighbor that are on CRACK_BOUNDARY_UPPER
for(unsigned int neighbor_side=0;
neighbor_side<neighbor->n_sides();
neighbor_side++)
{
if(mesh.get_boundary_info().has_boundary_id (
neighbor, neighbor_side, CRACK_BOUNDARY_UPPER))
{
for(unsigned int neighbor_node_index=0;
neighbor_node_index<neighbor->n_nodes();
neighbor_node_index++)
{
if(neighbor->is_node_on_side(neighbor_node_index, neighbor_side))
{
dof_id_type neighbor_node_id = neighbor->node_ref(neighbor_node_index).dof_number(0,0,0);
if(dof_map.is_constrained_dof(neighbor_node_id))
{
constrained_upper_dofs.push_back(neighbor_node_id);
}
}
}
}
}
}
}
}
if(!constrained_upper_dofs.empty() || !constrained_lower_dofs.empty())
{
std::cout << "constrained upper dofs: ";
for(unsigned int i=0; i<constrained_upper_dofs.size(); i++)
{
std::cout << constrained_upper_dofs[i] << " ";
}
std::cout << std::endl;
std::cout << "constrained lower dofs: ";
for(unsigned int i=0; i<constrained_lower_dofs.size(); i++)
{
std::cout << constrained_lower_dofs[i] << " ";
}
std::cout << std::endl << std::endl;
}
dof_map.constrain_element_matrix_and_vector (Ke, Fe, dof_indices);
system.matrix->add_matrix (Ke, dof_indices);
system.rhs->add_vector (Fe, dof_indices);
}
}
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, SlashDot.org! http://sdm.link/slashdot
_______________________________________________
Libmesh-devel mailing list
Libmesh-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/libmesh-devel