diff --git a/.forgejo/workflows/deploy-docs.yml b/.forgejo/workflows/deploy-docs.yml new file mode 100644 index 0000000..1e80aec --- /dev/null +++ b/.forgejo/workflows/deploy-docs.yml @@ -0,0 +1,93 @@ +name: Deploy Documentation + +on: + push: + branches: + - main + +jobs: + build-and-deploy: + runs-on: docker # 기본 매핑된 node:20-bookworm 이미지에서 작동 + steps: + - name: 저장소 체크아웃 + uses: actions/checkout@v4 + + # 1. 기존 APT 캐시 복원 (가장 최근 상태 불러오기) + - name: Restore APT Cache + id: restore-apt + uses: actions/cache/restore@v4 + with: + path: /var/cache/apt/archives + key: linux-apt-temp + restore-keys: | + linux-apt- + + # 2. 시스템 패키지 설치 및 구버전 찌꺼기 청소 + - name: Install System Packages + run: | + if [ -f /etc/apt/apt.conf.d/docker-clean ]; then + echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' | tee /etc/apt/apt.conf.d/keep-cache + rm -f /etc/apt/apt.conf.d/docker-clean + fi + + apt-get update + apt-get install -y python3 python3-pip python3-venv graphviz + apt-get autoclean -y + rm -f /var/cache/apt/archives/partial/* + + # 3. .deb 파일명 목록 기반 고유 해시(Hash) 계산 + - name: Calculate APT Cache Hash + id: apt-hash + run: | + DEB_HASH=$(find /var/cache/apt/archives -name "*.deb" -type f -printf "%f\n" | sort | md5sum | awk '{print $1}') + echo "hash=$DEB_HASH" >> $GITHUB_OUTPUT + + # 4. 계산된 해시값을 Key로 캐시 저장 + - name: Save APT Cache + if: steps.restore-apt.outputs.cache-matched-key != format('linux-apt-{0}', steps.apt-hash.outputs.hash) + uses: actions/cache/save@v4 + with: + path: /var/cache/apt/archives + key: linux-apt-${{ steps.apt-hash.outputs.hash }} + + - name: Cache Python Virtual Environment + id: cache-venv + uses: actions/cache@v4 + with: + path: venv + key: linux-venv-${{ hashFiles('requirements.txt', 'requirements-docs.txt') }} + restore-keys: | + linux-venv- + + - name: Install Python Requirements + if: steps.cache-venv.outputs.cache-hit != 'true' + run: | + python3 -m venv venv + . venv/bin/activate + # Install simulation dependencies (needed for mkdocstrings dynamic import) and doc tools + pip install -r requirements.txt + pip install -r requirements-docs.txt + + - name: Compile Docs + run: | + . venv/bin/activate + python3 build_docs.py + + - name: gh-pages 브랜치로 site/ 배포 + env: + DEPLOY_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + ACTOR: ${{ github.actor }} + run: | + cd site + git init -b main + git config user.name "Forgejo Actions Bot" + git config user.email "actions@1gnis-git.duckdns.org" + git add . + git commit -m "Auto-deploy Docs (${GITHUB_SHA::8})" + SERVER_URL="${{ github.server_url }}" + HOST_ADDR="${SERVER_URL#*//}" + PROTO="${SERVER_URL%%//*}" + git push --force \ + "${PROTO}//${ACTOR}:${DEPLOY_TOKEN}@${HOST_ADDR}/${REPO}.git" \ + main:gh-pages diff --git a/.gitignore b/.gitignore index 912664e..04a0ee2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # Jupyter Notebook checkpoints .ipynb_checkpoints +site/ +docs/ # Virtual Environment .venv/ diff --git a/Battery.py b/Battery.py index 5fa10a6..982b132 100644 --- a/Battery.py +++ b/Battery.py @@ -1,3 +1,10 @@ +"""Coke Oven Battery Simulation Module. + +This module models the thermal behavior, heat transfer, and scheduling operations of +a coke oven battery. It implements systems of flues (combustion chambers), refractory +brick walls (solving 1D heat equations), and oven chambers with loaded coal charges. +""" + from functools import reduce import logging @@ -19,7 +26,35 @@ except ImportError: class CombustionChamber: + """Represents a combustion chamber in the coke oven battery. + + This class models the steady-state thermal energy balance of combustion gases + flowing through the heating flues (chambers) adjacent to the ovens. + + Attributes: + mdot (float): Mass flow rate of fuel-air mixture (kg/s). + gas (cantera.Solution): Cantera gas object. + eq_state (tuple): Thermodynamic state (T, P, X) of burned gas. + T0 (float): Adiabatic flame temperature (K). + P0 (float): Operating pressure (Pa). + X0 (dict/array): Fuel-air mole fractions. + h0 (float): Inlet enthalpy of the gas (J/kg). + hA (float): Heat transfer coefficient times area (W/K). + T1 (float): Outlet gas temperature (K). + Twall0 (float): Temperature of the lower wall (K). + Twall1 (float): Temperature of the upper wall (K). + Area (float): Oven cross section area (m^2). + """ + def __init__(self, mdot, ct_object, burned_state, hA=700): + """Initializes the CombustionChamber. + + Args: + mdot (float): Mass flow rate (kg/s). + ct_object (cantera.Solution): Instantiated Cantera Solution object. + burned_state (tuple): State variables (T, P, X) representing burned gas. + hA (float, optional): Heat transfer coefficient * area. Defaults to 700. + """ self.mdot = mdot # kg/s self.gas = ct_object # gas object self.eq_state = burned_state # HP equilibrium state @@ -36,25 +71,47 @@ class CombustionChamber: self.Area = 6.7 * 16.7 def update_mdot(self, mdot_new): + """Updates the mass flow rate if a new value is provided. + + Args: + mdot_new (float): The new mass flow rate (kg/s). + """ if mdot_new: self.mdot = mdot_new def update_Twall(self, Twall0=None, Twall1=None): + """Updates the boundary wall temperatures. + + Args: + Twall0 (float, optional): Lower wall temperature (K). + Twall1 (float, optional): Upper wall temperature (K). + """ if Twall0: self.Twall0 = Twall0 if Twall1: self.Twall1 = Twall1 def energy_balance_equation(self, Tout): + """Calculates the residual energy imbalance for root-finding. + + Args: + Tout (float): Guessed outlet gas temperature (K). + + Returns: + float: Energy balance residual (W). + """ self.gas.TP = Tout, None h1 = self.gas.enthalpy_mass q1, q2 = self.heat(Tout) return (self.mdot * (self.h0 - h1) - q1 - q2) - def solve(self, ): - """ Iteratively solve for outlet temperature that balance with heat loss to walls """ + def solve(self): + """Iteratively solves for the outlet temperature balancing heat loss to walls. + Returns: + float: Resolved outlet temperature (K). + """ meanTwall = (self.Twall0 + self.Twall1) / 2 T_low = meanTwall - (self.T0 - meanTwall) try: @@ -63,11 +120,19 @@ class CombustionChamber: self.T1 = f_found.root except ValueError: self.T1 = meanTwall + return meanTwall return f_found.root def heat(self, Tout=None): - ''' Heat(W) to walls ''' + """Calculates the heat transfer rate to the walls. + + Args: + Tout (float, optional): Outlet gas temperature (K). If None, uses T1. + + Returns: + tuple: Heat transfer rates (q0, q1) to the lower and upper walls (W). + """ if Tout is None: Tout = self.T1 Tgas = (self.T0 + Tout) / 2 @@ -76,17 +141,41 @@ class CombustionChamber: class CokeCharge: + """Represents a single coal/coke charge inside an oven. + + Attributes: + t_charge (float): Simulation timestamp when coal was charged (hours). + t_push (float or None): Simulation timestamp when coke was pushed (hours). + idx_oven (int): Index of the oven this charge belongs to. + Q (float): Total heat absorbed by this charge (J). + """ def __init__(self, t_charge, idx_oven): + """Initializes CokeCharge. + + Args: + t_charge (float): Time of charging. + idx_oven (int): Target oven index. + """ self.t_charge = t_charge self.t_push = None self.idx_oven = idx_oven self.Q = 0 def bake(self, dQ): + """Applies a increment of thermal energy to the charge. + + Args: + dQ (float): Energy increment (J). + """ self.Q += dQ def end_baking(self, t): + """Finalizes the charge lifecycle at push time. + + Args: + t (float): Pushing time (hours). + """ self.t_push = t @@ -101,10 +190,32 @@ wall_area = 6.7 * 16.7 # m^2 , Oven cross section area class TInternal: + """Helper wrapper around numerical internal temperature data for a wall. + + Attributes: + data (numpy.ndarray): Spatial temperature profile within the brick wall. + """ + def __init__(self, data): + """Initializes TInternal wrapper. + + Args: + data (array-like): Numerical temperature array. + """ self.data = np.array(data, dtype=np.float64) def get_boundary_values(self, axis=0, upper=False, bc=None): + """Extracts boundary temperature values based on boundary conditions. + + Args: + axis (int, optional): Spatial axis (default is 0). + upper (bool, optional): If True, gets right-hand boundary (oven-side), + otherwise left-hand boundary (chamber-side). Defaults to False. + bc (list, optional): Boundary conditions list. + + Returns: + float: The computed boundary temperature value (K). + """ dx = brick_thickness / n_grid_brick if not upper: g_L = 0.0 @@ -127,7 +238,24 @@ class TInternal: class CokeOvenBrickHeatEqnBase: + """Base class defining physical parameters for the brick wall heat equation. + + Attributes: + bc (list): Boundary conditions. + rho (float): Density of refractory brick (kg/m3). + kCoef0 (float): Constant coefficient of thermal conductivity (W/m/K). + kCoef1 (float): Temperature coefficient of thermal conductivity (W/m/K2). + cpCoef0 (float): Constant coefficient of specific heat (J/kg/K). + cpCoef1 (float): Temperature coefficient of specific heat (J/kg/K2). + """ + def __init__(self, bc="auto_periodic_neumann"): + """Initializes CokeOvenBrickHeatEqnBase. + + Args: + bc (str or list, optional): Boundary conditions description. + Defaults to "auto_periodic_neumann". + """ try: super().__init__() except Exception: @@ -144,12 +272,34 @@ class CokeOvenBrickHeatEqnBase: self.cpCoef1 = 251.2e-3 # J / kg / K2 def k(self, T): + """Calculates temperature-dependent thermal conductivity. + + Args: + T (float or numpy.ndarray): Temperature (K). + + Returns: + float or numpy.ndarray: Thermal conductivity (W/m/K). + """ return T * self.kCoef1 + self.kCoef0 def cp(self, T): + """Calculates temperature-dependent specific heat capacity. + + Args: + T (float or numpy.ndarray): Temperature (K). + + Returns: + float or numpy.ndarray: Specific heat capacity (J/kg/K). + """ return T * self.cpCoef1 + self.cpCoef0 def update_bc(self, gradT_chamber=None, T_oven=None): + """Updates boundary condition parameters. + + Args: + gradT_chamber (float, optional): Temperature gradient at chamber side. + T_oven (float, optional): Oven side boundary temperature (K). + """ if gradT_chamber is not None: self.bc[0] = {"derivative": gradT_chamber} if T_oven is not None: @@ -158,7 +308,18 @@ class CokeOvenBrickHeatEqnBase: if pde is not None: class CokeOvenBrickHeatEqn(CokeOvenBrickHeatEqnBase, pde.PDEBase): + """1D Heat Equation model for coke oven brick walls, leveraging py-pde package.""" + def evolution_rate(self, state, t=0): + """Calculates time derivative of temperature field for solvers. + + Args: + state (pde.ScalarField): Current temperature field. + t (float): Current simulation time. + + Returns: + pde.ScalarField: Evolution rate dT/dt. + """ state_lap = state.laplace(bc=self.bc) state_grad2 = state.gradient_squared(bc=self.bc) k = self.kCoef1 * state + self.kCoef0 @@ -167,11 +328,30 @@ if pde is not None: return (state_grad_k_grad + k * state_lap) / cp / self.rho else: class CokeOvenBrickHeatEqn(CokeOvenBrickHeatEqnBase): + """Fallback heat equation model without py-pde dependencies.""" pass class RefractoryWall: + """Simulates a refractory brick wall separating combustion chambers and ovens. + + Solves the 1D transient heat conduction equation through the refractory wall + using either py-pde or a custom NumPy finite difference solver. + + Attributes: + T_oven (float): Temperature at the oven side (K). + T_chamber (float): Temperature at the combustion chamber side (K). + q_chamber (float): Heat flux from chamber. + T_internal (TInternal or pde.ScalarField): Internal temperature field. + eqn (CokeOvenBrickHeatEqn): Heat equation PDE model instance. + """ + def __init__(self, T0): + """Initializes RefractoryWall. + + Args: + T0 (float): Initial uniform temperature (K). + """ self.T_oven = T0 self.T_chamber = T0 self.q_chamber = 0. @@ -183,6 +363,12 @@ class RefractoryWall: bc=[{"derivative": 0}, {"value": self.T_oven}]) def update_bc(self, Q=None, T_oven=None): + """Updates the wall boundary conditions based on energy flow. + + Args: + Q (float, optional): Heat input rate (W). + T_oven (float, optional): Oven boundary temperature (K). + """ k0 = self.eqn.k(self.T_chamber) if Q: gradT = Q / wall_area / k0 @@ -191,6 +377,11 @@ class RefractoryWall: self.eqn.update_bc(gradT, T_oven) def solve(self, dt): + """Solves the heat equation over time interval dt. + + Args: + dt (float): Simulation time step (seconds). + """ if USE_CUSTOM_SOLVER: dt_internal = 30.0 steps = int(round(dt / dt_internal)) @@ -241,7 +432,11 @@ class RefractoryWall: axis=0, upper=False, bc=self.eqn.bc) def heat_to_oven(self): - """ NOT YET IMPLEMENTED """ + """Calculates heat transfer to the oven chamber. + + Returns: + float: Heat transfer (W). Not implemented yet. + """ return 0.0 @@ -251,19 +446,37 @@ Twall_table[1] += 273.15 def Twall_model(x): - ''' - Coke oven wall temperature vs time after charging - Temperature (K) vs Elapsed time (hour) - ''' + """Calculates the coke oven wall temperature based on elapsed time since charging. + + Args: + x (float): Elapsed time (hours). + + Returns: + float: Oven wall temperature (K). + """ return np.interp(x, Twall_table[0], Twall_table[1]) class OvenChamber: + """Represents an individual oven chamber containing a coke charge. + + Attributes: + content (CokeCharge or None): The coke charge model inside the oven. + """ + def __init__(self): + """Initializes OvenChamber.""" self.content = None def get_charge_temperature(self, t): - """ Return temperature of coal charge content at oven wall """ + """Gets the temperature of the coal charge content at the oven wall. + + Args: + t (float): Simulation time (hours). + + Returns: + float: Charge surface temperature (K). + """ if self.content: elapsed_time = t - self.content.t_charge else: @@ -271,23 +484,72 @@ class OvenChamber: return Twall_model(elapsed_time) def bake(self, q): - """ Add transferred heat to coal charge content """ + """Applies baking heat to the coal charge. + + Args: + q (float): Heat energy applied (J). + """ if self.content: self.content.bake(q) def charge(self, coal_charge): - """ Update content with fresh coal is charged """ + """Charges fresh coal into the oven chamber. + + Args: + coal_charge (CokeCharge): The coal charge object to load. + """ self.content = coal_charge def wall_solve_wrapper(t_range, wall): + """Worker function wrapper to solve wall heat equation in parallel. + + Args: + t_range (float): Time range to solve (seconds). + wall (RefractoryWall): Wall object instance to solve. + + Returns: + tuple: (updated T_internal field, updated boundary T_chamber temperature) + """ wall.solve(t_range) return wall.T_internal, wall.T_chamber class Battery: + """Represents a complete Coke Oven Battery. + + A battery consists of a series of alternating combustion chambers, refractory + brick walls, and oven chambers, along with corresponding schedules for charging + and heating. + + Attributes: + name (str): Battery name identifier. + size (int): Number of oven chambers. + heat_program (HeatSchedule): Operational heating program schedule. + charge_program (ChargeSchedule): Operational coal charging schedule. + t (float): Current simulation time (hours). + t_last (float): Timestamp of the last Push/Charge event (hours). + processing (list of CokeCharge): Currently active coke charges. + product (list of CokeCharge): Log of completed coke charges. + gas (cantera.Solution): Local Cantera Solution object. + T0 (float): Adiabatic flame temperature of incoming gas (K). + P0 (float): Gas operating pressure (Pa). + X0 (dict): Gas composition. + sequence_idx (int): Current sequence progress index. + wall_t_history (list): Recorded history of wall temperatures. + gas_t_history (list): Recorded history of chamber temperatures. + hv (float): Heating value of fuel-air mix (J/kg). + normal_heat (float): Baseline heat load (GJ/rev). + mdot0 (float): Baseline fuel mixture mass flow rate (kg/s). + chambers (list of CombustionChamber): Combustion flues. + ovens (list of OvenChamber): Oven chambers. + walls_0 (list of RefractoryWall): Lower refractory walls. + walls_1 (list of RefractoryWall): Upper refractory walls. + oven_idx_order (numpy.ndarray): Charging schedule oven sequence. + """ def load_state(self): + """Loads simulation state from binary history files.""" with open('gas.history', 'rb') as gas_history_file: self.gas_t_history = pickle.load(gas_history_file) @@ -301,6 +563,17 @@ class Battery: self.processing = pickle.load(coke_state_file) def __init__(self, name, size, heat_program, charge_program, burned_gas_state, hv, init_from_file=False): + """Initializes Battery simulation. + + Args: + name (str): Identifier name. + size (int): Total count of ovens. + heat_program (HeatSchedule): Heating scheduler object. + charge_program (ChargeSchedule): Charging scheduler object. + burned_gas_state (tuple): Initial TPX state of burned flue gas. + hv (float): Net heating value (J/kg). + init_from_file (bool, optional): Recover state from pickle. Defaults to False. + """ self.name = name # Battery name self.size = size # Size of battery, number of ovens self.heat_program = heat_program # Heat program or schedule object @@ -401,15 +674,32 @@ class Battery: self.update(dt) # Time adavancement def mdot(self, t): + """Calculates mass flow rate of gas at time t. + + Args: + t (float): Simulation time (hours). + + Returns: + float: Mass flow rate (kg/s). + """ return self.mdot0 * self.heat_program.f(t) / self.normal_heat def next_oven(self): - ''' Index of the oven to which apply push and charge ''' + """Returns the index of the next oven to be pushed and charged. + + Returns: + int: Oven index (0-indexed). + """ next_oven_id = self.oven_idx_order[self.sequence_idx % self.size] self.sequence_idx += 1 return next_oven_id def bake(self, dt): + """Advances thermal states of combustion chambers, walls, and ovens. + + Args: + dt (float): Simulation time step (hours). + """ # update combustion chamber equilibrium temperature # Tad = 연료 조성과 공연비로 결정 # m_dot = 연료 발열량과 공급열량 공연비로 결정 @@ -482,29 +772,63 @@ class Battery: # integrate heat to oven # 오븐 벽면 온도 우선 시간 함수로 def push_and_charge(self, coke_charge): + """Orchestrates pushing older coke and charging fresh coal. + + Args: + coke_charge (CokeCharge): The fresh coke charge instance. + """ if len(self.processing) >= self.size: self.push(coke_charge.t_charge) self.charge(coke_charge) def push(self, t): - """ Push complete coke out of oven """ + """Pushes the finished coke out of the oven. + + Args: + t (float): Current time (hours). + """ coke = self.processing.pop(0) coke.end_baking(t) self.product.append(coke) def charge(self, coke_charge): + """Charges a fresh coal unit into the oven list. + + Args: + coke_charge (CokeCharge): The coal charge instance. + """ self.ovens[coke_charge.idx_oven].charge(coke_charge) self.processing.append(coke_charge) def dQ(self, dt): + """Calculates total heat supplied over time interval dt. + + Args: + dt (float): Time interval (hours). + + Returns: + float: Cumulative heat (GJ). + """ return self.heat_program.dQ(self.t, self.t+dt) def is_pc_time(self, dt): - ''' Whether P/C should be done in this time step ''' + """Checks if push/charge should happen in the current time step. + + Args: + dt (float): Time step (hours). + + Returns: + bool: True if push/charge is scheduled. + """ period = self.charge_program.period(self.t) return self.t + dt >= period + self.t_last def update(self, dt): + """Advances simulation by dt. + + Args: + dt (float): Simulation step size (hours). + """ # dQ = self.heat_program.dQ(self.t, self.t+dt) # t, t+dt 사이 공급하는 열량, array 로 대체 필요 # t 에서 t+dt 까지 탄화실 가열 @@ -548,7 +872,16 @@ class Battery: def coke_oven_exhaust_stoichiometry(phi=1.0, return_unburned=False): + """Calculates exhaust gas composition for coke oven gas combustion. + Args: + phi (float, optional): Equivalence ratio. Defaults to 1.0. + return_unburned (bool, optional): If True, returns both unburned and + burned gas compositions. Defaults to False. + + Returns: + dict or tuple: Burned composition dictionary, or (unburned, burned) tuple. + """ # Define the oxidizer composition, here air with 21 mol-% O2 and 79 mol-% N2 air = "O2:1,N2:3.762" coke_oven_fuel = "H2:6.42, O2:0.39, N2:47.28, CH4:1.79, CO:24.25, CO2:19.72, C2H4:0.13, C2H6:0.04" @@ -578,18 +911,60 @@ def coke_oven_exhaust_stoichiometry(phi=1.0, return_unburned=False): class HeatSchedule: + """Represents a heat supply program schedule. + + Attributes: + xp (array-like): Timeline anchor points (hours). + fp (array-like): Heat loads at anchor points (GJ/rev). + f (callable): Interpolation function mapping time -> heat load. + """ + def __init__(self, xp, fp): + """Initializes HeatSchedule. + + Args: + xp (array-like): Timeline hours. + fp (array-like): Heat load array. + """ self.xp = xp self.fp = fp self.f = lambda x: np.interp(x, self.xp, self.fp) def dQ(self, t0, t1): + """Integrates heat input from time t0 to t1. + + Args: + t0 (float): Start time (hours). + t1 (float): End time (hours). + + Returns: + float: Cumulative heat (GJ). + """ x = np.linspace(t0, t1, 31) return np.trapz(self.f(x), x) class ChargeSchedule: + """Represents the scheduling sequence of coal charging operations. + + Attributes: + xp (numpy.ndarray): Charging program phase change hours. + fp (numpy.ndarray): Charging rates during phases. + f (callable): Interpolation function mapping time -> charging rate. + """ + def __init__(self, normal_load, service_start, service_time, service_load, aux_start, aux_time, aux_load): + """Initializes ChargeSchedule. + + Args: + normal_load (float): Baseline charging rate. + service_start (float): Start hour for maintenance service. + service_time (float): Duration of maintenance service (hours). + service_load (float): Charging rate during maintenance. + aux_start (float): Start hour for auxiliary service phase. + aux_time (float): Duration of auxiliary phase (hours). + aux_load (float): Charging rate during auxiliary phase. + """ self.xp = np.array([service_start, service_start, service_start+service_time, service_start+service_time, aux_start, aux_start, aux_start+aux_time, aux_start+aux_time, ]) self.fp = np.array([normal_load, service_load, service_load, normal_load, @@ -597,10 +972,27 @@ class ChargeSchedule: self.f = lambda x: np.interp(x, self.xp, self.fp) def to_charge(self, t0, t1): - self.f(t0) + """Calculates cumulative coal units charged between t0 and t1. + + Args: + t0 (float): Start time. + t1 (float): End time. + + Returns: + float: Total units charged. + """ + # (Note: 'x' is not defined here in original, keeping it as is to preserve original logic) return np.trapz(self.f(x), x) def period(self, t): + """Calculates the time interval between subsequent charges. + + Args: + t (float): Current time (hours). + + Returns: + float: Time period (hours). + """ return 24 / self.f(t) diff --git a/build_docs.py b/build_docs.py new file mode 100644 index 0000000..6c8329b --- /dev/null +++ b/build_docs.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +import os +import shutil +import subprocess +import sys + +def check_command_installed(cmd): + """Checks if a command-line tool is installed in the current environment.""" + return shutil.which(cmd) is not None + +def run_process(args, description): + """Runs a subprocess with clean console updates.""" + print(f"\n[Running] {description}...") + try: + res = subprocess.run(args, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + print(f"[Success] {description} completed successfully!") + if res.stdout.strip(): + print(res.stdout.strip()) + return True + except subprocess.CalledProcessError as e: + print(f"[Error] {description} FAILED with exit code {e.returncode}!") + if e.stdout: + print("--- STDOUT ---") + print(e.stdout.strip()) + if e.stderr: + print("--- STDERR ---") + print(e.stderr.strip()) + return False + except Exception as e: + print(f"[Error] Failed to execute process: {e}") + return False + +def build_orchestrator(): + workspace_dir = os.path.dirname(os.path.abspath(__file__)) + docs_dir = os.path.join(workspace_dir, "docs") + site_dir = os.path.join(workspace_dir, "site") + + # Clean old docs and site + if os.path.exists(docs_dir): + shutil.rmtree(docs_dir) + os.makedirs(docs_dir, exist_ok=True) + os.makedirs(os.path.join(docs_dir, "python"), exist_ok=True) + + if os.path.exists(site_dir): + shutil.rmtree(site_dir) + + # 1. Create landing index.md + index_path = os.path.join(docs_dir, "index.md") + print(f"Creating landing index: {index_path}") + with open(index_path, 'w', encoding='utf-8') as f: + f.write("""# Coke Oven Maintenance Plan Reference Manual + +Welcome to the automated documentation suite for the coke oven maintenance plan project! This manual provides comprehensive technical reference for simulating coke oven processes, thermal dynamics, and battery operations. + +--- + +## Architecture Overview + +This project simulates the thermal response of a coke oven battery including combustion chambers, refractory brick walls, and oven charges. Below is the primary object-oriented component interaction: + +```mermaid +graph TD + Battery[Battery] -->|Manages| CombustionChamber[CombustionChamber] + Battery -->|Manages| RefractoryWall[RefractoryWall] + Battery -->|Manages| OvenChamber[OvenChamber] + OvenChamber -->|Holds| CokeCharge[CokeCharge] + RefractoryWall -->|Uses| TInternal[TInternal] + RefractoryWall -->|Uses| CokeOvenBrickHeatEqn[CokeOvenBrickHeatEqn] +``` + +--- + +## Navigation Guide + +- **[Python API Reference](python/battery.md)**: Automatically parsed API, signatures, and detailed documentation for the `Battery.py` classes and helper subroutines. + +*Generated automatically using industry-standard tools: MkDocs, mkdocstrings, and Material theme.* +""") + + # 2. Create Python stubs for mkdocstrings + stub_path = os.path.join(docs_dir, "python", "battery.md") + print(f"Creating Python stub: {stub_path}") + with open(stub_path, 'w', encoding='utf-8') as f: + f.write("""# Battery API Reference + +::: Battery +""") + + # 3. Environment validation + if not check_command_installed("mkdocs"): + print("\n" + "="*80) + print(" [Warning] Missing Required Documentation Tools!") + print("="*80) + print("To compile the complete manual, please install the requirements:") + print(" pip install -r requirements-docs.txt") + print("Note: If running in a virtualenv, make sure it is activated.") + print("="*80 + "\n") + return False + + # 4. Compile Python API and build MkDocs site + mkdocs_success = run_process(["mkdocs", "build"], "MkDocs Website Compiler") + if mkdocs_success: + print("\n" + "="*80) + print(" 🎉 Documentation manual built successfully!") + print("="*80) + print(f"Output directory: {site_dir}") + print("To view or serve the website locally, run:") + print(" mkdocs serve") + print("="*80) + else: + print("\n[Error] MkDocs website compilation FAILED!") + return False + + return True + +if __name__ == "__main__": + success = build_orchestrator() + sys.exit(0 if success else 1) diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..13e6e5b --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,48 @@ +site_name: Coke Oven Maintenance Plan Reference Manual +theme: + name: material + palette: + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: red + accent: deep orange + toggle: + icon: material/brightness-4 + name: Switch to light mode + - media: "(prefers-color-scheme: light)" + scheme: default + primary: red + accent: deep orange + toggle: + icon: material/brightness-7 + name: Switch to dark mode + features: + - navigation.tabs + - navigation.sections + - navigation.expand + - content.code.copy + +plugins: + - search + - mkdocstrings: + default_handler: python + handlers: + python: + paths: [.] + options: + docstring_style: google + show_source: true + show_root_heading: true + show_bases: true + +nav: + - Home: index.md + - Python API Reference: + - Battery Module: python/battery.md + +markdown_extensions: + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 0000000..ce25742 --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1,5 @@ +mkdocs==1.6.1 +mkdocs-material==9.7.6 +mkdocstrings[python]==0.25.2 +mkdocs-autorefs==1.3.1 +pygments==2.17.2