OpenFOAMの境界条件
0/fluid/Uと0/fluid/Tの全文を載せときます。
- 0/UのinletにOpenModelicaで計算した質量流moutを設定
- 0/TのinletにOpenModelicaで計算したToutを設定
マルチリージョンソルバは各領域に対して境界条件を設定する必要があることに注意してください。
0/fluid/U
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
#include "<case>/constant/inletTempFile"; dimensions [0 1 -1 0 0 0 0]; internalField uniform (0 0 0); boundaryField { #includeEtc "caseDicts/setConstraintTypes" "f-inlet(XMin|XMax|YMin|YMax)" { type codedFixedValue; name pythonSyncFixedValue; value uniform (0 0 0); // ダミー codeInclude #{ #include "fvCFD.H" #}; code #{ // boundarySyncFile を登録(初回のみ) if (!db().foundObject<IOdictionary>("boundarySyncFile")) { autoPtr<IOdictionary> pythonSyncPtr ( new IOdictionary ( IOobject ( "boundarySyncFile", db().time().constant(), db(), IOobject::MUST_READ, IOobject::NO_WRITE ) ) ); db().store<IOdictionary>(pythonSyncPtr); } IOdictionary& pythonSync = db().lookupObjectRef<IOdictionary>("boundarySyncFile"); pythonSync.regIOobject::read(); // mout [kg/s] const scalar moutSync = readScalar(pythonSync.lookup("mout")); const fvPatch& p = patch(); const scalar A = gSum(p.magSf()); // パッチ全体面積 [m2] const scalar rho = 1.2; // 空気密度(簡易一定) // 法線方向速度 [m/s] (法線が外向きなので、流入にしたいならマイナス) const scalar Un = -moutSync / (rho * A); // 法線ベクトル const vectorField nHat = p.Sf()/p.magSf(); vectorField Ubc(p.size()); forAll(Ubc, faceI) { Ubc[faceI] = Un * nHat[faceI]; } Info<< "pythonSyncFixedValue: time = " << db().time().value() << " patch = " << p.name() << " A = " << A << " [m2]" << " mout = " << moutSync << " [kg/s]" << " Un = " << Un << " [m/s]" << nl << endl; operator==(Ubc); #}; } // f-inletXMin // { // type fixedValue; // value uniform (0 0 -1.0); // } f-outlet { type pressureInletOutletVelocity; value uniform (0 0 0); } // f-inletXMax // { // type fixedValue; // value uniform (0 0 -1.0); // } // f-inletYMax // { // type fixedValue; // value uniform (0 0 -1.0); // } f-walls { type noSlip; } f-inwalls { type noSlip; } fluid_to_solid { type fixedValue; value uniform (0 0 0); } } |
OpenModelicaでは質量流を計算しているため、OpenFOAMの境界条件で流速条件に変えています。
空気の密度$1.2\, [\text{kg}/\text{m}^3]$、 流入面積$0.06\, [text{m}^2]$となっているかを計算中のログで出力するようにしています。
0/fluid/T
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
#include "<case>/constant/inletTempFile"; dimensions [0 0 0 1 0 0 0]; internalField uniform 293.15;//初期値 boundaryField { #includeEtc "caseDicts/setConstraintTypes" "f-inlet(XMin|XMax|YMin|YMax)" { // type fixedValue; // value uniform 303.15; //流入温度(30deg.) type codedFixedValue; name pythonSyncFixedValue; value uniform $Tout; code #{ // pythonSyncファイルをobjectRegistryへ登録(初回のみ) if(!db().foundObject<IOdictionary>("boundarySyncFile")) { autoPtr<IOdictionary> pythonSyncPtr ( new IOdictionary ( IOobject ( "boundarySyncFile", db().time().constant(), db(), IOobject::MUST_READ, IOobject::NO_WRITE ) ) ); db().store<IOdictionary>(pythonSyncPtr); } // objectRegistry上のpythonSyncを呼び出す IOdictionary& pythonSync = db().lookupObjectRef<IOdictionary>("boundarySyncFile"); // 内容の更新 pythonSync.regIOobject::read(); // pythonSyncファイルからUColdSyncの値を取得 const scalar& ToutSync = pythonSync.get<scalar>("Tout"); // fixedValueの値を渡す operator==(ToutSync); #}; } // f-inletXMin // { // type fixedValue; // value uniform 303.15; //流入温度(30deg.) // } f-outlet { type inletOutlet; inletValue $internalField; value $internalField; } // f-inletYMax // { // type fixedValue; // value uniform 303.15; //流入温度(30deg.) // } f-walls { type zeroGradient; //断熱条件 } f-inwalls { type zeroGradient; //断熱条件 } fluid_to_solid { type compressible::turbulentTemperatureRadCoupledMixed; Tnbr T; kappaMethod fluidThermo; value uniform 293.15; } } |
consant/boundarySyncFileファイルに記述されたmoutとToutを計算毎に読み込む記述が書かれています。
あまり重要ではないですが初期条件は#include "<case>/constant/inletTempFile";として別ファイルにしています。
constant/inletTempFile
|
1 2 3 4 5 6 7 8 9 10 11 12 |
FoamFile { version 2.0; format ascii; class dictionary; location constant; object inletTempFile; } mout 0; Tout 293.15 Time 0.0; |
output.dat
output.datはupdateMOS_and_Run.pyの中に書かれたOpenModelicaの結果ファイルです。以下のように書き出されます。
output.dat
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Time mout Tout heatFlowSensor.Q_flow TSensor ramp.y 0.0011999 2.88 293.266 244256 293.15 298 0.00263965 2.88 293.405 243839 293.15 298 0.00436705 2.88 293.572 243327 293.156 298 0.00643992 2.88 293.777 242644 293.196 298 0.00892676 2.88 294.031 241771 293.269 298 0.0118421 2.88 294.327 240852 293.305 298 0.0151831 2.88 294.658 239894 293.302 298 0.0189567 2.88 295.044 238741 293.332 298 0.0231601 2.88 295.496 237376 293.403 298 0.027432 2.88 295.966 235999 293.47 298 0.0317261 2.88 296.442 234644 293.524 298 ...(省略)... |
output.datの出力はupdateMOS_and_Run.pyからimportしているsync_to_table.pyに書かれています。
sync_to_table.py
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
from pathlib import Path from typing import Dict, List, Tuple def _strip_semicolon(s: str) -> str: return s[:-1] if s.endswith(";") else s def parse_boundary_sync(sync_path: Path) -> Dict[str, float]: """ constant/boundarySyncFile を読み込み、{key: value} を返す。 FoamFile ブロックを無視し、 'Key Value;' 形式に対応。 """ vals: Dict[str, float] = {} in_foam_block = False with sync_path.open(encoding="utf-8") as f: for raw in f: line = raw.strip() if not line: continue if line.startswith("FoamFile"): in_foam_block = True continue if in_foam_block: if "}" in line: in_foam_block = False continue line = _strip_semicolon(line) parts = line.split() if len(parts) < 2: continue key, val_str = parts[0], parts[1] try: val: float | str = float(val_str) except ValueError: val = val_str vals[key] = val return vals def _fmt(v): if isinstance(v, (float, int)): return f"{v:g}" return "" if v is None else str(v) def _read_existing_header(path: Path, sep: str) -> List[str] | None: if not path.exists() or path.stat().st_size == 0: return None with path.open("r", encoding="utf-8") as f: first = f.readline().rstrip("\n") return first.split(sep) def _append_missing_columns_in_file(path: Path, sep: str, existing: List[str], target: List[str]) -> None: """ 既存ヘッダ existing に対し、target にあるが existing に無い列を末尾追加。 既存データ行には空欄を付加して列数を合わせる。 """ missing = [c for c in target if c not in existing] if not missing: return new_header = existing + missing # 全行を読み込み with path.open("r", encoding="utf-8") as f: lines = f.readlines() # 先頭行を新ヘッダへ lines[0] = sep.join(new_header) + "\n" # データ行を末尾に空欄を追加 for i in range(1, len(lines)): lines[i] = lines[i].rstrip("\n") + (sep + sep.join([""] * len(missing)) if missing else "") + "\n" with path.open("w", encoding="utf-8") as f: f.writelines(lines) def _ensure_header(out_path: Path, headers: List[str], sep: str) -> List[str]: """ output.dat のヘッダ整合性を保証: - ファイルが無ければ headers を書く - あれば既存ヘッダを読み、不足列を末尾に追加して既存行を補完 - 返り値: 実際のヘッダ(既存+不足列) """ existing = _read_existing_header(out_path, sep) if existing is None: out_path.write_text(sep.join(headers) + "\n", encoding="utf-8") return headers # 既存に不足があれば追加 _append_missing_columns_in_file(out_path, sep, existing, headers) return _read_existing_header(out_path, sep) or headers def append_output_row( base_dir: Path, vals: Dict[str, float], headers: List[str] | None = None, out_name: str = "output.dat", sep: str = "\t", ) -> Tuple[Path, str]: """ vals を out_name に追記。列は headers の順序で出力。 既存ファイルがあれば自動的に不足列を追加し整合させる。 """ if headers is None: headers = ["Time", "mout", "Tout", "pipe.Q_flow"] out_path = base_dir / out_name actual_headers = _ensure_header(out_path, headers, sep) row_vals = [_fmt(vals.get(h, None)) for h in actual_headers] row = sep.join(row_vals) with out_path.open("a", encoding="utf-8") as g: g.write(row + "\n") return out_path, row # ===== 使用例 ===== if __name__ == "__main__": base = Path("/mnt/d/work/openfoam/20251017_OpenCAE2025/heatedRoom") sync_file = base / "constant/boundarySyncFile" vals = parse_boundary_sync(sync_file) # ここで列が増えても OK(自動で不足列を追加し、既存行は空欄で補完) out_path, row = append_output_row( base, vals, headers=["Time", "mout", "Tout", "pipe.Q_flow", "TSensor", "TTarget"], sep="\t" ) print(f"[INFO] Appended to {out_path}: {row}") |
注意
HVACSystem002.mosによる冒頭の2行をコメントアウトしています。
初めてコンパイルする場合は、最初の2行のコメントアウトを外して、
|
1 |
./runMos.sh HVACSystem002.mos |
を実行する必要があります。
しかし、これは面倒なので以下のAllrun.runスクリプトではじめの1回だけコンパイルするようにしました。



