calc_kelvin_voigt_force|弾性力+粘性抵抗力を計算(多次元対応フォークトモデル、Python関数)

このPython関数は、
フォークトモデルにもとづき
2つの質点間(質点A⇔質点B)に働く、
「弾性力(ばねの力)」と
「粘性抵抗力」を合わせた力を計算します。

「弾性力(ばねの力)」と「粘性抵抗力」は、並列に繋がれています

質点Bの入力値を省略すれば、
動かない壁や地面との間に働く
粘弾性力として使うこともできます。

1次元の計算だけでなく、
NumPy配列を入力することで
多次元の計算にも自動で対応します。

目次

📋 関数の仕様

⚠️ 単位系について

本関数の入出力は、すべて 一貫した単位系 で入力されることを前提としています。

  • たとえば、SI単位系 [N/m, N/(m/s), m, m/s, N] で入力すれば、出力も [N] で返ります
  • 関数内のコメントでは一例としてSI単位系を記載していますが、単位系が統一されていれば他の単位でも計算できます

▶ 入力

  • ばね定数 (k)
    • 0以上の有限値
    • 多次元の場合は、それぞれの成分に値を入力
  • 粘性係数 (c)
    • 0以上の有限値
    • 多次元の場合は、それぞれの成分に値を入力
  • 質点Aの現在位置 (pos_a)
  • 質点Aの現在速度 (v_a)
  • 質点Bの現在位置 (pos_b)
    • 省略した場合は、基本的に0として計算
      ただし、pos_b_init に0以外の要素があるときは、後述のpos_b_initの項目を参照
    • v_b と両方指定するか両方省略
  • 質点Bの現在速度 (v_b)
    • 省略した場合は、0として計算
    • pos_b と両方指定するか両方省略
  • 質点Aの初期位置 (pos_a_init)
    • 省略した場合は、0として計算
  • 質点Bの初期位置 (pos_b_init)
    • 省略した場合は、0として計算
    • pos_b, v_bを省略してpos_b_initのみ指定した場合、
      質点Bをpos_b_initの位置に固定された壁とみなして計算
      このとき、pos_b_initに0以外の要素があるときは警告を出力
  • 質点Aがばねから受ける初期荷重 (f_init)
    • 省略した場合は、0として計算

▶ 出力

入力した質点Aの現在位置(pos_a)と同じデータ形式(数値、1次元のNumPy配列)で出力します。

  • 質点Aに働く粘弾性力 (f_a)
  • 質点Bに働く粘弾性力 (f_b)
    • f_a と大きさが同じで、向きが逆(作用・反作用)

▶ 計算方法

  1. 各質点の変位(現在位置−初期位置)(現在位置 − 初期位置)を計算
  2. 各質点の変位差(質点Aの変位−質点Bの変位)(質点Aの変位 − 質点Bの変位)を計算
  3. ばねの力(fk=ばね定数(k)×変位差−初期荷重(f_init))(f_k = ばね定数(k)× 変位差 − 初期荷重(f\_init))を計算
  4. 各質点の相対速度(質点Aの速度−質点Bの速度)(質点Aの速度 − 質点Bの速度)を計算
  5. 粘性抵抗力(fc=粘性係数(c)×相対速度)(f_c = 粘性係数(c)× 相対速度)を計算
  6. 質点Aに働く力としてf_a=−(fk+fc)f\_a=-(f_k + f_c)、質点Bに働く力としてf_b=(fk+fc)f\_b=(f_k + f_c)を出力

具体的な計算方法は、
次のダウンロードエリアで
関数をご確認ください。

📥 ダウンロード

ご自身のコードに組み込む場合は、
以下の関数をコピーしてご使用ください。

⚠️ ご利用にあたって

本関数の利用・改変・再配布条件、および免責事項については、プライバシーポリシー・免責事項 をご確認ください。

▶ 計算関数

この関数は、次の2記事で公開している関数を使って計算します。

各記事の「▶ Python関数+使用例」からzipファイルを入手して展開し、pyファイルを本関数と同じフォルダに置いてください。

入力値が数値・1次元のNumPy配列の場合は、次の関数と上記の2関数があれば動作します。

  • 入力値がリスト・タプルの場合やエラーチェックを追加したい場合は、後述の内部関数もコピーしてご使用ください。

インポートするライブラリ

# NumPy配列(ndarray)を入力する場合
import numpy as np                                # 配列計算に使用するライブラリ

# 警告を出力する場合
import warnings                                   # 警告の表示に使用する標準ライブラリ

# 別のpyファイルからインポートする関数
from stiffness_force import calc_stiffness_force  # ばね成分の計算に使用する関数
from viscous_drag import calc_viscous_drag        # 粘性成分の計算に使用する関数
  • 「別のpyファイルからインポートする関数」を、次の関数と同じファイルにコピーして使うこともできます。
    この場合、from ... import ... の2行は不要です

関数

def calc_kelvin_voigt_force(k, c, pos_a, v_a, pos_b=None, v_b=None,
                            pos_a_init=None, pos_b_init=None, f_init=None):

    """
    フォークトモデル(ばねと粘性の並列接続)に基づく粘弾性力の計算(多次元対応)
    ばね成分は calc_stiffness_force、粘性成分は calc_viscous_drag を用いて計算する
    Args:
        k          : float | tuple[float] | list[float] | ndarray         ばね定数 [N/m] (>= 0, 有限値)
        c          : float | tuple[float] | list[float] | ndarray         粘性係数 [N・s/m] (>= 0, 有限値)
        pos_a      : float | tuple[float] | list[float] | ndarray         質点Aの現在位置 [m](有限値)
        v_a        : float | tuple[float] | list[float] | ndarray         質点Aの現在速度 [m/s](有限値)
        pos_b      : float | tuple[float] | list[float] | ndarray | None  質点Bの現在位置 [m](有限値、省略時は固定壁として0。pos_b_initのみ指定した場合はpos_b_initの位置に固定)
        v_b        : float | tuple[float] | list[float] | ndarray | None  質点Bの現在速度 [m/s](有限値、省略時は固定壁として0)
        pos_a_init : float | tuple[float] | list[float] | ndarray | None  質点Aの初期位置 [m](有限値、省略時は0)
        pos_b_init : float | tuple[float] | list[float] | ndarray | None  質点Bの初期位置 [m](有限値、省略時は0)
        f_init     : float | tuple[float] | list[float] | ndarray | None  質点Aがばねから受ける初期荷重 [N](有限値、省略時は0)
        ※ ndarray は1次元配列(例:np.array([1.0, 2.0]))で指定してください。
        ※ 各要素が各成分(x, y, z)に対応します。
        ※ 0次元配列(例:np.array(1.0))はスカラーとして扱います。
        ※ 本関数のみを単体で使用する場合は、float または ndarray のみ対応します。
        ※ list・tuple を使う場合やエラーチェックを行う場合は、次の内部関数も必要です。
           ・_to_array_1dsim_com      : 入力値(数値・list・tuple・ndarray)を1次元ndarrayに変換
           ・_format_output_1dsim_com : 計算結果の桁あふれを確認し、基準入力と同じ形式に変換
    Returns:
        f_a : float | tuple[float] | list[float] | ndarray  質点Aに働く粘弾性力 [N]
        f_b : float | tuple[float] | list[float] | ndarray  質点Bに働く粘弾性力 [N]
        ※ いずれも pos_a と同じ形式(float / tuple / list / ndarray)で返します。
    Raises:
        TypeError  : 各引数の要素が実数でない場合、または各引数に数値・list・tuple・ndarray 以外を渡した場合
        ValueError : k, c が0以上の有限値でない場合、各引数が有限値でない場合、各引数に2次元以上の ndarray を渡した場合、
                     各引数の要素数が不一致・空の場合、pos_b と v_b の片方のみを指定した場合、または計算結果が桁あふれした場合
        ※ 値の妥当性チェックは calc_stiffness_force / calc_viscous_drag で行うため、エラーメッセージの先頭にはそれらの関数名が表示されます。
    Warns:
        UserWarning : pos_b, v_b を省略して pos_b_init のみを指定した場合(質点Bを pos_b_init の位置に固定された壁として計算します。pos_b_init がすべて0の場合は出しません)

    ========================================================
    Copyright (c) 2026 1Dsim.com

    本関数の利用・改変・再配布の条件は、
    1Dsim.com の「プライバシーポリシー・免責事項」
    (https://www.1dsim.com/privacy-policy/)に従います。
    このCopyright表記は削除しないでください。
    ========================================================
    """

    # エラーメッセージの先頭に表示する関数名を定義
    func_name = "calc_kelvin_voigt_force"

    # --- 1. 入力値の整合性チェック(pos_b/v_bの指定方法・要素数・固定壁の位置) ---
    # pos_bとv_bの片方だけが省略され、「固定壁なのに動いている」などの矛盾状態になるのを防ぎます。
    if (pos_b is None) != (v_b is None):
        raise ValueError(f"{func_name}: pos_bとv_bは両方指定するか、両方省略する必要があります。")

    # 内部関数(_to_array_1dsim_com)が定義されている場合は、ばね側と粘性側の要素数が一致しているか確認します。
    # (calc_stiffness_force と calc_viscous_drag は、それぞれの引数の中でしか要素数を確認しないため)
    if "_to_array_1dsim_com" in globals():
        dims = {
            "k": len(_to_array_1dsim_com(k, func_name)),
            "c": len(_to_array_1dsim_com(c, func_name)),
            "pos_a": len(_to_array_1dsim_com(pos_a, func_name)),
            "v_a": len(_to_array_1dsim_com(v_a, func_name)),
        }

        # pos_b・v_b・pos_a_init・pos_b_init・f_initは省略可能なため、
        # 指定されているものだけを要素数チェックの対象に加えます。
        if pos_b is not None:
            dims["pos_b"] = len(_to_array_1dsim_com(pos_b, func_name))
            dims["v_b"] = len(_to_array_1dsim_com(v_b, func_name))
        if pos_a_init is not None:
            dims["pos_a_init"] = len(_to_array_1dsim_com(pos_a_init, func_name))
        if pos_b_init is not None:
            dims["pos_b_init"] = len(_to_array_1dsim_com(pos_b_init, func_name))
        if f_init is not None:
            dims["f_init"] = len(_to_array_1dsim_com(f_init, func_name))

        # 各入力の要素数(成分の数)が一致しているか確認します。
        if len(set(dims.values())) != 1:
            raise ValueError(f"{func_name}: 入力値の要素数が一致していません。各引数の要素数: {dims}")

    # pos_b, v_b を省略して pos_b_init のみが指定された場合、そのままでは
    # 「壁が pos_b_init から 0 へ動いた」と扱われ、意図しないばね力が生じます。
    # これを防ぐため、壁は初期位置から動いていない(pos_b = pos_b_init)とみなします。
    is_fixed_wall_at_init = (pos_b is None and pos_b_init is not None)
    if is_fixed_wall_at_init:
        pos_b = pos_b_init

    # --- 2. ばね成分・粘性成分の計算 ---
    # ばね成分:各引数を calc_stiffness_force に渡し、初期位置や初期荷重も含めたばね力を計算します。
    f_a_spring, f_b_spring = calc_stiffness_force(k, pos_a, pos_b, pos_a_init, pos_b_init, f_init)

    # 粘性成分:v_bを省略した場合は、calc_viscous_drag側でも固定壁(速度0)として扱われるため、そのまま渡します。
    f_a_damp, f_b_damp = calc_viscous_drag(c, v_a, v_b)

    # --- 3. ばね成分と粘性成分の合算 ---
    # 内部関数(_to_array_1dsim_com)が定義されている場合は、ばね成分(pos_aの形式)と粘性成分(v_aの形式)を
    # いったん1次元ndarrayにそろえてから足し合わせます。
    if "_to_array_1dsim_com" in globals():
        f_a_spring_calc = _to_array_1dsim_com(f_a_spring, func_name)
        f_b_spring_calc = _to_array_1dsim_com(f_b_spring, func_name)
        f_a_damp_calc   = _to_array_1dsim_com(f_a_damp, func_name)
        f_b_damp_calc   = _to_array_1dsim_com(f_b_damp, func_name)
    else:
        # _to_array_1dsim_com を使用しない場合は、変換を行わず、数値またはndarrayとしてそのまま足し合わせます。
        # (+ 0.0 により、整数型のndarrayが渡された場合でもfloat型に変換し、常に浮動小数点数で計算します)
        f_a_spring_calc = f_a_spring + 0.0
        f_b_spring_calc = f_b_spring + 0.0
        f_a_damp_calc   = f_a_damp + 0.0
        f_b_damp_calc   = f_b_damp + 0.0

    # 質点A・Bに働く粘弾性力(ばね成分 + 粘性成分)を計算します [N]
    # (+ 0.0 により、力が0のとき -0.0 ではなく 0.0 を返します)
    f_a = f_a_spring_calc + f_a_damp_calc + 0.0
    f_b = f_b_spring_calc + f_b_damp_calc + 0.0

    # --- 4. 出力形式の調整 ---
    # 出力形式は pos_a の形式(型)のみで決定します。
    # 内部関数(_format_output_1dsim_com)が定義されている場合は、桁あふれを確認し、pos_a と同じ形式に変換します。
    if "_format_output_1dsim_com" in globals():
        f_a, f_b = _format_output_1dsim_com(pos_a, func_name, f_a, f_b)

    # すべてのエラーチェックが完了した後に、
    # pos_b, v_b を省略して pos_b_init(0以外)のみが指定されていた場合は警告を出します。
    # (pos_b_init がすべて0の場合は置き換えても結果が変わらないため、警告は出しません)
    if "warnings" in globals() and is_fixed_wall_at_init:
        # _to_array_1dsim_com の有無にかかわらず判定できるよう、pos_b_init の各要素をリストに取り出します。
        raw_b_init = pos_b_init.tolist() if hasattr(pos_b_init, "tolist") else pos_b_init
        b_init_vals = raw_b_init if isinstance(raw_b_init, (list, tuple)) else [raw_b_init]
        if any(x != 0 for x in b_init_vals):
            warnings.warn(
                f"{func_name}: pos_b, v_bを省略してpos_b_initのみが指定されたため、"
                "質点Bをpos_b_initの位置に固定された壁(pos_b = pos_b_init)として計算します。",
                stacklevel=2,  # 警告の発生箇所として、この関数の呼び出し元の行を表示します
            )

    # 計算結果を返します(_format_output_1dsim_com を使用しない場合は、数値またはndarrayのまま返ります)。
    return f_a, f_b

