Good morning everyone,
1. I am attempting to translate the multiphenicsx.fem.petsc.create_matrix_block to cpp. Similarly, the function will take the Jacobian matrix and tuple of the multiphenicsx DofMapRestriction. Also, I do know that both codes will essentially end up using the C++ source code of multiphenicsx and dolfinx, which is why I’m even more lost and therefore asking for help.
Mat _create_matrix_block(
const std::vector<std::vector<std::shared_ptr<dolfinx::fem::Form<PetscScalar>>>>& J,
const std::array<std::vector<std::shared_ptr<multiphenicsx::fem::DofMapRestriction>>, 2>&
restriction_J)
which will,
return multiphenicsx::fem::petsc::create_matrix_block(
J_raw, index_maps, index_maps_bs, dofmaps_list, dofmaps_bounds);
2. Now consider this minimal python example:
import dolfinx as dfx
import multiphenicsx.fem
from mpi4py import MPI
from petsc4py.PETSc import ScalarType
import numpy as np
import ufl
from solver import NonlinearBlockProblem, NewtonBlockSolver
mesh = dfx.mesh.create_unit_interval(MPI.COMM_WORLD, 100)
boundaries = [(1, lambda x: np.isclose(x[0], 0)),
(2, lambda x: np.isclose(x[0], 1)),]
left_facets = dfx.mesh.locate_entities(mesh, 0, boundaries[0][1])
right_facets = dfx.mesh.locate_entities(mesh, 0, boundaries[1][1])
facet_indices, facet_markers = [], []
fdim = mesh.topology.dim - 1
for (marker, locator) in boundaries:
facets = dfx.mesh.locate_entities(mesh, fdim, locator)
facet_indices.append(facets)
facet_markers.append(np.full(len(facets), marker))
facet_indices = np.array(np.hstack(facet_indices), dtype=np.int32)
facet_markers = np.array(np.hstack(facet_markers), dtype=np.int32)
sorted_facets = np.argsort(facet_indices)
facet_tag = dfx.mesh.meshtags(
mesh, fdim, facet_indices[sorted_facets], facet_markers[sorted_facets])
PHI_E = dfx.fem.FunctionSpace(mesh, ('CG', 1))
LM_IAPP = PHI_E.clone()
phi_e, lm_iapp = dfx.fem.Function(PHI_E), dfx.fem.Function(LM_IAPP)
dphi_e, dlm_iapp = ufl.TrialFunction(PHI_E), ufl.TrialFunction(LM_IAPP)
v_phi, v_lm_iapp = ufl.TestFunction(PHI_E), ufl.TestFunction(LM_IAPP)
dx = ufl.Measure('dx', mesh)
ds = ufl.Measure('ds', mesh, subdomain_data=facet_tag)
I_app = dfx.fem.Constant(mesh, ScalarType(10))
V_app = dfx.fem.Constant(mesh, ScalarType(1))
switch = dfx.fem.Constant(mesh, ScalarType(0))
residuals = [
ufl.inner(ufl.grad(phi_e), ufl.grad(v_phi)) * dx - lm_iapp * v_phi * ds(2),
((1 - switch) * (I_app - lm_iapp) * v_lm_iapp * ds(2)
+ switch * (V_app - phi_e) * v_lm_iapp * ds(2))
]
jacobian = [[ufl.derivative(r_i, u_i, du_i)
for u_i, du_i in zip([phi_e, lm_iapp], [dphi_e, dlm_iapp])] for r_i in residuals]
bc = dfx.fem.dirichletbc(dfx.fem.Constant(mesh, ScalarType(0)),
dfx.fem.locate_dofs_topological(PHI_E, 0, left_facets), PHI_E)
restriction=[
multiphenicsx.fem.DofMapRestriction(
PHI_E.dofmap, np.arange(0, (PHI_E.dofmap.index_map.size_local
+ PHI_E.dofmap.index_map.num_ghosts))),
multiphenicsx.fem.DofMapRestriction(
LM_IAPP.dofmap, dfx.fem.locate_dofs_topological(LM_IAPP, 0, right_facets))
]
3. Now it is important to note that a class that takes in the residuals, bcs, jacobian, functions etc. is pybinded. So for all dolfinx objects, their cpp_object is passed, with restriction_J being a tuple (restriction, restriction). The key point to note is that _create_matrix_block
is isolated, so I hope the example is transparent.
So the arguments for
Mat create_matrix_block(
const std::vector<std::vector<const dolfinx::fem::Form<PetscScalar, T>*>>& a,
std::array<std::vector<std::reference_wrapper<const dolfinx::common::IndexMap>>, 2> index_maps,
const std::array<std::vector<int>, 2> index_maps_bs,
std::array<std::vector<std::span<const std::int32_t>>, 2> dofmaps_list,
std::array<std::vector<std::span<const std::size_t>>, 2> dofmaps_bounds,
const std::string& matrix_type = std::string()),
in this example problem are:
i) In C++:
ii) In python:
With from what I see, the only difference being the data types.
4. In multiphenicsx::fem::petsc::create_matrix_block
, dolfinx::la::petsc::create_matrix
which will return a petsc matrix.
However,
Mat A = dolfinx::la::petsc::create_matrix(mesh->comm(), pattern, matrix_type);
PetscInt m, n, M, N;
MatGetLocalSize(A, &m, &n);
MatGetSize(A, &M, &N);
the local and global sizes are 202 for both (code is running in series) whereas in python they are 101 for both.
I found out as I was assembling the matrix, the first 101 values where identical, but the matrix generated solely on C++ was four times the size.
Appendix:
Mat _create_matrix_block(
const std::vector<std::vector<std::shared_ptr<dolfinx::fem::Form<PetscScalar>>>>& J,
const std::array<std::vector<std::shared_ptr<multiphenicsx::fem::DofMapRestriction>>, 2>&
restriction_J)
{
std::vector<std::vector<dfx_space>> function_spaces = _get_block_function_spaces(J);
int rows = static_cast<int>(function_spaces[0].size());
int columns = static_cast<int>(function_spaces[1].size());
std::shared_ptr<const dolfinx::mesh::Mesh<T>> mesh = nullptr;
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < columns; j++)
{
auto J_ij = J[i][j];
if (J_ij)
{
mesh = J_ij->mesh();
break;
}
}
if (mesh)
break;
}
// Asserts & checks
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < columns; j++)
{
auto J_ij = J[i][j];
if (J_ij)
{
assert(J_ij->mesh() == mesh);
}
}
}
for (int i = 0; i < 2; i++)
{
for (const auto& function_space: function_spaces[i])
{
assert(function_space->mesh() == mesh);
}
}
// Get index_maps, index_maps_bs, dofmaps_list, dofmaps_bounds
std::array<std::vector<std::reference_wrapper<const dfx_indexmap>>, 2> index_maps;
std::array<std::vector<int>, 2> index_maps_bs;
std::array<std::vector<std::span<const std::int32_t>>, 2> dofmaps_list;
std::array<std::vector<std::span<const std::size_t>>, 2> dofmaps_bounds;
// Only restricted case considered
assert(static_cast<int>(restriction_J[0].size()) == rows);
assert(static_cast<int>(restriction_J[1].size()) == columns);
std::array<int, 2> dims = {rows, columns};
for (int i = 0; i < 2; ++i)
{
for (int j = 0; j < dims[i]; ++j)
{
assert(function_spaces[i][j]->mesh() == mesh);
const auto& J_ij = restriction_J[i][j];
index_maps[i].push_back(std::cref(*(J_ij->dofmap()->index_map)));
index_maps_bs[i].push_back(J_ij->index_map_bs());
dofmaps_list[i].push_back(J_ij->map().first);
dofmaps_bounds[i].push_back(J_ij->map().second);
}
}
// Convert shared_ptr to raw pointers (needed for create_matrix_block)
std::vector<std::vector<const dolfinx::fem::Form<PetscScalar, PetscReal>*>> J_raw(J.size());
for (size_t i = 0; i < J.size(); ++i)
{
J_raw[i].resize(J[i].size());
for (size_t j = 0; j < J[i].size(); ++j)
{
auto J_ij = J[i][j].get();
if (J_ij)
{
J_raw[i][j] = J_ij;
}
}
}
// ///
// // Print inputs
// std::cout << "J_raw:\n";
// for (const auto& row : J_raw) {
// for (const auto& elem : row) {
// std::cout << elem << " ";
// }
// std::cout << "\n";
// }
// std::cout << "index_maps:\n";
// for (const auto& idx_map_list : index_maps) {
// for (const auto& idx_map : idx_map_list) {
// std::cout << idx_map.get().size_local() << " ";
// }
// std::cout << "\n";
// }
// std::cout << "index_maps_bs:\n";
// for (const auto& bs_list : index_maps_bs) {
// for (const auto& bs : bs_list) {
// std::cout << bs << " ";
// }
// std::cout << "\n";
// }
// std::cout << "dofmaps_list:\n";
// for (const auto& dofmap_list : dofmaps_list) {
// for (const auto& dofmap : dofmap_list) {
// for (const auto& val : dofmap) {
// std::cout << val << " ";
// }
// std::cout << "\n";
// }
// std::cout << "\n";
// }
// std::cout << "dofmaps_bounds:\n";
// for (const auto& dofmap_bound_list : dofmaps_bounds) {
// for (const auto& dofmap_bound : dofmap_bound_list) {
// for (const auto& val : dofmap_bound) {
// std::cout << val << " ";
// }
// std::cout << "\n";
// }
// std::cout << "\n";
// }
// ////
/// @param[in] a A bilinear form
/// @param[in] index_maps A pair of index maps. Row index map is given by index_maps[0], column
/// index map is given by index_maps[1].
/// @param[in] index_maps_bs A pair of int, representing the block size of index_maps.
/// @param[in] dofmaps_list An array of spans containing the dofmaps list. Row dofmap is given by
/// dofmaps[0], while column dofmap is given by dofmaps[1].
/// @param[in] dofmaps_bounds An array of spans containing the dofmaps cell bounds.
/// @param[in] matrix_type = std::string(), auto decides depending on if size_local == size_global
return multiphenicsx::fem::petsc::create_matrix_block(
J_raw, index_maps, index_maps_bs, dofmaps_list, dofmaps_bounds, std::string());
}
edit 1: image upload error
edit 2: correct _assemble_matrix_block in appendix
edit 3: expected output from assemble_matrix_block
note: In the original minimal example
jacobian = [[ufl.derivative(r_i, u_i, du_i)
for u_i, du_i in zip([phi_e, lm_iapp], [dphi_e, dlm_iapp])] for r_i in residuals]
restriction=[
multiphenicsx.fem.DofMapRestriction(
PHI_E.dofmap, np.arange(0, (PHI_E.dofmap.index_map.size_local
+ PHI_E.dofmap.index_map.num_ghosts))),
multiphenicsx.fem.DofMapRestriction(
LM_IAPP.dofmap, dfx.fem.locate_dofs_topological(LM_IAPP, 0, right_facets))
]
A = multiphenicsx.fem.petsc.create_matrix_block(
J, restriction=(restriction, restriction))
# View matrix of zeroes
A.zeroEntries()
A.assemble()
A.view()
where,
Mat Object: 1 MPI process
type: seqaij
row 0:
row 1:
row 2:
row 3:
row 4:
...
row 99:
row 100:
row 101: