Load processing#

loadtracer.validation#

Validation and normalization of papermodel dicts.

Two passes are applied to every member during a load rundown:

  1. validate_papermodel: checks the shape of the papermodel as output by

    papermodels.LoadedElement.model() (before any loads are expanded) and normalizes the values that papermodels is known to produce loosely (unsorted supports, list-valued point load locations on columns, distributed loads without extents).

  2. conform_loads_to_member: after the loads have been expanded into their

    load case components, keeps every load component on the member so that the analysis model can be built. Load that falls outside of the member is moved to the nearest member end as a point load so that the total load on the member is unchanged.

Both return the list of issues they found so that the designer can review them.

loadtracer.validation.validate_papermodel(pm, tol=0.001, directions=frozenset({'gravity', 'inplane', 'out_of_plane'}))[source]#

Returns a normalized copy of ‘pm’ and a list of issues found. Raises InvalidPapermodelError if ‘pm’ cannot be used.

Parameters:
  • pm (dict) – A papermodel dict for a single member

  • tol (float) – Supports off the member are moved onto the nearest member end (and reported as an issue when they are more than ‘tol’ off). Supports within ‘tol’ of each other are flagged as coincident.

  • directions (frozenset[str]) – The load directions that are recognized (the keys of the direction mappings on the LoadTracer).

Return type:

tuple[dict, list[str]]

loadtracer.validation.conform_loads_to_member(pm, tol=0.001)[source]#

Returns a list of issues. Mutates the expanded load components of ‘pm’ (a papermodel with expanded loads) so that every component is on the member:

  • Distributed load components with no length are removed.

  • Distributed load components are clipped to the member. The load on the

    clipped-off portion is added as a point load at the nearest member end.

  • Point load components off the member are moved onto the nearest end.

Members with no length (point columns) are not changed.

Parameters:
Return type:

list[str]

loadtracer.expand_occupancies#

loadtracer.expand_occupancies.expand_occupancy_loads(member_model, occupancies, distributed_direction_mapping, reaction_type)[source]#

Returns a copy of ‘member_model’ with the “expanded” load components of every occupancy load populated from the ‘occupancies’ dict.

Parameters:
  • member_model (dict) – A member dictionary as output from papermodels The member dictionary may have distributed loads with occupancy values assigned. Loads without an occupancy are copied unchanged.

  • occupancies (dict) –

    a dictionary of load components keyed by occupancy. e.g. {

    ‘occupancy1’: {

    ‘component1’: 1.0, ‘component2’: 0.3, ‘component3’: 1.9,

    }, ‘occupancy2’: {

    ‘component1’: 0.4, ‘component2’: 1.0, ‘component3’: 0.8,

    }

    } Each value (the numbers shown in the above example), should represent a pressure. These values will be multiplied by the ‘applied_area’ key of each load and also multiplied by the start and end magnitude values (in the case of distributed loads).

  • distributed_direction_mapping (dict) – A dictionary mapping the load directions from papermodels (e.g. ‘gravity’) to a list of analysis directions.

  • reaction_type (str) – One of ‘linear’ or ‘point’; used in determining how to treat supplied trib areas which represent different things based on the reaction_type.

Return type:

dict

loadtracer.expand_occupancies.expand_distributed_load(dist_load, occupancy, analysis_dir, length, reaction_type)[source]#

Returns ‘dist_load’ with a load component added to its “expanded” list for each load case in ‘occupancy’. ‘dist_load’ is mutated.

Parameters:
Return type:

dict

loadtracer.expand_reactions#

loadtracer.expand_reactions.expand_reaction_loads(member_model, member_reactions, distributed_direction_mapping, point_direction_mapping, missing_reactions='raise', exclude_members=None)[source]#

Returns a tuple of a copy of ‘member_model’ with the “expanded” load components of every transfer load populated from the reactions of its source member, and a dict of the reactions that were ignored (keyed by the tag of ‘member_model’, only populated when ‘missing_reactions’ is “ignore”).

