Construirea și calculul unui model în Python
Șapte obiecte acoperă aproape orice model: Node, Material, Section, Member, MemberSet, NodalSupport și NodalLoad. Le asamblați, apelați run_analysis() și citiți un pachet de rezultate indexat după ipoteza de încărcare.
Obiectele principale
Totul se importă din fers_core. Un model este o instanță FERS care conține grupuri de bare; încărcările atârnă de ipotezele de încărcare, nu direct de model, astfel încât aceeași geometrie poate purta mai multe ipoteze independente.
- Node — un punct X, Y, Z. Y este verticala în sus. Nodurile poartă condiția lor de rezemare.
- Material — modul de elasticitate, modul de forfecare, densitate, limită de curgere. Totul în SI: pascali și kg/m³.
- Section — aria și momentele de inerție, raportate la un material.
- Member — un element de tip grindă cu două noduri și șase grade de libertate pe nod, raportat la o secțiune.
- MemberSet — grupul pe care îl adăugați modelului și, totodată, unitatea asupra căreia acționează verificările.
- NodalSupport — condiția de rezemare atribuită unui nod.
- NodalLoad — o forță sau un moment aplicat unui nod, în cadrul unei ipoteze de încărcare.
from fers_core import (
FERS, Node, Member, Section, Material,
MemberSet, NodalSupport, NodalLoad
)Definirea geometriei, materialului și secțiunii
Coordonatele sunt în metri, iar proprietățile materialului în pascali. Secțiunea de mai jos este un IPE 180 din biblioteca de profile FERS.
Atenție la convenția de axe, pentru că este de departe cea mai frecventă cauză a unui rezultat greșit: i_z este axa tare, iar i_y cea slabă. O grindă care se încovoaie în planul obișnuit este guvernată de 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²
)Toate convențiile de axe, semne și unități sunt pe pagina de convenții. Contractul JSON așteaptă unghiuri în grade, nu în radiani.
Crearea barei, aplicarea reazemului și a încărcării
Un NodalSupport() fără argumente este încastrare completă. Pentru o articulație sau un reazem mobil, transmiteți explicit displacement_conditions și rotation_conditions — exemplele le arată pe amândouă.
Direcțiile de încărcare sunt vectori unitari globali, deci (0, 1, 0) cu magnitudine negativă este o forță în jos.
# 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))Lansarea calculului și citirea rezultatelor
run_analysis() rezolvă fiecare ipoteză de încărcare. Rezultatele se află în model.resultsbundle, indexate după numele ipotezei, cu deplasările și reacțiunile indexate după identificatorul nodului ca șir de caractere.
Comentariile arată rezultatul real pentru exact aceste date de intrare.
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 NmVerificare manuală: o consolă de 5 m care se încovoaie după axa tare dă δ = PL³ / 3EI_z = 1000·5³ / (3 · 210×10⁹ · 13,17×10⁻⁶) = 15,07 mm, cu o reacțiune de 1 kN și un moment de încastrare de 5 kN·m din echilibru. Solverul returnează 15,065 mm.
Citirea, salvarea și reîncărcarea rezultatelor
Din FERS 0.1.98, member_results și displacement_nodes sunt tabele doar pentru citire. Se citesc ca dicționare — [], get, in, .items() —, dar fiecare accesare construiește un obiect nou, care nu poate fi modificat, iar asta păstrează mic în memorie un rezultat mare. .copy() returnează un dicționar obișnuit de obiecte modificabile.
save_to_json scrie modelul împreună cu rezultatele, iar FERS.from_json citește înapoi orice fișier scris de SDK sau de solver, intrare cu intrare. Pentru un model mare, lăsați solverul să scrie singur fișierul, cu run_analysis_to_file: rezultatul nu mai trece niciodată prin Python ca un singur șir.
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")Cereți doar rezultatele pe care le citiți
result_filter păstrează sau elimină blocuri întregi de rezultate. result_requests (FERS 0.1.99) restrânge un bloc la barele sau nodurile, combinațiile de încărcări, componentele și capătul de bară pe care o verificare le citește efectiv. Valorile ajung în obișnuitele member_results, displacement_nodes și reaction_nodes, iar tot ce nu a fost cerut se citește ca None, astfel încât o verificare nu trece niciodată pe un zero pe care nu l-a calculat nimeni.
Combinațiile de încărcări se selectează după limit_state, la fel ca în verificările de proiectare, deci setați-l pe fiecare combinație. Valorile propriu-zise sunt în resultsbundle.selections: câte un tablou pentru fiecare cerere, de forma (seturi de rezultate, identificatori, câmpuri, componente), cu fiecare axă listată alături.
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)Pe un model de raft cu 4 travei, 8.546 de bare și 31 de combinații de încărcări, fișierul de rezultate a scăzut de la 452 MB la 41 MB, doar cu valorile citite de verificări.
Lăsați o grindă continuă să se rotească pe ce traversează
O șină care trece peste grinzi este continuă pe toată lungimea ei, dar se rotește liber pe fiecare grindă. Un MemberHinge eliberează un capăt de bară, așa că eliberarea ambelor capete ale șinei la o grindă ar împărți șina în două deschideri. Dați barelor șinei un grup de bare propriu cu un ScissorHinge (FERS 0.1.99): grupul rămâne legat în sine și se rotește sau alunecă pe fiecare bară din afara grupului pe care o întâlnește, fără bară de legătură sau nod suplimentar.
Fiecare valoare funcționează ca o eliberare a unei articulații de bară: None blochează axa, 0.0 o eliberează, iar o valoare pozitivă este un resort. Axele sunt globale, cu excepția cazului în care articulația numește un sistem de axe cu ReleaseAxes.user(x, y); scissor_hinge_nodes limitează articulația la unele noduri ale grupului, iar un MemberHinge își poate lua rotirile după axe globale sau proprii cu rotation_axes. Un model care folosește oricare dintre ele este scris cu schema_version 3, astfel încât un solver mai vechi îl refuză în loc să calculeze fiecare îmbinare ca rigidă.
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.6Verificare manuală: o șină continuă pe două deschideri de 1 m, dintre care una încărcată, are deasupra reazemului din mijloc momentul qL²/16 = 1000 · 1² / 16 = 62,5 N·m. Solverul dă 62,4 N·m, grinda cedând puțin sub șină; prinsă rigid de grindă, șina preia 122,6 N·m.
Dincolo de statica liniară
Același obiect model poartă setările pentru celelalte tipuri de analiză. Analiza de ordinul al doilea (P-Delta), flambajul liniar cu factor de încărcare critică, analiza modală și analiza seismică cu spectru de răspuns se conduc din setările de analiză, nu printr-un API diferit.
Verificările se atașează grupurilor de bare și se evaluează după calcul, returnând gradele de utilizare clauză cu clauză împreună cu valorile intermediare care le-au produs — o verificare se citește astfel ca un calcul manual, nu ca un simplu număr.
- Verificați mai întâi față de soluții analitice — vedeți testele de precizie și setul NAFEMS.
- Pentru verificări conform EN 1993-1-1,
check_beamconstruiește, calculează și verifică o deschidere într-un singur apel — vedeți exemplele.
Pagini conexe
Instalarea pachetului Python FERS
Instalare cu pip, verificarea importului și, opțional, autentificarea în cloud cu o cheie API sau un token.
Exemple rezolvate
Patru scripturi executabile — consolă, grindă simplu rezemată, cadru și o verificare EN 1993-1-1 — fiecare cu calculul manual.
FERS din terminal și din agenți de cod
Comanda fers: validați, rezolvați într-un fișier, verificați și inspectați modele dintr-un shell sau un agent de cod, cu un fișier skill pentru bucla de lucru.
Vezi și
Întrebări frecvente
De ce deplasările mele sunt indexate cu un șir de caractere?
displacement_nodes["2"], nu [2].De ce modificarea unui rezultat produce o eroare?
.copy() pe member_results sau displacement_nodes pentru un dicționar obișnuit de obiecte modificabile.La ce folosește un MemberSet?
Cum modelez o articulație sau un reazem mobil?
NodalSupport. O articulație blochează cele trei translații și lasă libere rotirile de încovoiere; blocați și rotirea de torsiune, altfel bara are un mod de corp rigid, iar sistemul devine singular. Exemplul cu grindă simplu rezemată le arată pe amândouă.Care moment de inerție corespunde axei tari?
i_z. FERS tratează axa z locală drept axă tare de încovoiere peste tot — solverul, verificările și diagramele sunt de acord. Inversarea lui i_y cu i_z este cea mai frecventă greșeală de modelare.Am nevoie de conexiune la internet pentru calcul?
run_analysis() rulează solverul compilat în propriul dumneavoastră proces. Rețeaua este folosită doar dacă apelați un punct final din cloud.