mcpField
F
FERS — structural FEM

FERS — structural FEM

リモート
ferscloud.com科学・教育32 日前に更新
リポジトリ公式サイト

FERS — structural FEM について

FERSは、エンジニアやAIエージェント向けのPython SDK、コマンドラインツール、ローカル実行のコンパイル済みソルバーです。3Dフレーム、トラス、板の有限要素解析とEurocode 3の照査に対応し、PythonとJSONでモデルや結果を扱えます。

主な機能

  1. 013Dフレーム、トラス、板の解析
  2. 02Eurocode 3の部材照査
  3. 03コマンドラインでの解析・確認・検証
  4. 04Pythonのモデル・結果API
  5. 05JSONによるモデル・結果の保存
  6. 06結果リクエストの選択

活用シーン

  1. 01構造モデルのローカル解析
  2. 02Eurocode 3による梁の耐力照査
  3. 03ターミナルからモデルを検証
  4. 04解析結果の保存と再読み込み

MCP 対応

FERSはMCPサーバーを提供し、MCPクライアントから構造解析機能を利用できます。READMEには、コーディングエージェント向けのFERS skillも記載されています。

設定

リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。

エンドポイントhttps://ferscloud.com/api/mcp
インストールコマンドをコピー
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。

パッケージ
npm@ferscloud/fers-mcp2.76.0

ツール

詳細

FERS:Python での構造解析

FERS は、エンジニアや AI エージェント向けの構造有限要素解析(3D 骨組み、トラス、プレート、Eurocode 3 の照査)です。これは Python SDK と fers コマンドです。ソルバーはお使いのマシン上で動作するコンパイル済みエンジンで、アカウントもネットワーク通信も不要です。非商用利用で、部材数 100 まで無料で利用できます。

pip install FERS
from fers_core import check_beam

# IPE200 in S235, simply supported over 6 m, 10 kN/m. SI units: m, N, Pa.
# check_beam adds an EN 1993-1-1 member check on a ULS combination of 1.35 x the load.
model = check_beam(6.0, "IPE200", udl=10_000.0)
model.settings.analysis_options.include_member_deflected_shape = True
model.run_analysis()

beam = model.resultsbundle.loadcases["Q"].member_results["1"]
x_frac, (_, dy, _) = beam.member_displacement_peak
ec3 = model.unity_check_results()[0]
print(f"M {beam.local_minimums.mz / 1e3:.1f} kNm, sag {dy * 1e3:.1f} mm at x/L {x_frac:.2f}")
print(f"EC3 utilization {ec3['max_utilization']:.2f}")
M -45.0 kNm, sag -41.8 mm at x/L 0.50
EC3 utilization 2.85

たわみは、曲げによる 41.3 mm(5wL⁴/384EI)に、エンジンが考慮するせん断変形による 0.4 mm を加えた値です。この梁は不合格です。ULS 曲げモーメント 60.75 kNm は曲げ耐力 51.9 kNm を超えており、圧縮フランジを拘束するものがないため、横ねじれ座屈の利用率は 2.85 になります。

  • エージェントとスクリプト: fers solve、fers check、fers validate はモデルの JSON ファイルを受け取り、1 行(または --json)を出力し、結果を使用できるかどうかを終了コードで示します。詳しくはターミナルから、およびコーディングエージェントからをご覧ください。fers skill --install . を実行すると、コーディングエージェントに FERS スキルを提供できます。また、MCP クライアントはFERS MCP サーバーを利用できます。Claude Code では、両方が 1 つのプラグインとして提供されます:/plugin install fers --marketplace Jeroen124/FERS_core。
  • 追加機能: プロット(pip install "FERS[plot]")、独自の寸法からメッシュ生成する断面特性("FERS[sections]")、DXF インポート("FERS[dxf]")があります。"FERS[all]" をインストールすると、0.1.x のすべての機能がインストールされます。カタログの断面(IPE200、HEB300、UB 457x191x67、W14X30、...)には追加機能は不要です。
  • ライセンス: SDK は BSD-3-Clause です。インストールされるエンジンは非商用利用で無料です(ライセンス)。

モデルの解析