▶ 内部関数①

計算関数にリスト・タプルを入力したいときは、
1次元のNumPy配列へ変換するために
この関数をコピーしてご使用ください。

  • 計算関数と同じファイル内にコピーするだけで、計算関数に認識されて使用されます。

インポートするライブラリ

import numbers      # 数値型の判定に使用する標準ライブラリ
import numpy as np  # 配列計算に使用するライブラリ

関数

def _to_array_1dsim_com(val, func_name):
    """入力値(数値・list・tuple・ndarray)を、成分ごとに分けた1次元ndarrayに変換する内部関数"""
    # 例:5.0 → array([5.0])、(1.0, 2.0) → array([1.0, 2.0])、np.array([1.0, 2.0]) → array([1.0, 2.0])
    # 上記以外の型(文字列・集合・辞書・range・pandas.Series など)は受け付けません。
    # (受け付けると出力形式を正しく判定できず、結果の一部が失われるため)

    if isinstance(val, np.ndarray):
        # 2次元以上のndarrayは、各要素を成分(x, y, z)として扱えないため受け付けません。
        if val.ndim > 1:
            raise ValueError(f"{func_name}: ndarrayは1次元配列で指定してください(入力は{val.ndim}次元です)。")
        # 0次元のndarray(np.array(1.0) など)は、中の数値を取り出して要素1つのリストにします。
        if val.ndim == 0:
            raw_list = [val.item()]
        else:
            # 1次元のndarrayは、要素を順に並べたリストに取り出します。
            # (tolist() により、各要素は numpy の数値(np.float64 など)ではなく Python の数値になります)
            raw_list = val.tolist()

    # list・tupleは、要素を順に並べたリストに取り出します。
    elif isinstance(val, (list, tuple)):
        # numpyの数値(np.float64 など)が含まれる場合は、Pythonの数値に変換してから格納します。
        raw_list = [x.item() if getattr(x, "ndim", None) == 0 else x for x in val]

    # 数値(スカラー)は要素1つのリストにします。
    elif isinstance(val, numbers.Number):
        # numpyの数値(np.float64 など)は、Pythonの数値に変換してから格納します。
        if getattr(val, "ndim", None) == 0:
            raw_list = [val.item()]
        else:
            raw_list = [val]

    # 上記のいずれにも当てはまらない型はエラーとします。
    else:
        raise TypeError(f"{func_name}: {type(val).__name__}型は入力できません。数値、list、tuple、ndarrayを使用してください。")

    # 各要素の型(boolなど)を保持したまま要素ごとの四則演算ができるよう、object型の1次元ndarrayとして返します。
    return np.array(raw_list, dtype=object)

