C2.8 Concrete Selfweight

Calculate the concrete based on the changing tributary vertex areas. (by Lotte)

Objectives

You will learn how to form find the cable net under self-weight that adapts to each form-finding iteration.

In the steps C2.1 to C2.7 we form found the cablemesh for its pure prestress state and with constant external loading. Now, if the concrete weight is added as an external load 'pz', the geometry will find a new configuration in equilibrium for this loading state. However, in the new geometric configuration, the loads from the concrete's self-weight have changed with the tributary areas of the vertices. The previous loads were bound to the previous geometry and are not valid anymore. Instead, new loads must be calculated to the current geometry of the form-active structure. Thus the cablemesh must be again form found for the new loading state. This must be repeated until the difference in loads/residuals is small enough to consider convergence.

Procedure

The file is the same as in C2.7 Internal Supports with the following modifications:

What is happening?

  • in step a. we have no loads applied yet, it is the pure prestressed state

  • in step b. the structure found its new equilibrium shape under the self-weight that was calculated for the prestressed state

  • c. the loads from the new equilibrium shape are higher than the ones from the prestressed state because the tributary area increased. Thus, this leads to a delta that must be added to the residual forces.

  • if in step d. we now we form find again, the equilibrium only differs very slightly from the previous equilibrium state, thus the load's delta is very tiny.

  • in the next step, the geometry does not visibly change anymore and the residuals are so small that the system converges.

a. Update Default Attributes

Add to the cablemesh data structure the thickness (35 mm) of the concrete shell to the default vertex attributes and the concrete density (24 kN/m3) as a general mesh attribute. Also set the external load back to 0.

# ==============================================================================
# a. Cablenet mesh datastructure > NEW
# ==============================================================================

# create the mesh from imported geometry
mesh = Mesh.from_json(FILE_I)

# set default vertex attributes
dva = {
    ..
    'pz': 0.0,            # Z-component of an externally applied load. > NEW: back to 0   # noqa: E501
    ...    
    't': 0.035            # Thickness of the concrete shell. > NEW!
}
mesh.update_default_vertex_attributes(dva)

...

# set mesh attributes > NEW!
mesh.attributes['density'] = 24.0  # Density of the lightweight concrete.

At this stage, nothing has changed yet, the vertex load 'pz' is back to 0 and it is in its pure prestress state.

b. Compute the Selfweight

We will compute the self-weight per vertex based on its tributary area, shell thickness and concrete density, and add this load as the vertex attribute 'pz'. Then we form find the cablemesh under this concrete loading that is bound to the geometry from the pure prestressed state!

The selfweight should be computed for all vertices that are not anchored as follows and assigned in the negative direction to the 'pz' attribute.

# ==============================================================================
# Helpers
# ==============================================================================

def selfweight(mesh):  # b. NEW
    """Compute the selfweight per vertex and update the load attribute in negative z.  # noqa: E501
    """
    density = mesh.attributes['density']

    # for all vertices that are free
    for key, attr in mesh.vertices_where({'is_anchor': False}, True):

        # get the thickness of the vertex
        thickness = attr['t']

        # compute the tributary area of the vertex
        area = mesh.vertex_area(key)

        # compute the concrete weight
        selfweight = area * thickness * density

        # apply selfweight by updating vertex attribute (acting in negative z)
        attr['pz'] = -selfweight
# ==============================================================================
# Compute equilibrium and update the geometry under changing selfweight # NEW!
# ==============================================================================

# compute selfweight for the initial geometry > b. NEW
selfweight(mesh)

# form finding with selfweight loads and geometry update
fofin(mesh)

Also, scale up the residuals and loads to better understand what is going on:

...
draw_residuals(mesh, baselayer=baselayer, scale=4)
...
draw_loads(mesh, baselayer=baselayer, scale=4)

The cablemesh has sagged down under the self-weight load:

Through this change of geometry the self-weight at each vertex has changed (the tributary area has become either larger or smaller), thus the load is not valid anymore and must be updated! This results in residual forces.

c. Update Residuals with Difference of Selfweights

The residuals that are retrieved after the fofin update do not represent the change in self-weights between the geometry before and after the form-finding step (the tributary area changes in size). Thus, the difference in self-weight must be added to the residuals after the self-weight is recalculated for the updated geometry.

use Mesh.key_index to translate the list sorted by indices to the respective key!