モデルを手動で作成する場合は、まず FERS オブジェクトを作成し、次にそのノード、部材、荷重を作成します。FERS を作成すると ID カウンターがリセットされるため、その前に作成したノードは、後で作成したノードと ID が重複します。また、ソルバーは 1 から始まり、欠番のないノード ID しか受け付けません。

結果の読み取り

run_analysis() は結果を model.resultsbundle に格納します。荷重ケースと荷重組み合わせは名前をキーとして格納されます。それぞれの中では、JSON を経由して結果を往復させるため、ノードと部材は ID を文字列としてキーにします。

case = model.resultsbundle.loadcases["Load"]    # or .loadcombinations["ULS 1"]

case.displacement_nodes["5"].dy                  # dx dy dz rx ry rz warp
case.reaction_nodes["1"].nodal_forces.fy         # fx fy fz mx my mz bw
beam = case.member_results["4"]
beam.local_maximums.mz, beam.local_minimums.mz   # envelope along the member
beam.local_start_forces.fy                       # end forces, member axes

start_node_forces、end_node_forces、maximums、minimums はグローバル座標系の値です。local_* の力、section_forces、internal_force_series は部材固有の軸に沿った値で、mz は強軸回りのモーメントです。骨組みのモーメントはローカル座標系の値から読み取ってください。XY 平面外ではグローバル座標系の mz は曲げモーメントではなく、YZ 平面内の骨組みでは 0 と表示されます。

0.1.98 以降、member_results と displacement_nodes はオブジェクトの辞書ではなく、読み取り専用のテーブルになりました。これにより、大きな結果でもメモリ使用量を抑えられます。辞書と同じように、[]、get、in、len、.items()、およびソルバーの順序での反復を使えます。検索のたびに新しいオブジェクトが作られ、その内容は変更できません。

beam.local_maximums.mz = 0.0                     # AttributeError: read-only
editable = case.member_results.copy()            # a plain dict of editable objects
editable["4"].local_maximums.mz = 0.0            # fine

section_forces、internal_force_series、member_displacements はタプルとして返されます。テーブルかどうかを判定するには、dict ではなく isinstance(x, collections.abc.Mapping) を使ってください。reaction_nodes と plate_results は、従来どおり通常の辞書です。

保存と読み込み
from fers_core import FERS

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

FERS.from_json は、ソルバーまたは SDK が書き出したファイルを、エントリごとに読み込みます。一般的な結果を読み込むのに必要なメモリは、ファイルサイズ未満で、最大でもファイルサイズの約 2 倍です。0.1.98 より前は、ファイルサイズの 15~20 倍が必要でした。大きなモデルでは、ソルバー自身にファイルを書き出させてください。そうすれば、結果全体が 1 つの文字列として Python を経由することはありません。

model.run_analysis_to_file("beam_results.json")
solved = FERS.from_json("beam_results.json")

fers_core/examples/809_Reading_Results.py を実行すると、これらすべての処理を最初から最後まで確認できます。

読み込んだモデルを保存すると、ソルバーが読み取るすべての情報が、同じ ID で書き戻されます。これには、ファイルで定義されているものの、まだ何にも使われていない情報(エディターがライブラリに保持している断面など)も含まれます。読み込み後に作成したノード、部材などには、読み込んだモデルで使われていない ID が割り当てられるため、保存時に新しいオブジェクトが読み込み済みのオブジェクトを置き換えることはありません。

読み取った結果のみ(engine 0.2.68)

AnalysisOptions(result_filter=[...]) は、結果ブロック全体を保持するか、除外します。 result_requests ではさらに絞り込めます。各 ResultRequest でブロックを指定し、対象の部材または節点、荷重組み合わせ、成分、および部材端の一方を選択します。

from fers_core import ResultBlock, ResultRequest
from fers_core.builders import check_beam
from fers_core.result_requests import nodes_of
from fers_core.unity_checks import classification

model = check_beam(5.0, "IPE300", udl=10000.0)    # one "Beam", one ULS combination
beams = classification("Beam")
model.settings.analysis_options.result_requests = [
    ResultRequest(ResultBlock.LOCAL_ENVELOPES, members=[beams],
                  limit_state="ULS", components=["fx", "my", "mz"]),
    ResultRequest(ResultBlock.NODE_DISPLACEMENTS, nodes=nodes_of(beams),
                  limit_state="ULS", components=["dy"]),
    ResultRequest(ResultBlock.REACTIONS, limit_state="ULS"),
]
model.run_analysis()