▶ 内部関数②

計算結果の桁あふれを確認したり、
リスト・タプルで入力した計算結果を
リスト・タプルで出力したいときは、
この関数をコピーしてご使用ください。

  • 計算関数と同じファイル内にコピーするだけで、計算関数に認識されて使用されます。

インポートするライブラリ

import numpy as np  # 配列計算に使用するライブラリ

関数

def _format_output_1dsim_com(ref_input, func_name, *result_arrays):
    """計算結果の桁あふれを確認し、基準入力(ref_input)と同じ形式に変換して返す内部関数"""
    # _to_array_1dsim_com を使用しない場合(計算結果がスカラー値や0次元ndarrayの場合)でも
    # 同じ処理で変換できるよう、各計算結果を要素のリストに統一します。
    result_lists = []
    for arr in result_arrays:
        raw_vals = arr.tolist() if hasattr(arr, "tolist") else arr
        result_lists.append(raw_vals if isinstance(raw_vals, list) else [raw_vals])

    # 入力値が有限でも、値が極端に大きいと計算の途中で桁あふれ(オーバーフロー)し、
    # 結果が無限大やNaNになるため、エラーとします。
    for vals in result_lists:
        for res_val in vals:
            if not (-float("inf") < res_val < float("inf")):
                raise ValueError(f"{func_name}: 計算結果が桁あふれしました。入力値の大きさを確認してください。")

    # 基準入力がスカラー値(または0次元ndarray)なのに、計算結果が複数要素になっている場合は、
    # 結果の一部が失われてしまうため、エラーとします。
    # (_to_array_1dsim_com を使用しない場合に、スカラー値とndarrayが混在していると、ここで検出されます)
    ref_is_scalar = not isinstance(ref_input, (list, tuple)) and getattr(ref_input, "ndim", 0) != 1
    if ref_is_scalar and any(len(vals) != 1 for vals in result_lists):
        raise ValueError(f"{func_name}: 入力値の要素数が一致していません。")

    # 出力形式は ref_input の形式(型)のみで決定します。
    if isinstance(ref_input, np.ndarray):
        # 0次元のndarrayで入力された場合は、0次元のndarrayで返します。
        if ref_input.ndim == 0:
            res = tuple(np.array(vals[0]) for vals in result_lists)
        else:
            res = tuple(np.array(vals) for vals in result_lists)
    elif isinstance(ref_input, list):
        res = tuple(vals for vals in result_lists)
    elif isinstance(ref_input, tuple):
        res = tuple(tuple(vals) for vals in result_lists)
    else:
        # スカラー値(数値など)で入力された場合
        res = tuple(vals[0] for vals in result_lists)

    return res[0] if len(res) == 1 else res

