Een model bouwen en rekenen in Python

Zeven objecten dekken vrijwel elk model: Node, Material, Section, Member, MemberSet, NodalSupport en NodalLoad. U stelt ze samen, roept run_analysis() aan en leest een resultatenbundel uit, geordend per belastinggeval.

De kernobjecten

Alles wordt geïmporteerd uit fers_core. Een model is een FERS-instantie met staafgroepen; belastingen hangen aan belastinggevallen en niet rechtstreeks aan het model, zodat dezelfde geometrie meerdere onafhankelijke gevallen kan dragen.

  • Node — een X-, Y-, Z-punt. Y wijst omhoog. Knopen dragen hun oplegvoorwaarde.
  • Material — elasticiteitsmodulus, glijdingsmodulus, dichtheid, vloeigrens. Alles in SI: pascal en kg/m³.
  • Section — oppervlakte en traagheidsmomenten, verwijzend naar een materiaal.
  • Member — een liggerelement met twee knopen en zes vrijheidsgraden per knoop, verwijzend naar een doorsnede.
  • MemberSet — de groep die u aan het model toevoegt, en tevens de eenheid waarop toetsingen werken.
  • NodalSupport — de randvoorwaarde die aan een knoop is toegekend.
  • NodalLoad — een kracht of moment op een knoop binnen een belastinggeval.
from fers_core import (
    FERS, Node, Member, Section, Material,
    MemberSet, NodalSupport, NodalLoad
)

Geometrie, materiaal en doorsnede definiëren

Coördinaten in meters en materiaaleigenschappen in pascal. De doorsnede hieronder is een IPE 180 uit de FERS-profielenbibliotheek.

Let op de assenafspraak, want die is verreweg de meest voorkomende oorzaak van een fout antwoord: i_z is de sterke as en i_y de zwakke. Een ligger die in zijn gebruikelijke vlak buigt wordt bepaald door i_z.

# Nodes (x, y, z coordinates in metres)
node1 = Node(0, 0, 0)
node2 = Node(5, 0, 0)

# Material (Steel S235)
steel = Material(
    name="Steel S235",
    e_mod=210e9,     # Pa
    g_mod=80.769e9,  # Pa
    density=7850,    # kg/m³
    yield_stress=235e6  # Pa
)

# Cross-section (IPE 180), values from the FERS steel section library.
# Axis convention: i_z is the STRONG axis (bending in the local x-y plane),
# i_y the weak axis — see ferscloud.com/conventions
section = Section(
    name="IPE 180",
    material=steel,
    i_y=1.009e-6,  # m⁴ (weak axis)
    i_z=13.17e-6,  # m⁴ (strong axis)
    j=0.0477e-6,   # m⁴
    area=0.00240   # m²
)

Alle assen-, teken- en eenheidsafspraken staan op de conventiepagina. Het JSON-contract verwacht hoeken in graden, niet in radialen.

Staaf maken, oplegging en belasting aanbrengen

Een kale NodalSupport() is volledig ingeklemd. Voor een scharnier of een rol geeft u displacement_conditions en rotation_conditions expliciet mee — de voorbeelden laten beide zien.

Belastingrichtingen zijn globale eenheidsvectoren, dus (0, 1, 0) met een negatieve grootte is een neerwaartse kracht.

# Create beam element
beam = Member(start_node=node1, end_node=node2, section=section)

# Fixed support at node1
node1.nodal_support = NodalSupport()

# Organise members into a set and add to model
model = FERS()
model.add_member_set(MemberSet(members=[beam]))

# Load case with a −1 kN point load at node2
lc = model.create_load_case(name="Gravity")
NodalLoad(node=node2, load_case=lc, magnitude=-1000, direction=(0, 1, 0))

De berekening starten en resultaten lezen

run_analysis() rekent elk belastinggeval. De resultaten staan op model.resultsbundle, geordend op naam van het belastinggeval, met vervormingen en reacties geïndexeerd op knoop-id als tekenreeks.

De opmerkingen tonen de werkelijke uitvoer voor precies deze invoer.

model.run_analysis()

results = model.resultsbundle.loadcases["Gravity"]

dy = results.displacement_nodes["2"].dy
Vy = results.reaction_nodes["1"].nodal_forces.fy
Mz = results.reaction_nodes["1"].nodal_forces.mz

print(f"Tip deflection : {dy*1000:.3f} mm")   # -15.065 mm
print(f"Reaction Vy    : {Vy:.1f} N")         # 1000.0 N
print(f"Reaction Mz    : {Mz:.1f} Nm")        # 5000.0 Nm

Handberekening: een uitkraging van 5 m die om zijn sterke as buigt geeft δ = PL³ / 3EI_z = 1000·5³ / (3 · 210×10⁹ · 13,17×10⁻⁶) = 15,07 mm, met uit evenwicht een reactie van 1 kN en een inklemmingsmoment van 5 kN·m. De solver geeft 15,065 mm.

Resultaten lezen, opslaan en opnieuw laden