Loads without a ‘transfer_source’ (e.g. occupancy loads) are copied unchanged.

Parameters:
  • member_model (dict) – A member dictionary as output from papermodels

  • member_reactions (dict) –

    a dictionary of the reactions of the members analysed so far, keyed by member tag and then by support index. The support indexes match the ‘transfer_reaction_index’ of the transfer loads. Reactions are the forces on the supports so the load components have the opposite sign. e.g. {

    ‘member_name’: {

    0: {‘FY’: {‘D’: -12.3, ‘L’: -14.5}, ‘FX’: {…}, …}, 1: {‘FY’: {‘D’: -43.2, ‘L’: -34.2}, ‘FX’: {…}, …},

    }, ‘wall_name’: {

    0: {‘FY’: {‘D’: [dist_load, …], ‘L’: [dist_load, …]}},

    },

    } A support with no reaction in a direction or load case (e.g. a member with no loads) contributes no load components.

  • distributed_direction_mapping (dict)

  • point_direction_mapping (dict)

  • missing_reactions (str)

  • exclude_members (list[str] | None)

Return type:

tuple[dict, dict]

‘distributed_direction_mapping’ / ‘point_direction_mapping’:

A dictionary mapping the names of the load directions from papermodels (i.e. ‘gravity’, ‘inplane’, ‘out_of_plane’) to the list of reaction directions to apply from the source reactions. e.g. {‘gravity’: [“FY”]} or {‘gravity’: [“FY”, “MZ”]}

Parameters:
  • missing_reactions (str) – one of either ‘raise’ or ‘ignore’. The reactions of a source member are missing when the source was not analysed (or failed) or does not have the ‘transfer_reaction_index’.

  • exclude_members (list[str] | None) – Transfer loads from these members (or from the sub-elements of these members, e.g. “J1.0-3” for “J1.0”) are removed.

  • member_model (dict)

  • member_reactions (dict)

  • distributed_direction_mapping (dict)

  • point_direction_mapping (dict)

Return type:

tuple[dict, dict]

loadtracer.expand_reactions.is_excluded(transfer_source, exclude_members)[source]#

Returns True if ‘transfer_source’ or its parent element (the part of a sub-element tag before the last “-”, e.g. “J1.0” of “J1.0-3”) is in ‘exclude_members’.

Parameters:
  • transfer_source (str)

  • exclude_members (set[str])

Return type:

bool

loadtracer.expand_reactions.get_indexed_reaction(member_reactions, transfer_source, transfer_index)[source]#

Returns the reactions of ‘transfer_source’ at ‘transfer_index’ or None if they are not available. A missing ‘transfer_index’ (”” or None) means the first reaction.

Parameters:
  • member_reactions (dict)

  • transfer_source (str)

Return type:

dict | None

loadtracer.expand_reactions.expand_point_reactions(point_load, load_cases, analysis_dir, tag)[source]#

Returns a list of point load components (one per load case) at the location of ‘point_load’ for the reactions in ‘load_cases’ ({case: reaction}).

Example output:

[
    {"magnitude": 12.3, "location": 2.5, "case": "D", "dir": "FY"},
    {"magnitude": 14.5, "location": 2.5, "case": "L", "dir": "FY"},
]
Parameters:
Return type:

list[dict]

loadtracer.expand_reactions.expand_distributed_reactions(dist_load, load_cases, analysis_dir, tag)[source]#

Returns a list of distributed load components for the reactions in ‘load_cases’ ({case: reaction}) applied over the extents of ‘dist_load’.

A reaction from a frame member with a linear reaction (a force per unit length) is applied over the extents of ‘dist_load’, scaled by its magnitudes. A reaction from a wall (a list of distributed loads along the wall) is mapped onto the member with map_wall_reaction.

Example output:

[
    {
        "start_magnitude": 1.2,
        "end_magnitude": 1.2,
        "start_loc": 0.0,
        "end_loc": 4.5,
        "case": "D",
        "dir": "FY",
    },
    ...
]
Parameters:
Return type:

list[dict]

loadtracer.expand_reactions.map_wall_reaction(dist_load, wall_reaction, case_name, analysis_dir)[source]#

Returns the load components on the receiving member from the distributed reactions of a wall (‘wall_reaction’, in the wall’s local coordinates).

The ‘transfer_source_start_extent’ to ‘transfer_source_end_extent’ of ‘dist_load’ (the part of the wall that is over the receiving member) is mapped onto its ‘start_loc’ to ‘end_loc’. When the two lengths differ (e.g. from rounding in the drawing) the magnitudes are scaled so that the force is unchanged.

The reaction taken from the wall is the part within the ‘transfer_source_cell’ of ‘dist_load’ (see assign_wall_transfer_cells) so that the parts of the wall that are not over any member (e.g. a wall that is longer than the wall below it) are not lost. Those parts map to just beyond ‘start_loc’/’end_loc’. When ‘dist_load’ has no source extents, the whole wall bears on the member starting at ‘start_loc’.

Parameters:
Return type:

list[dict]

loadtracer.expand_reactions.assign_wall_transfer_cells(papermodels)[source]#

Returns a dict of issues keyed by the tag of the wall they concern. Mutates the distributed loads in ‘papermodels’ ({tag: papermodel}) whose transfer source is a wall by adding a “transfer_source_cell”: [start, end].

The length of each wall is divided into one cell per load that it transfers (in the order of the loads’ source extents): the first cell has no start, the last cell has no end and the gap between two adjacent source extents is divided at its midpoint. The whole reaction of the wall is then transferred exactly once.

Source extents that overlap mean that more than one member supports the same part of the wall. How the load divides between them depends on their stiffness, which is not known, so the overlapping part is transferred to each of them and an issue is reported.

Parameters:

papermodels (dict[str, dict])

Return type:

dict[str, list[str]]

loadtracer.joist_load_conversion#

Conversion of joist point loads into distributed loads.

A joist array from papermodels (e.g. “J1.0”) is analysed as one member per joist (“J1.0-0”, “J1.0-1”, …), so the members that support the array receive one point load per joist. That is the most accurate representation, but it is hard to compare against hand calculations, which use a line load (or an occupancy over a trib width). This module replaces the point loads of each joist array on a member with an equivalent distributed load.

For each run of joists and each load case, the joist reactions P_i are divided by the width that each joist carries (half the distance to its neighbours) to get line load intensities w_i. The profile of the intensities along the run decides the shape of the distributed load:

  • “uniform”: a uniform load (UDL)

  • “increasing” / “decreasing”: a trapezoidal load fitted through the intensities

  • “variable”: a UDL, with a JoistLoadVariabilityWarning when the largest

    intensity is ‘variability_ratio’ (default 2) or more times the smallest

In every case the total of the distributed load equals the total of the joist reactions it replaces.

exception loadtracer.joist_load_conversion.JoistLoadVariabilityWarning[source]#

Bases: LoadTracerWarning

The joist reactions of an array vary so much that the uniform load that replaces them may not be representative.

exception loadtracer.joist_load_conversion.JoistTribWidthWarning[source]#

Bases: LoadTracerWarning

The trib width view of a converted joist load is approximate (the joists carry more than one occupancy or loads other than occupancy loads).

loadtracer.joist_load_conversion.split_joist_tag(tag)[source]#

Returns (parent, index) for the tag of a joist in a joist array (e.g. “J1.0-3” -> (“J1.0”, 3)) or None if ‘tag’ is not a joist tag.

Parameters:

tag (str)

Return type:

tuple[str, int] | None

loadtracer.joist_load_conversion.group_joist_point_loads(point_loads, gap_factor=1.5, min_joists=2)[source]#

