Construir y calcular un modelo en Python
Siete objetos cubren casi cualquier modelo: Node, Material, Section, Member, MemberSet, NodalSupport y NodalLoad. Los ensambla, llama a run_analysis() y lee un conjunto de resultados indexado por hipótesis de carga.
Los objetos principales
Todo se importa desde fers_core. Un modelo es una instancia de FERS que contiene grupos de barras; las cargas cuelgan de las hipótesis y no del modelo, de modo que la misma geometría puede soportar varias hipótesis independientes.
- Node — un punto X, Y, Z. Y es la vertical hacia arriba. Los nudos llevan su condición de apoyo.
- Material — módulo de elasticidad, módulo de cortante, densidad y límite elástico. Todo en SI: pascales y kg/m³.
- Section — área y momentos de inercia, referidos a un material.
- Member — un elemento viga de dos nudos con seis grados de libertad por nudo, referido a una sección.
- MemberSet — el grupo que añade al modelo y también la unidad sobre la que actúan las comprobaciones.
- NodalSupport — la condición de contorno asignada a un nudo.
- NodalLoad — una fuerza o un momento aplicado a un nudo dentro de una hipótesis de carga.
from fers_core import (
FERS, Node, Member, Section, Material,
MemberSet, NodalSupport, NodalLoad
)Definir geometría, material y sección
Las coordenadas van en metros y las propiedades del material en pascales. La sección de abajo es un IPE 180 tomado de la biblioteca de perfiles de FERS.
Fíjese en el convenio de ejes, porque es con diferencia la causa más habitual de un resultado erróneo: i_z es el eje fuerte e i_y el débil. Una viga que flecta en su plano habitual se gobierna por 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²
)Todos los convenios de ejes, signos y unidades están en la página de convenios. El contrato JSON espera ángulos en grados, no en radianes.
Crear la barra, aplicar apoyo y carga
Un NodalSupport() sin argumentos está totalmente empotrado. Para una articulación o un apoyo deslizante, pase displacement_conditions y rotation_conditions explícitos — los ejemplos muestran ambos casos.
Las direcciones de carga son vectores unitarios globales, así que (0, 1, 0) con módulo negativo es una fuerza hacia abajo.
# 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))Lanzar el cálculo y leer resultados
run_analysis() resuelve todas las hipótesis. Los resultados quedan en model.resultsbundle, indexados por nombre de hipótesis, con desplazamientos y reacciones indexados por identificador de nudo como cadena.
Los comentarios muestran la salida real para estas entradas exactas.
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 NmComprobación manual: una ménsula de 5 m que flecta respecto a su eje fuerte da δ = PL³ / 3EI_z = 1000·5³ / (3 · 210×10⁹ · 13,17×10⁻⁶) = 15,07 mm, con una reacción de 1 kN y un momento de empotramiento de 5 kN·m por equilibrio. El solver devuelve 15,065 mm.
Leer, guardar y volver a cargar resultados
Desde FERS 0.1.98, member_results y displacement_nodes son tablas de solo lectura. Se leen como diccionarios —[], get, in, .items()—, pero cada consulta construye un objeto nuevo que no se puede modificar, y eso es lo que mantiene pequeño en memoria un resultado grande. .copy() devuelve un diccionario normal de objetos editables.
save_to_json escribe el modelo con sus resultados, y FERS.from_json vuelve a leer cualquier archivo escrito por el SDK o por el solver, entrada a entrada. Con un modelo grande, deje que el solver escriba el archivo con run_analysis_to_file: así el resultado nunca pasa por Python como una sola cadena.
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")Pida solo los resultados que lee
result_filter conserva o descarta bloques enteros de resultados. result_requests (FERS 0.1.99) restringe un bloque a las barras o nudos, combinaciones de carga, componentes y extremo de barra que una comprobación realmente lee. Los valores se cargan en los habituales member_results, displacement_nodes y reaction_nodes, y todo lo no solicitado se lee como None, de modo que una comprobación nunca pasa con un cero que nadie calculó.
Las combinaciones de carga se seleccionan por su limit_state, igual que las comprobaciones de diseño, así que fíjelo en cada combinación. Los valores en sí están en resultsbundle.selections: una matriz por solicitud, con forma (conjuntos de resultados, identificadores, campos, componentes) y cada eje listado al lado.
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)En un modelo de estantería de 4 vanos con 8.546 barras y 31 combinaciones de carga, el archivo de resultados pasó de 452 MB a 41 MB con solo los valores que leen sus comprobaciones.
Dejar que una viga continua gire sobre lo que cruza
Un carril que pasa sobre vigas es continuo en toda su longitud, pero gira libremente sobre cada viga. Un MemberHinge libera un extremo de barra, así que liberar los dos extremos del carril en una viga lo partiría en dos vanos. Da a las barras del carril un grupo de barras propio con una ScissorHinge (FERS 0.1.99): el grupo sigue unido entre sí y gira, o desliza, sobre cada barra ajena al grupo con la que se encuentra, sin barra de enlace ni nudo adicional.
Cada valor funciona como una liberación de articulación de barra: None retiene el eje, 0.0 lo libera y un valor positivo es un muelle. Los ejes son globales salvo que la articulación indique un sistema de ejes con ReleaseAxes.user(x, y); scissor_hinge_nodes limita la articulación a algunos nudos del grupo, y una MemberHinge puede tomar sus giros respecto a ejes globales o propios con rotation_axes. Un modelo que usa cualquiera de ellos se escribe con schema_version 3, para que un solver más antiguo lo rechace en lugar de calcular rígida cada unión.
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.6Comprobación manual: un carril continuo sobre dos vanos de 1 m, con uno de ellos cargado, tiene sobre el apoyo central un momento qL²/16 = 1000 · 1² / 16 = 62,5 N·m. El solver devuelve 62,4 N·m, porque la viga cede un poco bajo el carril; unido rígidamente a la viga, el carril toma 122,6 N·m.
Más allá de la estática lineal
El mismo objeto de modelo lleva la configuración de los demás tipos de análisis. El análisis de segundo orden (P-Delta), el pandeo lineal con factor de carga crítica, el análisis modal y el sísmico por espectro de respuesta se gobiernan desde los ajustes de análisis, no desde una API distinta.
Las comprobaciones se asocian a grupos de barras y se evalúan tras el cálculo, devolviendo aprovechamientos cláusula a cláusula junto con los valores intermedios que los produjeron: una comprobación se lee como un cálculo manual y no como un número suelto.
- Contraste primero con soluciones cerradas — véanse los contrastes de precisión y el conjunto NAFEMS.
- Para comprobaciones según EN 1993-1-1,
check_beamconstruye, calcula y comprueba un vano en una sola llamada — véanse los ejemplos.
Páginas relacionadas
Instalar el paquete Python de FERS
Instalar con pip, verificar la importación y configurar opcionalmente la autenticación en la nube con clave de API o token.
Ejemplos resueltos
Cuatro scripts ejecutables — ménsula, viga biapoyada, pórtico y comprobación según EN 1993-1-1 — cada uno con su cálculo manual.
FERS desde la terminal y desde agentes de código
El comando fers: validar, resolver en un archivo, comprobar e inspeccionar modelos desde una shell o un agente de código, con un archivo skill para el ciclo de trabajo.
Véase también
Preguntas frecuentes
¿Por qué mis desplazamientos están indexados por una cadena?
displacement_nodes["2"], no [2].¿Por qué modificar un resultado lanza un error?
.copy() sobre member_results o displacement_nodes para obtener un diccionario normal de objetos editables.¿Para qué sirve un MemberSet?
¿Cómo modelo una articulación o un apoyo deslizante?
NodalSupport. Una articulación fija las tres traslaciones y deja libres los giros de flexión; retenga además el giro de torsión o la barra tendrá un modo de sólido rígido y el sistema será singular. El ejemplo de viga biapoyada muestra ambos.¿Qué momento de inercia corresponde al eje fuerte?
i_z. FERS trata el eje z local como eje fuerte de flexión en todo el sistema: solver, comprobaciones y diagramas coinciden. Intercambiar i_y e i_z es el error de modelado más común.¿Necesito conexión a Internet para calcular?
run_analysis() ejecuta el solver compilado dentro de su propio proceso. Solo se usa la red si llama a un punto final en la nube.