値は同じ member_results、displacement_nodes、reaction_nodes に読み込まれるため、それらを読み取るコードは引き続き動作します。要求していない値はゼロではなく None として読み取られます。 そのため、要求されていない値を読み取るチェックは、ゼロによって通過するのではなく失敗します。

  • members には、ユニティチェックで使うセレクター classification(...)、members([...])、member_sets([...])、all_members() を指定します。複数のセレクターを指定すると、その和集合が対象になります。
  • nodes には nodes([...])、または選択した部材の端節点を対象とする nodes_of(*selectors) を指定します。
  • 荷重組み合わせは、ユニティチェックと同様に limit_state で照合するか、load_combination_ids で照合します。限界状態で選択するには、各 LoadCombination に limit_state を設定してください。ソルバーは situation を読み取りません。
  • limit_state も load_combination_ids も指定しないリクエストは、荷重ケースも対象にします。
  • end="start" または end="end" を指定すると、端部力と端部変位のブロックのうち、一方の端だけが保持されます。
  • ソルバーは、対応できないリクエストを処理開始前に拒否します。これには section_forces、internal_force_series、ブロックに存在しない成分、どの組み合わせにも設定されていない限界状態、および result_filter が同時に設定されている場合が含まれます。

値は model.resultsbundle.selections に格納されます。結果セットのリクエストとグループごとにエントリーが1つあります。各エントリーの values は読み取り専用の numpy 配列で、形状は (result_sets, ids, fields, components) です。各軸はその横に示されています。

sel = model.resultsbundle.selections[0]
sel["group"]                     # "loadcombinations" or "loadcases"
sel["result_sets"], sel["ids"]   # combination names, member or node ids
sel["fields"], sel["components"] # e.g. ["local_maximums", "local_minimums"], ["fx", "my", "mz"]
my = sel["values"][..., sel["components"].index("my")]   # every set, member and field

save_to_json は選択内容をソルバーが出力したとおりに書き込むため、保存した結果を読み込んでも変更されません。fers_core/examples/810_Result_Requests.py では、この一連の処理を実行します。

シザー・ヒンジ:交差する部材上で回転する連続梁(エンジン 0.2.68)

梁の上を通り、それぞれの梁に載るレールは、全長にわたって連続していますが、すべての梁上で自由に回転できます。MemberHinge は部材の一方の端を解放するため、梁上でレールの両端を解放すると、レールは2つのスパンに分かれます。代わりに、レールの部材を専用の部材セットにまとめ、そのセットに ScissorHinge を設定します。リンク部材も追加の節点も必要ありません。

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
  • 適用箇所: セット内の部材がセット外の部材または板と接するすべての節点です。そこで、セット内の部材同士は接合されたまま、解放された軸に沿って、またその軸を中心に節点に対して相対移動します。セットがセット内の部材または支持点とだけ接する箇所では、何も解放されません。そのため、連続梁の部材は専用のセットにまとめる必要があります。すべての部材を含むセットは何とも接しないため、ソルバーはその設定を拒否します。
  • 対象となる節点の指定: MemberSet(scissor_hinge_nodes=[...]) でヒンジを適用する箇所を指定できます。接する部材の一部の上では回転し、残りとは接合されている梁に使用します。
  • 各値: MemberHinge の解放と同じように機能します。None は固定、0.0 は解放を表します。正の値はセットと節点の間にばねを設けます。並進(translational_release_x/_y/_z)では長さあたりの力、回転(rotational_release_x/_y/_z)ではラジアンあたりのモーメントを表します。
  • 軸はグローバル軸です: ヒンジで独自の軸を指定しない限り、グローバル軸が使われます。ScissorHinge(..., axes=ReleaseAxes.user(x=..., y=...)) は、第1軸を x に沿って、第2軸を y に沿って、かつ第1軸に垂直に設定し、第3軸をそれらの外積として設定します。ラックを平面内で回転させる場合に必要な設定です。
  • 解放された節点における支持点と節点荷重: 節点に作用します。つまり、セット外の部材に作用します。節点の変位として報告されるのも、セット外の部材の変位です。その箇所におけるセット自体の動きは、セット内の部材の local_displacement_start_node と local_displacement_end_node に記録されます。
  • 2つのヒンジ付きセットが他の部材なしで交差する場合: それぞれのセットが節点から解放されるため、どちらか一方が解放する軸はすべて、両セット間で自由になります。
  • 部材が1本だけのセット: その部材の両端で、ヒンジの軸を中心とする解放が設定されます。
  • 二次解析: 部材ヒンジと同様に、シザー・ヒンジを含むモデルでコローテーショナル補正が適用されるのは、NonlinearMethod.COROTATIONAL を指定した場合のみです。

