【OpenFOAMとOpenMoelica連携(3)】chtMultiRegionFoamによるマルチリージョンソルバ
kamakiri
OpenFOAMの境界条件
0/fluid/Uと0/fluid/Tの全文を載せときます。
- 0/UのinletにOpenModelicaで計算した質量流moutを設定
- 0/TのinletにOpenModelicaで計算したToutを設定
マルチリージョンソルバは各領域に対して境界条件を設定する必要があることに注意してください。
0/fluid/U
#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
#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
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
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
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
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行のコメントアウトを外して、
./runMos.sh HVACSystem002.mos
を実行する必要があります。
しかし、これは面倒なので以下のAllrun.runスクリプトではじめの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}")