Returns runs of point loads from joist arrays. Each run is a list of point loads from the same joist array (and in the same direction), sorted by location, with no gap between neighbours of more than ‘gap_factor’ times the median joist spacing of the array. Runs with fewer than ‘min_joists’ point loads are not returned.

Joists at the same location are left as point loads by convert_joist_point_loads. Only point loads that have been expanded (their source reactions were found) are included. Point loads from members that are not joists are ignored.

Parameters:
Return type:

list[list[dict]]

loadtracer.joist_load_conversion.trib_widths(locations)[source]#

Returns the width carried by each joist at ‘locations’ (sorted): half of the distance to each neighbour. The widths add up to the length of the run.

Parameters:

locations (list[float])

Return type:

list[float]

loadtracer.joist_load_conversion.classify_profile(intensities, rel_tol=0.1)[source]#

Returns one of “uniform”, “increasing”, “decreasing” or “variable” for the line load ‘intensities’ along a joist run (in order of location).

‘rel_tol’ is relative to the load: the intensities are divided by their mean magnitude before they are compared, so the result does not depend on the units or the size of the load.

  • “uniform”: the normalized intensities vary by no more than ‘rel_tol’

  • “increasing”: no step down of more than ‘rel_tol’ and a rise of more than

    ‘rel_tol’ from the first to the last

  • “decreasing”: the mirror of “increasing”

  • “variable”: anything else, including loads that change sign

Parameters:
Return type:

str

loadtracer.joist_load_conversion.max_min_ratio(intensities)[source]#

Returns the ratio of the largest to the smallest intensity magnitude (infinity when the smallest is zero, 1.0 when all are zero).

Parameters:

intensities (list[float])

Return type:

float

loadtracer.joist_load_conversion.fit_trapezoid(locations, intensities, total)[source]#

Returns the (start, end) intensities of a trapezoidal load over locations[0] to locations[-1] whose resultant is ‘total’. The slope comes from a least squares line through the intensities; both ends are then scaled so that the resultant is exactly ‘total’. An end that would have the opposite sign of ‘total’ is set to zero.

Parameters:
Return type:

tuple[float, float]

loadtracer.joist_load_conversion.convert_joist_point_loads(pm, joist_pms=None, occupancies=None, rel_tol=0.1, variability_ratio=2.0, min_joists=2)[source]#

Returns ‘pm’ (an expanded papermodel) with the point loads from each run of joists (see group_joist_point_loads) replaced by one distributed load. ‘pm’ is mutated. The replaced point loads are kept in pm[“loads”][“converted_point_loads”].

The distributed load has the usual keys of a transfer load (its ‘transfer_source’ is the joist array) and:

“converted_from”: the tags of the joists “trib_view”: {case: {…}} the load as an occupancy over a trib width

(see trib_view)

Each of its “expanded” components also has:

“profile”: the profile of the joist reactions (see classify_profile) “point_load_total”: the total of the joist reactions it replaces “max_min_ratio”: the ratio of the largest to the smallest intensity

Parameters:
  • joist_pms (dict | None) – The expanded papermodels of the joists, keyed by tag. Used for the trib width view.

  • occupancies (dict | None) – The occupancies of the load rundown. Used for the trib width view.

  • rel_tol (float) – The relative tolerance for classify_profile

  • variability_ratio (float) – A “variable” profile whose max/min intensity ratio is this or more raises a JoistLoadVariabilityWarning

  • pm (dict)

  • min_joists (int)

Return type:

dict

loadtracer.joist_load_conversion.trib_view(components, joist_tags, joist_pms, occupancies, tag, parent)[source]#

Returns the converted load ‘components’ as an occupancy pressure over a trib width, keyed by load case:

{case: {“occupancy”, “pressure”, “trib_width_start”, “trib_width_end”,

“approximate”}}

The occupancy is the one with the most applied area on the joists. The trib widths are the line load divided by the pressure of that occupancy. They are exact when the joists carry only that occupancy; otherwise “approximate” is True (and a JoistTribWidthWarning is raised).

Parameters:
Return type:

dict