-
Notifications
You must be signed in to change notification settings - Fork 249
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Core] Neighbor MPC Assigner: A process and utility for assigning MPCs to neighbouring nodes #11083
Conversation
It is currently done in serial.
It worked when using vector variable, e.g. VELOCITY
@KratosMultiphysics/altair and specially @ddiezrod |
for constraint in self.computing_model_part.MasterSlaveConstraints: | ||
constraint.Set(KM.TO_ERASE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use VariableUtils
@@ -0,0 +1,3308 @@ | |||
Begin Properties 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, can you consider a smaller mesh?, you can put this one in the Example repository
|
||
ModelPart::MasterSlaveConstraintType const& r_clone_constraint = KratosComponents<MasterSlaveConstraint>::Get("LinearMasterSlaveConstraint"); | ||
|
||
#pragma omp parallel |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please ParallelUtilities
DofPointerVectorType& rCloud_of_dofs, | ||
array_1d<double,3>& rSlave_coordinates | ||
){ | ||
KRATOS_TRY; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tabs missing (spaces)
Matrix& rCloud_of_nodes_coordinates | ||
) | ||
{ | ||
KRATOS_TRY; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tabs missing (spaces)
) | ||
{ | ||
//TODO: Do it in parallel | ||
KRATOS_TRY; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tabs missing (spaces)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments
Matrix r_cloud_of_nodes_coordinates; | ||
array_1d<double,3> r_slave_coordinates; | ||
GetDofsAndCoordinatesForNode(pNode, rVariable, r_slave_dof, r_slave_coordinates); | ||
KRATOS_WATCH(i) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
KRATOS_WATCH(i) |
Debugging leftover.
Way slower than OMP and I couldn't manage to do the omp critical for adding the MPCs.
I've been trying to switch from OMP to
Overall, it seems like using For the current implementation, I kept the original code using |
* @param RotationMatrix 3x3 rotation matrix to be applied to the nodes' positions. | ||
**/ | ||
void AssignRotationToNodes( | ||
NodesContainerType pNodes, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a reference. Indeed it is strange to me that this works if you pass it by copy...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not used anymore.
|
||
// Search for the neighbouring nodes (in rStructureNodes) of each rNode on a given array of rNodes | ||
void SearchNodesInRadiusForNodes( | ||
NodesContainerType const& rNodes, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though it is valid, in Kratos we normally put the const
in front.
|
||
// Search for the neighbouring nodes (in rStructureNodes) of each rNode on a given array of rNodes | ||
void AssignMPCsToNeighboursUtility::SearchNodesInRadiusForNodes( | ||
NodesContainerType const& rNodes, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normally in Kratos we put the const
in front.
VectorResultNodesContainerType& rResults, | ||
const double MinNumOfNeighNodes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a good programming practice to put the results arguments after the input (const ones).
const double MinNumOfNeighNodes) | ||
{ | ||
KRATOS_TRY; | ||
NodesContainerType::ContainerType& nodes_array = const_cast<NodesContainerType::ContainerType&>(rNodes.GetContainer()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
auto& r_nodes_array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And why you need the const_cast
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved, changed to:
KRATOS_TRY;
auto& r_nodes_array = rNodes.GetContainer();
rResults.resize(r_nodes_array.size());
// Declare a counter variable outside the loop as std::atomic<int>
std::atomic<int> i(0);
block_for_each(r_nodes_array, [&](const Node<3>::Pointer& pNode)
{
|
||
block_for_each(nodes_array, [&](Node<3>::Pointer& pNode) | ||
{ | ||
double localRadius = Radius; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please follow the style guide (local_radius
).
KRATOS_TRY; | ||
NodesContainerType::ContainerType& nodes_array = const_cast<NodesContainerType::ContainerType&>(rNodes.GetContainer()); | ||
|
||
rResults.resize(nodes_array.size()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means you're allocating much memory than needed. I'd do a shrink_to_fit
at the end of this function to avoid so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am allocating what I need. Nonetheless, I could updated it to this:
// Resize rResults vector
rResults.resize(r_nodes_array.size());
// Declare a counter variable outside the loop as std::atomic<int>
std::atomic<int> i(0);
block_for_each(r_nodes_array, [&](const Node<3>::Pointer& pNode)
{
double local_radius = Radius;
IndexType it = i.fetch_add(1); // Atomically increment the counter and get the previous value
while (rResults[it].size()<MinNumOfNeighNodes){
rResults[it].clear();
SearchNodesInRadiusForNode(pNode, local_radius, rResults[it]);
local_radius += Radius;
}
});
// Optimize memory usage
rResults.shrink_to_fit();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. You are allocating much more than what you need since you're resizing the vector to the number of nodes, which would only be the case if all the nodes are found. Indeed, the proper usage would be
rResults.reserve(r_nodes_array.size());
// ... your stuff in here ...
rResults.shrink_to_fit();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am changing the names of the functions to make them more self-explanatory. In this function, we retrieve the degrees of freedom (DOFs) and coordinates of the cloud of nodes. Therefore, we already know the size.
|
||
//Search for the neighbouring nodes (in rStructureNodes) of the given rNode | ||
void AssignMPCsToNeighboursUtility::SearchNodesInRadiusForNode( | ||
NodeType::Pointer pNode, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd pass a constant reference instead. Note that the SearchInRadius
supports it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you find it necesarry, since this is a constant pointer. Isn't it okay like this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it is a constant pointer but nevermind, leave it like this.
|
||
if (ComponentIndex < 3) { | ||
std::string component_variable_name = rVectorVariable.Name() + component_suffixes[ComponentIndex]; | ||
return KratosComponents<Variable<double>>::Get(component_variable_name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is extremely expensive and being done, if I'm not wrong, inside a loop. An alternative approach must be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After a long afternoon, I managed to do it. Now it only acces the Kratos components once and has a pointer in the loop.
rCloud_of_dofs.push_back(pNode->pGetDof(rVariable)); | ||
rSlave_coordinates = pNode->Coordinates(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the point on doing a function of two lines. IMO it only makes more complex browsing the code with no advantage at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is to preserve similarity. Is it really necessary to undo this function? I know it is not typical to have a two line function, but for me, it preserves similarity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get your point on "similarity"... I'd remove it but if you really require it leave it. Up to you.
if (pNode->HasDofFor(GetComponentVariable(rVariable, 0)) && | ||
pNode->HasDofFor(GetComponentVariable(rVariable, 1)) && | ||
pNode->HasDofFor(GetComponentVariable(rVariable, 2))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would happen in the 2D case in which the third component DOF might not be present?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this really something that happens?
The user can also specify ["DISPLACEMENT_X","DISPLACEMENT_Y"].
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought the 2D also 3 components. But solves only for X and Y components.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Depends on the implementation so you cannot take it as granted.
DofPointerVectorType& rCloud_of_dofs_x, | ||
DofPointerVectorType& rCloud_of_dofs_y, | ||
DofPointerVectorType& rCloud_of_dofs_z, | ||
array_1d<double,3>& rSlave_coordinates |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please follow the style guide for function arguments.
|
||
|
||
void AssignMPCsToNeighboursUtility::GetDofsAndCoordinatesForNodes( | ||
ResultNodesContainerType nodes_array, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why passing by copy?
rCloud_of_dofs_x.resize(nodes_array.size()); | ||
rCloud_of_dofs_y.resize(nodes_array.size()); | ||
rCloud_of_dofs_z.resize(nodes_array.size()); | ||
rCloud_of_nodes_coordinates.resize(nodes_array.size(), 3); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd try to shrink these containers afterwards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(same reseve
and shrink_to_fit
comment).
// Check if the node has the required DOFs | ||
if (pNode->HasDofFor(GetComponentVariable(rVariable, 0)) && | ||
pNode->HasDofFor(GetComponentVariable(rVariable, 1)) && | ||
pNode->HasDofFor(GetComponentVariable(rVariable, 2))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment about the 2D third component.
{ | ||
//TODO: Do it in parallel | ||
KRATOS_TRY; | ||
NodesContainerType::ContainerType& nodes_array = const_cast<NodesContainerType::ContainerType&>(pNodes.GetContainer()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
auto& r_nodes_array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the const_cast
?
|
||
// Assign rotation to given nodes | ||
for(unsigned int i = 0; i < nodes_array.size(); i++){ | ||
auto coordinate_vector = nodes_array[i]->GetInitialPosition().Coordinates(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are allocating/deallocating an unnecessary copy for each node.
// Assign rotation to given nodes | ||
for(unsigned int i = 0; i < nodes_array.size(); i++){ | ||
auto coordinate_vector = nodes_array[i]->GetInitialPosition().Coordinates(); | ||
Vector rotated_position(coordinate_vector.size()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't this be an array_1d<double,3>
?
nodes_array[i]->X() = rotated_position[0]; | ||
nodes_array[i]->Y() = rotated_position[1]; | ||
nodes_array[i]->Z() = rotated_position[2]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
noalias(nodes_array[i]->Coordinates()) = rotated_position;
I think this should work.
void AssignMPCsToNeighboursUtility::AssignMPCsToNodes( | ||
NodesContainerType pNodes, | ||
double const Radius, | ||
ModelPart& rComputingModelPart, | ||
const Variable<array_1d<double, 3>>& rVariable, | ||
double const MinNumOfNeighNodes | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is basically copy & paste of the above function but modifying the section in which the Master-Slave Constraints are created. I'd do the creation within a private auxiliary function and have a unique implementation of AssignMPCsToNodes
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a concern here, at first I though of the user passing a variable which could be either array_1d or double.
If array_1d, then you have to create the variable components lists, etc. If double, you can directly retrieve the varible with GetDof. Now I am thinking to always create a list of variables, e.g, if ["VELOCITY","PRESSURE], then create a list:
["DISPLACEMENT_X","DISPLACEMENT_Y","PRESSURE"] for 2D.
Or:
["DISPLACEMENT_X","DISPLACEMENT_Y","DISPLACEMENT_Z","PRESSURE"] for 3D.
We can discuss this in person, since it is taking a lot of time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK.
// Add the constraints to the rComputingModelPart in a single call | ||
rComputingModelPart.AddMasterSlaveConstraints(temp_constraints.begin(), temp_constraints.end()); | ||
|
||
KRATOS_INFO("AssignMPCsToNeighboursUtility") << "Build and Assign MPCs Time: " << build_and_assign_mpcs.ElapsedSeconds() << std::endl; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may result in a too verbose output. If you want to output this, I'd introduce an echo level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO.
|
||
BuiltinTimer build_and_assign_mpcs; | ||
|
||
int prev_num_mpcs = rComputingModelPart.NumberOfMasterSlaveConstraints(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In here you are assuming that your new MPCs are not interacting with previous ones. Though this might be a valid assumption, I'd throw a warning message if the previous number of MPCs is non-zero to inform about this.
|
||
int prev_num_mpcs = rComputingModelPart.NumberOfMasterSlaveConstraints(); | ||
|
||
NodesContainerType::ContainerType& nodes_array = const_cast<NodesContainerType::ContainerType&>(pNodes.GetContainer()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
auto& r_nodes_array
.
|
||
NodesContainerType::ContainerType& nodes_array = const_cast<NodesContainerType::ContainerType&>(pNodes.GetContainer()); | ||
|
||
ModelPart::MasterSlaveConstraintType const& r_clone_constraint = KratosComponents<MasterSlaveConstraint>::Get("LinearMasterSlaveConstraint"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const auto& r_clone_constraint`.
DofPointerVectorType r_cloud_of_dofs, r_slave_dof; | ||
Matrix r_cloud_of_nodes_coordinates; | ||
array_1d<double, 3> r_slave_coordinates; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're allocating/deallocating these for each node. Declare them as a TLS outside the loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Master-Slave constraints require rSlaveDofs to be a DofPointerVectorType. To do r_slave_dofs thread_local I need to resize and assign to Dof::Pointer. Or:
rCloudOfDofs.resize(1);
rCloudOfDofs[0] = pNode->pGetDof(rVariable);
Which I dont like. I will fix it.
list_of_processes = process_factory.KratosProcessFactory(current_model).ConstructListOfProcesses(settings["process_list"]) | ||
for process in list_of_processes: | ||
process.ExecuteInitialize() | ||
process.ExecuteInitializeSolutionStep() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You have a unique process. I don't see the point on doing it with a list. I'd directly call the constructor.
for ew, ow in zip(expected_weights, obtained_weights): | ||
self.assertAlmostEqual(ew, ow, delta=1e-6) # Assert the weights are equal within the specified tolerance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm not wrong, there is an assertAlmostEqualVector
or something of this sort.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assertVectorAlmostEqual
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though this is a working implementation, I think it doesn't suffice the quality requirements for being in the KratosCore
. I hope you take my comments into consideration in a new PR.
KRATOS_CATCH(""); | ||
} | ||
|
||
void AssignMPCsToNeighboursUtility::AssignRotationToNodes( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not used anywhere in this process. Besides, it is a custom function for your target application. Long story short, it must be removed from here to be placed somewhere else.
DESCRIPTION:
Utility:
This pull request adds a new class that provides a method to search for neighboring nodes of a given nodes structure within a specified radius and assign a multipoint constraint (MPC). The MPC weights are calculated based on a spatial function that employs radial basis functions.
Process:
This pull request enhances the functionality of the master-slave MPC algorithm by facilitating the discovery of neighboring nodes in a master model part within a designated radius for each node in the slave model part. The process involves the following steps:
For each node in the slave model part, the utility searches for neighboring nodes in the master model part within a specified radius.
![image](https://user-images.githubusercontent.com/61457043/236417112-8c5b1c89-9e43-4467-bba0-241ad994b17a.png)
![image](https://user-images.githubusercontent.com/61457043/236417146-46b90785-4b0a-48c0-8949-f9ba2abed309.png)
![image](https://user-images.githubusercontent.com/61457043/236417228-ba3a7a9c-519f-452c-b2db-69fd1226a06e.png)
The utility then establishes an MPC between the slave node and its neighboring master nodes, and calculates the MPC weights based on a spatial function that employs radial basis functions.
![image](https://user-images.githubusercontent.com/61457043/236417284-bcaa49ba-340e-45de-9fc8-695766648072.png)
![image](https://user-images.githubusercontent.com/61457043/236417336-6e117e24-5a91-4903-ab4c-d302b29ae576.png)
The process repeats for all nodes in the slave model part.
Added files:
assign_mpcs_to_neighbours_utility.cpp
: This file contains all the functions to look for neighboring nodes and apply multipoint constraints (MPCs) using radial basis functions.assign_mpcs_to_neighbours_utility.h
: This header file defines the functions and data structures used in assign_mpcs_to_neighbours_utility.cpp`.assign_mpcs_to_neighbours_process.py
: This Python script calls the functions inassign_mpcs_to_neighbours_utility.cpp
in the appropriate order to assign MPCs to neighboring nodes.assign_mpcs_to_neighbours_process.mdpa
: This is an MDPA file used for testing the MPC algorithm.Modified files:
add_geometrical_utilities_to_python.cpp
: This file was modified to export the functions inassign_mpcs_to_neighbours_utility.cpp
to Python, so that they can be called fromassign_mpcs_to_neighbours_process.py
.test_processes.py
: This file was modified to include a small test to validate the functionality ofassign_mpcs_to_neighbours_process.py
.