def update_residual(mesh, loads_previous):  # c. NEW!
    """Compute the residual forces with respect to the stored loads.
    """
    key_index = mesh.key_index()

    R = []
    for key in mesh.vertices_where({'is_anchor': False}):

        index = key_index[key]

        p_previous = loads_previous[index]
        p = mesh.vertex_attributes(key, ['px', 'py', 'pz'])
        r = mesh.vertex_attributes(key, ['rx', 'ry', 'rz'])

        r = add_vectors(r, subtract_vectors(p, p_previous))

        mesh.vertex_attributes(key, ['rx', 'ry', 'rz'], r)

        r_length = length_vector(r)
        R.append(r_length)

    residuals = sum(R)

    return residuals

Visualise the resulting residuals.

Remember to store the previous loads before computing the new self-weight and the resulting residual forces!

# ==============================================================================
# Compute equilibrium and update the geometry under changing selfweight # NEW!
# ==============================================================================

# compute selfweight for the initial geometry > b. NEW
selfweight(mesh)

# form finding with selfweight loads and geometry update
fofin(mesh)

# > c. NEW
# store previous selfweight loads for which the cablemesh was formfound  # noqa: E501
loads_previous = mesh.vertices_attributes(('px', 'py', 'pz'))

# recompute selfweight for the updated geometry
selfweight(mesh)

# recompute the residuals with difference of selfweight from updated to previous geometry # noqa: E501
residuals = update_residual(mesh, loads_previous)
print(residuals)

d. Form find again under new selfweight

Since the residuals are above the acceptable threshold, the cablemesh must be form found again under the new loading condition! Recompute the residuals to check if the system convergences.

# ==============================================================================
# Compute equilibrium and update the geometry under changing selfweight # NEW!
# ==============================================================================

... after c. ...

# d1. repeat > NEW
# form finding with selfweight loads and geometry update
fofin(mesh)

# store previous selfweight loads for which the cablemesh was formfound > c. NEW  # noqa: E501
loads_previous = mesh.vertices_attributes(('px', 'py', 'pz'))

# recompute selfweight for the updated geometry > c. NEW
selfweight(mesh)

# recompute the residuals with difference of selfweight from updated to previous geometry > c. NEW  # noqa: E501
residuals = update_residual(mesh, loads_previous)
print(residuals)

The change in geometry is already almost not visible anymore and the residual forces are too small to visualise:

To make sure that the form finding has converged, repeat the last step again:

# ==============================================================================
# Compute equilibrium and update the geometry under changing selfweight # NEW!
# ==============================================================================

... after d1. ...

# d2. repeat again > NEW
# form finding with selfweight loads and geometry update
fofin(mesh)

# store previous selfweight loads for which the cablemesh was formfound > c. NEW  # noqa: E501
loads_previous = mesh.vertices_attributes(('px', 'py', 'pz'))

# recompute selfweight for the updated geometry > c. NEW
selfweight(mesh)

# recompute the residuals with difference of selfweight from updated to previous geometry > c. NEW  # noqa: E501
residuals = update_residual(mesh, loads_previous)
print(residuals)

Now the change of geometry is not anymore visible and the system converged!

e. Iterative Procedure

Now, all steps would need to be repeated until the residuals converge below a certain admissible threshold. Instead of copying the code over and over, we wrap the procedure into a for-loop and into a function. After each iteration we verify if the residuals are below a threshold and if so break the loop.

Set a maximum number of iterations so that in case you don't find convergence your computer does not crash!

def fofin_selfweight(mesh):
    """Compute the equilibrium for the concrete selfweight with the iterative procedure.  # noqa: E501
    """
    # define maximum iterations and tolerance for residuals
    tol = 0.01
    kmax = 10

    # compute selfweight for the starting geometry
    selfweight(mesh)

    # for all k smaller than kmax
    for k in range(kmax):

        # form finding with selfweight loads and geomtry update
        fofin(mesh)

        # store previous selfweight loads for which the cablemesh was formfound
        loads_previous = mesh.vertices_attributes(('px', 'py', 'pz'))

        # recompute selfweight for the updated geometry
        selfweight(mesh)

        # recompute the residuals with difference of selfweight from updated to previous geometry  # noqa: E501
        residuals = update_residual(mesh, loads_previous)

        # stopping criteria if updated residual is smaller than tolerance or at least once  # noqa: E501
        if residuals < tol and not k == 0:
            print('Convergence! at k', k, 'residuals', residuals)
            break
        else:
            print('k', k, 'residuals', residuals)

Then all you need to do in the future is call this function:

# ==============================================================================
# Compute equilibrium and update the geometry under changing selfweight # NEW!
# ==============================================================================

fofin_selfweight(mesh)

Last updated