支柱の上を滑るレールでは、並進も解放できます。ここでは最初の支柱の上だけを滑るため、引張力は2本目の支柱が分担します。

from fers_core import (
    FERS, DistributedLoad, Material, Member, MemberPointLoad, MemberSet, NodalSupport, Node,
    ScissorHinge, Section,
)

model = FERS()
steel = Material(name="S355", e_mod=210e9, g_mod=81e9, density=7850.0, yield_stress=355e6)
frame = Section(name="frame", material=steel, i_y=4e-6, i_z=4e-6, j=1e-7, area=2e-3)
anchor, top_1, top_2 = Node(0.0, 0.0, 0.0), Node(2.0, 0.0, 0.0), Node(4.0, 0.0, 0.0)
foot_1, foot_2 = Node(2.0, -1.5, 0.0), Node(4.0, -1.5, 0.0)
anchor.nodal_support = foot_1.nodal_support = foot_2.nodal_support = NodalSupport()

rail_1 = Member(start_node=anchor, end_node=top_1, section=frame)
rail_2 = Member(start_node=top_1, end_node=top_2, section=frame)
model.add_member_set(MemberSet(
    members=[rail_1, rail_2],
    scissor_hinge=ScissorHinge(translational_release_x=0.0),  # slides along X
    scissor_hinge_nodes=[top_1],                               # over the first post only
))
for foot, top in ((foot_1, top_1), (foot_2, top_2)):
    model.add_member_set(MemberSet(members=[Member(start_node=foot, end_node=top, section=frame)]))

pull = model.create_load_case(name="pull")
DistributedLoad(member=rail_1, load_case=pull, magnitude=2000.0, end_magnitude=2000.0, direction=(0, -1, 0))
MemberPointLoad(member=rail_2, load_case=pull, position=0.5, magnitude=1000.0, direction=(1, 0, 0))
model.run_analysis()
reactions = model.resultsbundle.loadcases["pull"].reaction_nodes
takes = [abs(reactions[str(foot.id)].nodal_forces.fx) for foot in (foot_1, foot_2)]
print(f"post 1 takes {takes[0]:.1f} N, post 2 {takes[1]:.1f} N")  # 0.0 N and 67.2 N

シザー・ヒンジを使用するモデルは schema_version 3 で書き出されるため、0.2.68 より古いソルバーでは、すべての接続を剛接合として解析するのではなく、モデルの読み込みが拒否されます。シザー・ヒンジを使用しない場合、モデルはこれまでとまったく同じ形式で書き出されます。 fers_core/examples/054_Scissor_Hinge_Rail_On_Beam.py では、剛接合、部材ヒンジ、シザー・ヒンジ、ばねを比較します。また、fers_core/examples/055_Scissor_Hinge_Sliding_Rail_And_Release_Axes.py では、レールを滑らせ、ヒンジを1本の支柱に限定し、ラックを平面内で回転させます。

グローバル軸またはユーザー定義軸での部材ヒンジ(エンジン 0.2.68)

MemberHinge は、部材自身の軸を中心とする回転を解放します。平面内で斜めになっており、そこから吊られた桁を中心にピン接合する梁では、桁の軸を中心とするピンが必要です。これは梁自身の軸では、曲げとねじりが混ざる軸になります。rotation_axes では、回転解放、剛性曲線、およびモーメント–回転図に使用する軸を指定します。

from fers_core import MemberHinge, ReleaseAxes

pin_about_x = MemberHinge(rotational_release_mx=0.0, rotation_axes=ReleaseAxes.GLOBAL)