▶ 使用例

前述の計算関数を使って
動作を確認するためのテストコードです。

1次元も多次元も、
入力の形式に合わせて
正しく計算できていることが分かります。

# ===== 1次元のテスト(スカラー入力) =====
res_1d = calc_kelvin_voigt_force(10.0, 2.0, 0.5, 0.2)
print("1D Result:", res_1d, "\n")

# ===== 多次元のテスト(ndarray入力) =====
res_nd = calc_kelvin_voigt_force(
    np.array([10.0, 20.0]), np.array([1.0, 2.0]),
    np.array([1.0, -1.0]), np.array([0.5, 0.0]),
    np.array([2.0, 0.0]), np.array([-0.5, 0.5])
)
print("ndarray Result:", res_nd)

▶ Python関数+使用例

先ほどの関数を
すぐに実行できるようにまとめました。

よろしければご活用ください!

  • zipに同梱している stiffness_force.py と viscous_drag.py は、更新前のバージョンの可能性があります。
  • 最新版は、calc_stiffness_force と calc_viscous_drag の各記事からご入手ください。

☕ ちょっとした小話

「ばね」と「粘性抵抗」を
並列に繋いだものを
フォークトモデルとよびます。

フォークトモデルは
「ばね」だけのモデルより、
物理モデリングでよく使われます。

「ばね」を使えば、
エネルギーを保持したまま
振動や変形を再現できます。

一方で、
現実の多くの現象は、
振動や変形をするときに
運動エネルギーの一部が
熱などに変わり、
失われていきます。

これを エネルギー散逸 といいます。

また、
「ばね」を使って振動を再現するとき、
計算の誤差が少しずつたまって
異常に大きな振動にならないよう、
微小な粘性抵抗を加える場合もあります。

これを 数値減衰 といいます。

こういった エネルギー散逸 や
数値減衰 を組み込めるよう、
フォークトモデルを使って
骨格を組み立てることが多いです。

いざとなれば、
粘性係数(c)に0(ゼロ)を入れれば、
ただの「ばね」にもできますしね。

このあたりのマニアックなお話は、
またいずれ!

🔗 こんな記事も書いてます

  • ここで紹介した関数を使った記事を作成したら追記する予定です。

最後までお読みいただき
ありがとうございます!🙇‍♂️

少しでも参考になりましたら、
SNSでシェアしていただけると
次の記事を作成する励みになります!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次