Sinds FERS 0.1.98 zijn member_results en displacement_nodes alleen-lezentabellen. Ze lezen als woordenboeken — [], get, in, .items() — maar elke opvraging bouwt een nieuw object dat niet te wijzigen is, en juist dat houdt een groot resultaat klein in het geheugen. .copy() geeft een gewoon woordenboek met wijzigbare objecten terug.

save_to_json schrijft het model met zijn resultaten weg, en FERS.from_json leest elk bestand terug dat de SDK of de solver schreef, item voor item. Laat bij een groot model de solver het bestand zelf schrijven met run_analysis_to_file: het resultaat gaat dan nooit als één tekenreeks door Python.

from fers_core import FERS

results = model.resultsbundle.loadcases["Gravity"]

# A read-only table: every lookup builds a fresh object
beam_result = results.member_results["1"]
print(beam_result.local_start_forces.mz)

editable = results.member_results.copy()   # a plain dict of editable objects
editable["1"].local_start_forces.mz = 0.0

model.save_to_json("cantilever.json")      # the model and its results
reloaded = FERS.from_json("cantilever.json")

# A large model: let the solver write the file itself
model.run_analysis_to_file("cantilever_results.json")
solved = FERS.from_json("cantilever_results.json")

Vraag alleen de resultaten op die u leest

result_filter behoudt of schrapt hele resultaatblokken. result_requests (FERS 0.1.99) beperkt een blok tot de staven of knopen, belastingcombinaties, componenten en het staafeinde die een toets werkelijk leest. De waarden komen in de gebruikelijke member_results, displacement_nodes en reaction_nodes terecht, en alles wat niet is opgevraagd leest als None, zodat een toets nooit slaagt op een nul die niemand heeft berekend.