ReleaseAxes.LOCAL(デフォルト)では部材の軸が使われます。ReleaseAxes.user(x, y) では、上記のように座標系を指定します。このようなヒンジに設定した曲線は、同じ軸を中心とする端部モーメントを参照します。部材の結果は引き続きローカル軸で示されます。並進解放は部材のローカル軸のままです。部材ヒンジが別の軸で解放されるモデルも、schema_version 3 で書き出されます。解放軸は空間内に固定されるため、コローテーショナル解析でも構造とともには回転しません。

部材の自重

ソルバーは各部材の自重、密度·面積·g を計算し、self_weight_load_case_id が指定する荷重ケースに加えます。荷重の組み合わせに自重が含まれるのは、そのケースが組み合わせに含まれている場合だけなので、永久荷重ケースを指定してください。create_load_case("G", self_weight=True) は G を作成し、両方のオプションを設定します。

from fers_core.builders import create_beam

model = create_beam(6.0, "IPE300", udl=5_000.0, load_case_name="Q")   # 5 kN/m imposed load
imposed = model.load_cases[0]
dead = model.create_load_case("G", self_weight=True)                   # G takes the self-weight
model.create_load_combination("ULS", {dead: 1.35, imposed: 1.5}, limit_state="ULS")
model.run_analysis()

uls = model.resultsbundle.loadcombinations["ULS"]
print(round(sum(r.nodal_forces.fy for r in uls.reaction_nodes.values()), 1))   # 48357.4

ULS の反力は、1.5 × 30 kN に、梁の 2.49 kN の 1.35 倍を加えた値です。enable_self_weight が設定されていて self_weight_load_case_id が指定されていない場合、ソルバーは「Self-weight」という独立した荷重ケースに自重を加えます。ユーザーの荷重ケースから作成された組み合わせには、このケースは含まれません。組み合わせを含むモデルの場合、run_analysis は自重なしですべての組み合わせを計算することを避けるため、モデルの解析を拒否し、各組み合わせについて self_weight_not_in_combinations 警告を出します。組み合わせのないモデルは解析でき、「Self-weight」ケースは以下のように独立した結果になります。

ソルバーは断面から、モード解析や地震解析に使う各部材の質量も計算します。外装材や設備配管など、より大きな荷重を担う部材は、両方の値を上書きできます。

from fers_core.builders import create_beam

model = create_beam(5.0, "IPE180")                  # no load but its own weight
model.settings.analysis_options.enable_self_weight = True
for member in model.members:
    member.weight_override = 1500.0                 # N/m, gravity included
model.run_analysis()

self_weight = model.resultsbundle.loadcases["Self-weight"]
print(round(sum(r.nodal_forces.fy for r in self_weight.reaction_nodes.values()), 1))   # 7500.0

ソルバーは断面の 184 N/m の代わりに 1500 N/m を適用し、部材の質量を 1500 / 9.81 kg/m として扱います。weight_override の単位はモデルの力/長さの単位です。Member.weight は別の値で、SDK が計算する部材の質量(密度·面積·長さ)を表し、ソルバーに送信されることはありません。

地震解析

add_seismic_analysis はソルバーに、モード応答スペクトル解析(デフォルト)、水平力法、またはその両方を実行するよう指示します。スペクトルヘルパーは EN 1998-1 の設計スペクトルを作成します。モデルの単位にかかわらず、加速度の単位は m/s²、周期の単位は秒です。節点質量の単位は kg です。

from fers_core import (
    FERS, Member, MemberSet, MaterialLibrary, NodalMass, NodalSupport, Node, Section,
    SeismicAnalysisSettings,
)

model = FERS()                                                    # first: it restarts the ids
steel = MaterialLibrary.S235()
column = Section.from_name("HEB200", steel)
nodes = [Node(0.0, float(height), 0.0) for height in range(7)]    # a 6 m mast
nodes[0].nodal_support = NodalSupport()                           # fixed base
model.add_member_set(MemberSet(members=[Member(a, b, section=column) for a, b in zip(nodes, nodes[1:])]))
model.add_nodal_mass(NodalMass(nodes[-1], 2000.0))                # 2 t of equipment on top

