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.
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 geometrymesh = Mesh.from_json(FILE_I)# set default vertex attributesdva ={ ..'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# ==============================================================================defselfweight(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 freefor 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. NEWselfweight(mesh)# form finding with selfweight loads and geometry updatefofin(mesh)
Also, scale up the residuals and loads to better understand what is going on:
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!
defupdate_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. NEWselfweight(mesh)# form finding with selfweight loads and geometry updatefofin(mesh)# > c. NEW# store previous selfweight loads for which the cablemesh was formfound # noqa: E501loads_previous = mesh.vertices_attributes(('px', 'py', 'pz'))# recompute selfweight for the updated geometryselfweight(mesh)# recompute the residuals with difference of selfweight from updated to previous geometry # noqa: E501residuals =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 updatefofin(mesh)# store previous selfweight loads for which the cablemesh was formfound > c. NEW # noqa: E501loads_previous = mesh.vertices_attributes(('px', 'py', 'pz'))# recompute selfweight for the updated geometry > c. NEWselfweight(mesh)# recompute the residuals with difference of selfweight from updated to previous geometry > c. NEW # noqa: E501residuals =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 updatefofin(mesh)# store previous selfweight loads for which the cablemesh was formfound > c. NEW # noqa: E501loads_previous = mesh.vertices_attributes(('px', 'py', 'pz'))# recompute selfweight for the updated geometry > c. NEWselfweight(mesh)# recompute the residuals with difference of selfweight from updated to previous geometry > c. NEW # noqa: E501residuals =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!
deffofin_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 geometryselfweight(mesh)# for all k smaller than kmaxfor k inrange(kmax):# form finding with selfweight loads and geomtry updatefofin(mesh)# store previous selfweight loads for which the cablemesh was formfound loads_previous = mesh.vertices_attributes(('px', 'py', 'pz'))# recompute selfweight for the updated geometryselfweight(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: E501if residuals < tol andnot k ==0:print('Convergence! at k', k, 'residuals', residuals)breakelse: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)