Modeling surface contact with point-to-point linear contact

Hi, I’m following the book “Practical Finite Element Analysis for mechanical engineers”, and in section 14.7.2 I find an example where the base of a fitting is in contact with a plate and this contact is modeled as a point-to-point linear contact:

All the nodes of the fitting’s base have their own MPCs. The plate is not represented in the model, the MPC linear contacts are grounded.

I’m using PreProMax for the FEM except for the MPCs. The CalculiX manual mentions in point 6.7.2 that MPCs are done with *EQUATION and SPCs, so I’m crafting with a Python script the additional code needed for the .inp file.

I’m creating two groups of new nodes corresponding to the ones in the base of the fitting. A first group is created (and placed in the exact same place as the original ones of the base of the fitting) and I add some SPCs to constrain their x,y,z degrees of freedom with *BOUNDARY. The second group of nodes are as well created in the original position of the ones of the base and their x and y DOF restricted with *BOUNDARY.

Finally, I use *EQUATION for each node of the base to say something like:
-w_555 + w_1555 + w_2555 = 0


  • w_555 would be the z component of the node 555 (one of the nodes of the base)
  • w_1555 is the z component of the fully constrained new node 1555
  • w_2555 is is the z component of the new node 2555 whose z is unconstrained.

So I expect that w_555 = w_1555 + w_2555, and since w_1555 is fully constrained, only w_2555 move. The problem is that it’s moving up and down, I would expect it to move only upwards and not be able to go below w_1555. How do I do that?

I have uploaded all my files here for easier inspection:

The code is the following:

from collections import namedtuple

Point = namedtuple('Point', ['x', 'y', 'z'])

def MPC(nodes, base_nodes, movable_nodes):
    These must be defined before the first *STEP in the Calculix .inp file
    - Number of terms in the equation
    - Node number of the first variable.
    - Degree of freedom at above node for the first variable.
    - Value of the coefficient of the first variable.
    equations = []
    for node_id, base_node_id, mv_node_id in zip(nodes, base_nodes, movable_nodes):
        eq = (
            f"** MY EQUATIONS\n*EQUATION\n"
            f"{node_id}, {3}, -1, {base_node_id}, {3}, 1, {mv_node_id}, {3}, 1\n")
    return "".join(equations)

def get_nodes_map(calculix_filename):
    Load CalculiX .inp file to parse its nodes and return
    something like: {'5462': Point(), ...}

    nodes_map = {}
    found = False
    with open(calculix_filename, "r") as f:
        for line in f.readlines():
            line = line.strip()
            if found:
                if "**" in line:
                    node_id, x, y, z = line.split(', ')
                    nodes_map[node_id] = Point(x=float(x), y=float(y), z=float(z))
                if "*Node" in line:
                    found = True
                    print("Found where the nodes are")

    return nodes_map

def gen_nodes(first_node_id, query_nodes, calculix_filename="Fitting_fea.inp"):
    Create new nodes (with id starting at `first_node_id`) positioned in
    the same place of the query nodes.

    nodes_map = get_nodes_map(calculix_filename)
    s = "** MY NODES\n*Node\n"
    dummy_nodes = []                
    for i,node_id in enumerate(query_nodes):
        x,y,z = nodes_map[str(node_id)]
        s += f"{first_node_id+i}, {x}, {y}, {z}\n"

    return s, dummy_nodes

def constrain_dofs(nodes):
    These must be defined inside a *STEP

    for node_id in nodes:
        s += f"{node_id}, 1, 2\n"
    return s

def fix_base(nodes):
    s = "** Name: MY BOUNDARIES\n*BOUNDARY\n"
    for node_id in nodes:
        s += f"{node_id}, 1, 3, 0\n" # constrain x, y, z
    return s