model.add_seismic_analysis(
    SeismicAnalysisSettings.eurocode_spectrum(ag=2.5, ground_type="C", q=1.5),
    directions=["X"],
    num_modes=4,
)
model.run_analysis()

x = model.resultsbundle.seismic["modal_response_spectrum"]["per_direction"][0]
print(f"{x['participating_mass_ratio']:.0%} of the mass, base shear {x['base_shear'] / 1000:.1f} kN")

mass_sources=[(load_case, psi)] は、重力荷重ケースを地震質量に変換します(永久荷重の場合は ψE = 1.0)。direct_spectrum は、国別付属書の S、T_B、T_C、T_D を直接受け取り、custom_spectrum は周期と加速度のテーブルを受け取ります。結果は resultsbundle.seismic に格納された通常の dict で、各手法につき 1 つのエントリーがあります。fers_core/examples/151_Seismic_Response_Spectrum.py では、ラーメン構造を両方の手法で解析します。

利用率チェックレポート

ソルバーは、すべての利用率チェックをまとめた HTML レポートを 1 つ、結果に埋め込めます。

from fers_core.builders import check_beam

model = check_beam(5.0, "IPE300", udl=10000.0)
model.settings.analysis_options.include_report_html = True
model.run_analysis()
html = model.unity_report_html()                    # a complete HTML page

render_unity_reports = True を設定すると、チェック対象の各部材について、チェックの report_template から rendered_report も生成され、これらの説明文が HTML レポートに追加されます。どちらもデフォルトでは無効になっており、結果のサイズを小さく保てます。

有料プランでの解析とタイムアウト

API キーがない場合、ソルバーは無料プランの上限(部材数 100)で実行され、ネットワーク呼び出しは一切行いません。無料プランは非商用利用向けです。商用利用には、モデルのサイズにかかわらず Pro キーが必要です。api_key を渡すと、これらの上限が引き上げられます。

model.run_analysis(api_key=KEY)

キーを指定すると、各解析の前に ferscloud.com でライセンスを確認するため、このハンドシェイクにはタイムアウトが設定されます。ライセンスサーバーが license_timeout 秒(デフォルトは 30 秒)以内に応答しない場合、解析の開始前に呼び出しが FersTimeoutError を送出します。そのため、常に安全に再試行できます。

import fers_calculations

try:
    model.run_analysis(api_key=KEY, license_timeout=60)
except fers_calculations.FersTimeoutError:
    ...   # transient stall — retry or skip this model
except RuntimeError:
    ...   # definitive — bad key, non-premium account, or a rejected model

2 つの処理を分けることが重要です。RuntimeError を再試行しても同じように失敗しますが、両方を except Exception にまとめると、再試行可能な一時的なエラーが結果の消失につながります。無人バッチ処理の全体的なパターンについては、fers_core/examples/203_Premium_Batch_Solve.py を参照してください。

FersTimeoutError は組み込みの TimeoutError をサブクラス化しているため、通常の except TimeoutError でも捕捉できます。

呼び出し箇所を編集せずにデフォルトを変更するには、FERS_LICENSE_TIMEOUT(秒)を設定してください。実際の環境変数である必要があります。エンジンは .env ファイルを読み込みません。

license_timeout が制限するのは、解析ではなくライセンスのハンドシェイクです。解析はキャンセルポイントのないネイティブコードで実行されるため、プロセス内のタイムアウトでは中断できません。モデルごとの合計実時間に上限を設けるには、各解析を終了可能なサブプロセスで実行してください。これにより、そのメモリも解放できます。

エンジン fers_calculations >= 0.2.53 が必要です。

ターミナルおよびコーディングエージェントから

pip install FERS を実行すると、fers コマンドがパスに追加されます。これは、上記のオブジェクトとエンジンをシェル、スクリプト、CI ジョブ、コーディングエージェントから使うための薄いレイヤーです。大きな出力はすべてファイルに保存され、標準出力にはコマンドごとに 1 行、または --json を指定した場合は JSON オブジェクトが出力されます。

