Source code for simulator.tools.tool_bsfc_vs_speed_rc

from __future__ import annotations
from pathlib import Path
from typing import Any, Dict, List
import csv
import json

import plotly.graph_objects as go

from ..core import EngineSimulator

ROOT = Path(__file__).resolve().parents[1]
IN_DIR = ROOT / "in"
OUT_DIR = ROOT / "out"


def _load_base_config(name: str = "sample_si_engine.json") -> Dict[str, Any]:
    path = IN_DIR / name
    with path.open("r", encoding="utf-8") as f:
        cfg = json.load(f)
    print(f"[BSFC rc–N] Loaded base config: {path}")
    return cfg


[docs] def main() -> None: # Pulkrabek-style BSFC vs speed for two compression ratios. # Mimics Fig. 2-12: bsfc vs engine speed for rc = 8 and 10. base_cfg = _load_base_config() speeds = [500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, 7000, 7500, 8000] rc_values = [8.0, 8.5, 9.0, 9.5, 10.0, 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 14.5, 15.0] OUT_DIR.mkdir(parents=True, exist_ok=True) rows: List[List[float]] = [] series: Dict[float, List[Dict[str, float]]] = {rc: [] for rc in rc_values} for rc in rc_values: for N in speeds: cfg = json.loads(json.dumps(base_cfg)) geom = cfg.setdefault("geometry", {}) op = cfg.setdefault("operating", {}) geom["compression_ratio"] = rc op["engine_speed_rpm"] = float(N) op.setdefault("friction_mode", "passenger") sim = EngineSimulator.from_dict(cfg) result = sim.run(cycles=1).to_dict() bsfc = result.get("bsfc_g_per_kWh") bmep = result.get("bmep_bar") if bsfc is None or bmep is None: continue series[rc].append( { "speed_rpm": float(N), "bsfc_g_per_kWh": float(bsfc), "bmep_bar": float(bmep), } ) rows.append([rc, float(N), float(bmep), float(bsfc)]) csv_path = OUT_DIR / "bsfc_vs_speed_rc_table.csv" with csv_path.open("w", newline="", encoding="utf-8") as f: w = csv.writer(f) w.writerow(["compression_ratio", "speed_rpm", "bmep_bar", "bsfc_g_per_kWh"]) for r in rows: w.writerow(r) print(f"[BSFC rc–N] Wrote CSV: {csv_path}") fig = go.Figure() for rc, recs in series.items(): recs_sorted = sorted(recs, key=lambda r: r["speed_rpm"]) N = [r["speed_rpm"] for r in recs_sorted] bsfc = [r["bsfc_g_per_kWh"] for r in recs_sorted] fig.add_trace( go.Scatter( x=N, y=bsfc, mode="lines+markers", name=f"r_c = {rc:g}", ) ) fig.update_layout( title="Brake specific fuel consumption vs speed (Pulkrabek-style)", xaxis_title="Engine speed N [rpm]", yaxis_title="BSFC [g/kWh]", ) html_path = OUT_DIR / "bsfc_vs_speed_rc_plot.html" fig.write_html(str(html_path)) print(f"[BSFC rc–N] Wrote HTML plot: {html_path}")
if __name__ == "__main__": # pragma: no cover main()