Belastingcombinaties worden gekozen op hun limit_state, zoals ontwerptoetsen ze kiezen; stel die dus in op elke combinatie. De waarden zelf staan in resultsbundle.selections: één array per opvraging, met de vorm (resultaatsets, id's, velden, componenten) en elke as ernaast vermeld.

from fers_core import ResultBlock, ResultRequest
from fers_core.loads.enums import LimitState
from fers_core.result_requests import nodes_of
from fers_core.unity_checks import members

# Requests select load combinations by limit state, as design checks do
uls = model.create_load_combination("ULS 1", {lc: 1.35}, situation="ULS", check="ALL")
uls.limit_state = LimitState.ULS

beams = members([beam.id])
model.settings.analysis_options.result_requests = [
    ResultRequest(ResultBlock.LOCAL_END_FORCES, members=beams,
                  limit_state="ULS", end="start", components=["fy", "mz"]),
    ResultRequest(ResultBlock.NODE_DISPLACEMENTS, nodes=nodes_of(beams),
                  limit_state="ULS", components=["dy"]),
]
model.run_analysis()

uls_results = model.resultsbundle.loadcombinations["ULS 1"]
start = uls_results.member_results["1"].local_start_forces
print(start.mz)   # requested
print(start.my)   # None: not requested

selection = model.resultsbundle.selections[0]
print(selection["fields"], selection["components"])
print(selection["values"].shape)   # (result sets, ids, fields, components)

Bij een stellingmodel van 4 vakken met 8.546 staven en 31 belastingcombinaties kromp het resultatenbestand van 452 MB tot 41 MB, met alleen de waarden die de toetsen lezen.

Een doorgaande ligger laten draaien op wat hij kruist

Een rail die over liggers loopt, is over zijn lengte doorgaand, maar draait vrij op elke ligger. Een MemberHinge geeft één staafeinde vrij, dus beide railuiteinden bij een ligger vrijgeven zou de rail in twee velden splitsen. Geef de staven van de rail een eigen stavenset met een ScissorHinge (FERS 0.1.99): de set blijft met zichzelf verbonden en draait, of glijdt, op elke staaf buiten de set die hij ontmoet, zonder koppelstaaf of extra knoop.

Elke waarde werkt zoals een vrijgave van een staafscharnier: None houdt de as vast, 0.0 geeft haar vrij en een positieve waarde is een veer. De assen zijn globaal, tenzij het scharnier met ReleaseAxes.user(x, y) een assenstelsel noemt; scissor_hinge_nodes beperkt het scharnier tot enkele knopen van de set, en een MemberHinge kan zijn rotaties met rotation_axes om globale of eigen assen nemen. Een model dat een van deze gebruikt, wordt geschreven met schema_version 3, zodat een oudere solver het weigert in plaats van elke verbinding star te rekenen.

from fers_core import FERS, DistributedLoad, Material, Member, MemberSet, NodalSupport, Node, ScissorHinge, Section
from fers_core.supports.supportcondition import SupportCondition as C

model = FERS()
steel = Material(name="S355", e_mod=210e9, g_mod=81e9, density=7850.0, yield_stress=355e6)
rail_section = Section(name="rail", material=steel, i_y=1e-6, i_z=1e-6, j=1e-7, area=1e-3)
beam_section = Section(name="beam", material=steel, i_y=1e-4, i_z=1e-4, j=1e-4, area=5e-3)

turns = {"X": C.fixed(), "Y": C.free(), "Z": C.free()}
a, m, b = Node(0.0, 0.0, 0.0), Node(1.0, 0.0, 0.0), Node(2.0, 0.0, 0.0)
a.nodal_support = NodalSupport(rotation_conditions=turns)
b.nodal_support = NodalSupport(
    displacement_conditions={"X": C.free(), "Y": C.fixed(), "Z": C.fixed()}, rotation_conditions=turns
)
beam_start, beam_end = Node(1.0, 0.0, -0.5), Node(1.0, 0.0, 0.5)
beam_start.nodal_support = beam_end.nodal_support = NodalSupport()  # clamped

rail_1 = Member(start_node=a, end_node=m, section=rail_section)
rail_2 = Member(start_node=m, end_node=b, section=rail_section)
model.add_member_set(MemberSet(
    members=[rail_1, rail_2],
    scissor_hinge=ScissorHinge(rotational_release_y=0.0, rotational_release_z=0.0),
))
model.add_member_set(MemberSet(members=[
    Member(start_node=beam_start, end_node=m, section=beam_section),
    Member(start_node=m, end_node=beam_end, section=beam_section),
]))

span = model.create_load_case(name="span")
DistributedLoad(member=rail_1, load_case=span, magnitude=1000.0, end_magnitude=1000.0, direction=(0, -1, 0))
model.run_analysis()
mz = model.resultsbundle.loadcases["span"].member_results[str(rail_1.id)].local_end_forces.mz
print(f"{abs(mz):.1f} Nm")  # 62.4; a knife edge gives qL²/16 = 62.5, a rigid joint 122.6

Handberekening: een rail die doorloopt over twee velden van 1 m, waarvan één belast, heeft boven de middensteun een moment qL²/16 = 1000 · 1² / 16 = 62,5 N·m. De solver geeft 62,4 N·m, doordat de ligger onder de rail iets meegeeft; star met de ligger verbonden neemt de rail 122,6 N·m op.

Verder dan lineaire statica

Hetzelfde modelobject draagt de instellingen voor de andere berekeningssoorten. Tweede-orde (P-Delta), lineair knikken met kritieke belastingfactor, modale analyse en responsspectrum-aardbevingsberekening worden allemaal vanuit de analyse-instellingen aangestuurd, niet via een andere API.

Toetsingen hangen aan staafgroepen en worden na de berekening geëvalueerd. Ze geven per artikel de unity check met de tussenwaarden die eraan ten grondslag liggen — een toetsing leest dus als een handberekening en niet als één getal.

Gerelateerde pagina’s

Zie ook

Veelgestelde vragen

Waarom zijn mijn vervormingen op een tekenreeks geïndexeerd?

Resultaatwoordenboeken zijn geïndexeerd op knoop- en staaf-id als tekenreeks, omdat de resultatenbundel door JSON gaat. Dus displacement_nodes["2"], niet [2].

Waarom geeft het wijzigen van een resultaat een fout?

Sinds FERS 0.1.98 zijn resultaten alleen-lezen: elke opvraging bouwt een nieuw object, dus een wijziging zou bij de volgende opvraging verloren gaan. Roep .copy() aan op member_results of displacement_nodes voor een gewoon woordenboek met wijzigbare objecten.

Waar dient een MemberSet voor?

Het is de eenheid die u aan het model toevoegt en de eenheid waarop toetsingen werken. Staven groeperen die één fysiek onderdeel vormen — bijvoorbeeld een kolom die uit twee staven is gelast — laat een kniklengte of een normtoetsing het geheel omvatten.

Hoe modelleer ik een scharnier of een rol?

Geef NodalSupport expliciete voorwaarden mee. Een scharnier houdt de drie verplaatsingen vast en laat de buigrotaties vrij; houd ook de torsierotatie vast, anders heeft de staaf een starre-lichaamsdraaiing en wordt de berekening singulier. Het voorbeeld met de ligger op twee steunpunten toont beide.

Welk traagheidsmoment hoort bij de sterke as?

i_z. FERS behandelt de lokale z-as overal als sterke buigas — solver, toetsingen en diagrammen zijn het daarover eens. Verwisselde i_y en i_z zijn de meest voorkomende modelleerfout.

Heb ik internet nodig om te rekenen?

Nee. run_analysis() draait de gecompileerde solver in uw eigen proces. Netwerktoegang komt alleen in beeld als u een cloudeindpunt aanroept.

Krijg ik de doorbuigingslijn en niet alleen de eindwaarden?

Ja, en die is belastingexact in plaats van geïnterpoleerd tussen de eindvervormingen — een aan beide zijden ingeklemde ligger onder gelijkmatige belasting geeft de werkelijke doorbuiging wL⁴/384EI. Vraag hem aan in de analyse-opties.