fers validate model.json                 # the input schema: the failing field, and keys the engine ignores
fers solve model.json --out results.json # the engine streams the results to the file
fers check model.json                    # solve, print each unity check's utilization and a verdict
fers report results.json --out report.html
fers schema                              # the schema's definitions; `fers schema Member` prints one
fers sections get IPE200                 # catalogue properties in SI; `sections list` names the series
fers materials list
fers examples list                       # the numbered examples; `examples show 001`, `examples run 001`
fers skill --install .                   # the skill for coding agents, into .claude/skills
fers skill --zip fers-skill.zip          # the same skill as a zip to upload to Claude.ai
fers doctor                              # versions, and whether the key is accepted

fers solve はファイルをそのままエンジンに渡し、結果はサマリー行のためだけに読み戻します。サマリーには荷重ケース、組み合わせ、記録された失敗、収束しなかった結果セット、メッセージが含まれます。数百メガバイトを書き出すモデルでも、このプロセスには負荷がかかりません。

終了コードは、数値を使ってよいかどうかを示します。

コード意味
0完了。check の場合は、すべてのチェックに合格
1エンジンが解析を拒否、または失敗
2入力不正:ファイルがない、JSON が無効、モデルがスキーマに適合しない(validate --strict の場合は、スキーマに定義されていないキーも該当)
3ライセンス:必要なキーがない、キーが拒否された、ライセンスのタイムアウト
4解析は完了したが、数値を使用しないでください:結果セットにエラーがある(メカニズムは singular_model)、収束しなかった、または失敗が記録された。理由は出力に示されます。
5check:判定なし:チェックが Indeterminate、未解析の組み合わせが残っている、またはモデルにチェックがない
6check:チェックに不合格(利用率が 1 を超える)

複数の条件に該当する check は、4、6、5 の順で最初に該当する終了コードを返します。--json を指定すると、solve と check は verdict とその reasons を追加します。「DO NOT USE these results」の後には理由が続きます。理由はソルバーの最初のエラー、または荷重ケースや組み合わせが完全に失敗したときに記録したエラーです。fers check は各チェックを支配するものを示します。たとえば、governing member 4 (Column, HEA300) in "ULS 1" (combination 2) のように表示され、--json では同じ情報が governing_entity_label と governing_combination_name として渡されます。

--api-key が指定されていない場合、FERS_API_KEY が Premium 制限用のキーを設定します。キーがなければ、エンジンは上記のとおり Free-tier の制限で実行され、ネットワークへの通信は行いません。代わりに --solve-token(または FERS_SOLVE_TOKEN)を使うと、FERS Cloud の有効期限が短い Premium トークンを指定できます。エンジンはこのトークンをオフラインで検証するため、ferscloud.com に接続できないマシンでも Premium 制限を利用できます。Python では run_analysis(solve_token=...) と指定します。

エージェント向けのスキルには、1 ページに実用的な作業手順がまとめられています。どの経路(MCP ツール、シェルまたはコードサンドボックス、あるいは FERS Studio 用のファイル)を選ぶか、検証、解析、チェック、答えに影響する規約が含まれており、実行可能なモデルは reference/api.md にあります。パッケージには fers_core/skills/fers/ に同梱されています。fers skill --install . はそれを .claude/skills/fers/ にコピーします。この場所は Claude Code が参照します。fers skill --zip fers-skill.zip は Claude.ai へのアップロード用にパッケージ化し、fers skill --path はほかのエージェント用に SKILL.md のパスを示します。MCP クライアントは、代わりに同じエンジンをローカル MCP サーバーとして実行できます(npx -y @ferscloud/fers-mcp。解析はユーザーのマシン上で行われます)。プログラムを起動できないクライアントは、ホスト型サーバー https://ferscloud.com/api/mcp を使えます。このサーバーはブラウザーで解析するためのリンクを返します。各クライアントのセットアップ方法:https://ferscloud.com/mcp。

fers_core/examples/811_Command_Line_Workflow.py で、一連の手順を最初から最後まで実行できます。

ライセンス

このパッケージ FERS は、BSD 3-Clause License(LICENSE)に基づくオープンソースです。

インストールされるソルバーエンジン fers_calculations は、別途ライセンスされています。0.2.70 からは PolyForm Strict License 1.0.0 の下で提供され、学習、教育、研究、趣味のプロジェクトなど、あらゆる非商用目的で無料で利用できます。商用利用には、有料の FERS Cloud plan が必要です。このプランには商用ライセンスが含まれます。

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。