# The follwing list of nodes was extracted from an exported CalculiX file based on a Node Set
nodes = [
70, 71, 74, 75, 80, 81, 84, 85, 448, 452, 453, 454, 455, 456, 457, 458, 
459, 460, 461, 474, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 615, 616, 
617, 618, 619, 620, 621, 622, 623, 624, 637, 638, 639, 640, 641, 642, 643, 644, 
645, 646, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 
662, 663, 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 2586, 2587, 2588, 2589, 
2590, 2591, 2592, 2593, 2594, 2595, 2596, 2597, 2598, 2599, 2600, 2601, 2602, 2603, 2604, 2605, 
2606, 2607, 2608, 2609, 2610, 2611, 2612, 2613, 2614, 2615, 2616, 2617, 2618, 2619, 2620, 2621, 
2622, 2623, 2624, 2625, 2626, 2627, 2628, 2629, 2630, 2631, 2632, 2633, 2634, 2635, 2636, 2637, 
2638, 2639, 2640, 2641, 2642, 2643, 2644, 2645, 2646, 2647, 2648, 2649, 2650, 2651, 2652, 2653, 
2654, 2655, 2656, 2657, 2658, 2659, 2660, 2661, 2662, 2663, 2664, 2665, 2666, 2667, 2668, 2669, 
2670, 2671, 2672, 2673, 2674, 2675, 2676, 2677, 2678, 2679, 2680, 2681, 2682, 2683, 2684, 2685, 
2686, 2687, 2688, 2689, 2690, 2691, 2692, 2693, 2694, 2695, 2696, 2697, 2698, 2699, 2700, 2701, 
2702, 2703, 2704, 2705, 2706, 2707, 2708, 2709, 2710, 2711, 2712, 2713, 2714

with open("nodes.txt", "w") as f:
    s, dummy_base_nodes = gen_nodes(first_node_id=5487, query_nodes=nodes)

    s, dummy_movable_nodes = gen_nodes(first_node_id=5487+len(dummy_base_nodes), query_nodes=nodes)

with open("boundaries.txt", "w") as f:
    s = fix_base(dummy_base_nodes)

    s = constrain_dofs(dummy_movable_nodes)

with open("equations.txt", "w") as f:
    s = MPC(nodes, dummy_base_nodes, dummy_movable_nodes)

The expected result:

What I get:

The deformation in your screenshot is scaled, maybe this part of the model actually moves down only a bit.

Have you compared your MPC equation with those presented in the book ? By the way, you can download the files for all examples included in this book from author’s website. The only problem is that they are MSC-NASTRAN files but if you have access to such software, it might be helpful to check how this example is defined there.

1 Like

Thanks for your answer. Indeed it moves, but I would expect it to not move at all, or am I wrong expecting that?

I have seen the decks in the author’s page for this specific problem, I tried loading it with pynastrangui but I received an error. I don’t have access to any Nastran and I’m not yet comfortable with the syntaxis.

For completeness, these are the equations shown in the book:

I’m considering:

  • u_closing would be my fully restricted nodes
  • u_opening would be the base of the plate and
  • u_g would be my nodes with only x and y restricted.

HI Manuel,

You need a " Compression Only" BC at the base .
I have done something similar with other software but I don’t see this option in CalculiX.(I can see only compression material in the manual).
Maybe some other user knows how to sort this.

EDIT. I have just realized that you could maybe include the base plate in the model as an Only compression material, just to avoid penetration below the BOP. You will have to merge perfectly the nodes (extrusion of the base) and use a very high young modulus for this base plate.

1 Like

That’s an interesting workaround but the purpose of this analysis is different. After all, standard contact between the fitting and plate (also rigid) could also be defined. However, the goal here is to do it the old linear way :wink: So MPCs must suffice.

I think that the problem may lie in u_0 which is a scalar point, according to the book. This specific functionality is offered by Nastran and one needs to carefully consider how to replace it in CalculiX.

1 Like

I believe that MSC Software (Hexagon now?) has student/hobbyist licenses available. Also, I think cgx can read Nastran *.bdf files → Overview pre/post-processor capabilities of CalculiX Version 2.18.

Can you just simply constrain the other nodes in Z-direction? Something like:

NodeSet_Fixed_Bottom, 3,3,0.0

Would that work?

The software I have used says :

“A compression only support behaves similar to a frictionless support at nodes where the support is in compression and applies no constraint to nodes that would put the support in tension. This is a non-linear feature so the solver will perform several iterations to determine which nodes should be constrained and which are free to displace. It stops when there’s no change in the status of any node between two successive iterations. It can only be applied to 2D faces, this includes faces of solid elements and the surfaces of shells”.

I think the proper way to reproduce this in Calculix would be not merging but stablishing an SPC of each node to its equivalent in the base plate.
The base plate would have its base free to slide in the plane defined by the normal (XY in this case).
Base plate made of only compression material with huge Young Modulus. Base Plate probably build from a normal offset of the surface of interest to have an easy and perfectly matching Point to Point correspondence.
Node to Node SCP of the Normal displacement component. In this case something like Uzi(F) +Uzi(BP) = 0 for i=1,…N with N the number of nodes.

Rigid contact or merging nodes between the two bodies as I suggested would not be the same as it wouldn’t allow compression of the fitting base in the XY plane.

1 Like