feat: add automated documentation building workflow and Google-style docstrings
All checks were successful
Deploy Documentation / build-and-deploy (push) Successful in 3m17s

This commit is contained in:
ignis 2026-06-05 13:40:22 +00:00
parent fb2887e068
commit 43499f58e3
6 changed files with 673 additions and 15 deletions

View file

@ -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

2
.gitignore vendored
View file

@ -1,5 +1,7 @@
# Jupyter Notebook checkpoints # Jupyter Notebook checkpoints
.ipynb_checkpoints .ipynb_checkpoints
site/
docs/
# Virtual Environment # Virtual Environment
.venv/ .venv/

View file

@ -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 from functools import reduce
import logging import logging
@ -19,7 +26,35 @@ except ImportError:
class CombustionChamber: 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): 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.mdot = mdot # kg/s
self.gas = ct_object # gas object self.gas = ct_object # gas object
self.eq_state = burned_state # HP equilibrium state self.eq_state = burned_state # HP equilibrium state
@ -36,25 +71,47 @@ class CombustionChamber:
self.Area = 6.7 * 16.7 self.Area = 6.7 * 16.7
def update_mdot(self, mdot_new): 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: if mdot_new:
self.mdot = mdot_new self.mdot = mdot_new
def update_Twall(self, Twall0=None, Twall1=None): 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: if Twall0:
self.Twall0 = Twall0 self.Twall0 = Twall0
if Twall1: if Twall1:
self.Twall1 = Twall1 self.Twall1 = Twall1
def energy_balance_equation(self, Tout): 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 self.gas.TP = Tout, None
h1 = self.gas.enthalpy_mass h1 = self.gas.enthalpy_mass
q1, q2 = self.heat(Tout) q1, q2 = self.heat(Tout)
return (self.mdot * (self.h0 - h1) - q1 - q2) return (self.mdot * (self.h0 - h1) - q1 - q2)
def solve(self, ): def solve(self):
""" Iteratively solve for outlet temperature that balance with heat loss to walls """ """Iteratively solves for the outlet temperature balancing heat loss to walls.
Returns:
float: Resolved outlet temperature (K).
"""
meanTwall = (self.Twall0 + self.Twall1) / 2 meanTwall = (self.Twall0 + self.Twall1) / 2
T_low = meanTwall - (self.T0 - meanTwall) T_low = meanTwall - (self.T0 - meanTwall)
try: try:
@ -63,11 +120,19 @@ class CombustionChamber:
self.T1 = f_found.root self.T1 = f_found.root
except ValueError: except ValueError:
self.T1 = meanTwall self.T1 = meanTwall
return meanTwall
return f_found.root return f_found.root
def heat(self, Tout=None): 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: if Tout is None:
Tout = self.T1 Tout = self.T1
Tgas = (self.T0 + Tout) / 2 Tgas = (self.T0 + Tout) / 2
@ -76,17 +141,41 @@ class CombustionChamber:
class CokeCharge: 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): 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_charge = t_charge
self.t_push = None self.t_push = None
self.idx_oven = idx_oven self.idx_oven = idx_oven
self.Q = 0 self.Q = 0
def bake(self, dQ): def bake(self, dQ):
"""Applies a increment of thermal energy to the charge.
Args:
dQ (float): Energy increment (J).
"""
self.Q += dQ self.Q += dQ
def end_baking(self, t): def end_baking(self, t):
"""Finalizes the charge lifecycle at push time.
Args:
t (float): Pushing time (hours).
"""
self.t_push = t self.t_push = t
@ -101,10 +190,32 @@ wall_area = 6.7 * 16.7 # m^2 , Oven cross section area
class TInternal: 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): def __init__(self, data):
"""Initializes TInternal wrapper.
Args:
data (array-like): Numerical temperature array.
"""
self.data = np.array(data, dtype=np.float64) self.data = np.array(data, dtype=np.float64)
def get_boundary_values(self, axis=0, upper=False, bc=None): 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 dx = brick_thickness / n_grid_brick
if not upper: if not upper:
g_L = 0.0 g_L = 0.0
@ -127,7 +238,24 @@ class TInternal:
class CokeOvenBrickHeatEqnBase: 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"): def __init__(self, bc="auto_periodic_neumann"):
"""Initializes CokeOvenBrickHeatEqnBase.
Args:
bc (str or list, optional): Boundary conditions description.
Defaults to "auto_periodic_neumann".
"""
try: try:
super().__init__() super().__init__()
except Exception: except Exception:
@ -144,12 +272,34 @@ class CokeOvenBrickHeatEqnBase:
self.cpCoef1 = 251.2e-3 # J / kg / K2 self.cpCoef1 = 251.2e-3 # J / kg / K2
def k(self, T): 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 return T * self.kCoef1 + self.kCoef0
def cp(self, T): 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 return T * self.cpCoef1 + self.cpCoef0
def update_bc(self, gradT_chamber=None, T_oven=None): 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: if gradT_chamber is not None:
self.bc[0] = {"derivative": gradT_chamber} self.bc[0] = {"derivative": gradT_chamber}
if T_oven is not None: if T_oven is not None:
@ -158,7 +308,18 @@ class CokeOvenBrickHeatEqnBase:
if pde is not None: if pde is not None:
class CokeOvenBrickHeatEqn(CokeOvenBrickHeatEqnBase, pde.PDEBase): 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): 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_lap = state.laplace(bc=self.bc)
state_grad2 = state.gradient_squared(bc=self.bc) state_grad2 = state.gradient_squared(bc=self.bc)
k = self.kCoef1 * state + self.kCoef0 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 return (state_grad_k_grad + k * state_lap) / cp / self.rho
else: else:
class CokeOvenBrickHeatEqn(CokeOvenBrickHeatEqnBase): class CokeOvenBrickHeatEqn(CokeOvenBrickHeatEqnBase):
"""Fallback heat equation model without py-pde dependencies."""
pass pass
class RefractoryWall: 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): def __init__(self, T0):
"""Initializes RefractoryWall.
Args:
T0 (float): Initial uniform temperature (K).
"""
self.T_oven = T0 self.T_oven = T0
self.T_chamber = T0 self.T_chamber = T0
self.q_chamber = 0. self.q_chamber = 0.
@ -183,6 +363,12 @@ class RefractoryWall:
bc=[{"derivative": 0}, {"value": self.T_oven}]) bc=[{"derivative": 0}, {"value": self.T_oven}])
def update_bc(self, Q=None, T_oven=None): 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) k0 = self.eqn.k(self.T_chamber)
if Q: if Q:
gradT = Q / wall_area / k0 gradT = Q / wall_area / k0
@ -191,6 +377,11 @@ class RefractoryWall:
self.eqn.update_bc(gradT, T_oven) self.eqn.update_bc(gradT, T_oven)
def solve(self, dt): def solve(self, dt):
"""Solves the heat equation over time interval dt.
Args:
dt (float): Simulation time step (seconds).
"""
if USE_CUSTOM_SOLVER: if USE_CUSTOM_SOLVER:
dt_internal = 30.0 dt_internal = 30.0
steps = int(round(dt / dt_internal)) steps = int(round(dt / dt_internal))
@ -241,7 +432,11 @@ class RefractoryWall:
axis=0, upper=False, bc=self.eqn.bc) axis=0, upper=False, bc=self.eqn.bc)
def heat_to_oven(self): 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 return 0.0
@ -251,19 +446,37 @@ Twall_table[1] += 273.15
def Twall_model(x): def Twall_model(x):
''' """Calculates the coke oven wall temperature based on elapsed time since charging.
Coke oven wall temperature vs time after charging
Temperature (K) vs Elapsed time (hour) Args:
''' x (float): Elapsed time (hours).
Returns:
float: Oven wall temperature (K).
"""
return np.interp(x, Twall_table[0], Twall_table[1]) return np.interp(x, Twall_table[0], Twall_table[1])
class OvenChamber: 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): def __init__(self):
"""Initializes OvenChamber."""
self.content = None self.content = None
def get_charge_temperature(self, t): 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: if self.content:
elapsed_time = t - self.content.t_charge elapsed_time = t - self.content.t_charge
else: else:
@ -271,23 +484,72 @@ class OvenChamber:
return Twall_model(elapsed_time) return Twall_model(elapsed_time)
def bake(self, q): 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: if self.content:
self.content.bake(q) self.content.bake(q)
def charge(self, coal_charge): 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 self.content = coal_charge
def wall_solve_wrapper(t_range, wall): 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) wall.solve(t_range)
return wall.T_internal, wall.T_chamber return wall.T_internal, wall.T_chamber
class Battery: 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): def load_state(self):
"""Loads simulation state from binary history files."""
with open('gas.history', 'rb') as gas_history_file: with open('gas.history', 'rb') as gas_history_file:
self.gas_t_history = pickle.load(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) self.processing = pickle.load(coke_state_file)
def __init__(self, name, size, heat_program, charge_program, burned_gas_state, hv, init_from_file=False): 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.name = name # Battery name
self.size = size # Size of battery, number of ovens self.size = size # Size of battery, number of ovens
self.heat_program = heat_program # Heat program or schedule object self.heat_program = heat_program # Heat program or schedule object
@ -401,15 +674,32 @@ class Battery:
self.update(dt) # Time adavancement self.update(dt) # Time adavancement
def mdot(self, t): 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 return self.mdot0 * self.heat_program.f(t) / self.normal_heat
def next_oven(self): 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] next_oven_id = self.oven_idx_order[self.sequence_idx % self.size]
self.sequence_idx += 1 self.sequence_idx += 1
return next_oven_id return next_oven_id
def bake(self, dt): 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 # update combustion chamber equilibrium temperature
# Tad = 연료 조성과 공연비로 결정 # Tad = 연료 조성과 공연비로 결정
# m_dot = 연료 발열량과 공급열량 공연비로 결정 # m_dot = 연료 발열량과 공급열량 공연비로 결정
@ -482,29 +772,63 @@ class Battery:
# integrate heat to oven # 오븐 벽면 온도 우선 시간 함수로 # integrate heat to oven # 오븐 벽면 온도 우선 시간 함수로
def push_and_charge(self, coke_charge): 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: if len(self.processing) >= self.size:
self.push(coke_charge.t_charge) self.push(coke_charge.t_charge)
self.charge(coke_charge) self.charge(coke_charge)
def push(self, t): 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 = self.processing.pop(0)
coke.end_baking(t) coke.end_baking(t)
self.product.append(coke) self.product.append(coke)
def charge(self, coke_charge): 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.ovens[coke_charge.idx_oven].charge(coke_charge)
self.processing.append(coke_charge) self.processing.append(coke_charge)
def dQ(self, dt): 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) return self.heat_program.dQ(self.t, self.t+dt)
def is_pc_time(self, 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) period = self.charge_program.period(self.t)
return self.t + dt >= period + self.t_last return self.t + dt >= period + self.t_last
def update(self, dt): 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 로 대체 필요 # dQ = self.heat_program.dQ(self.t, self.t+dt) # t, t+dt 사이 공급하는 열량, array 로 대체 필요
# t 에서 t+dt 까지 탄화실 가열 # t 에서 t+dt 까지 탄화실 가열
@ -548,7 +872,16 @@ class Battery:
def coke_oven_exhaust_stoichiometry(phi=1.0, return_unburned=False): 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 # Define the oxidizer composition, here air with 21 mol-% O2 and 79 mol-% N2
air = "O2:1,N2:3.762" 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" 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: 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): def __init__(self, xp, fp):
"""Initializes HeatSchedule.
Args:
xp (array-like): Timeline hours.
fp (array-like): Heat load array.
"""
self.xp = xp self.xp = xp
self.fp = fp self.fp = fp
self.f = lambda x: np.interp(x, self.xp, self.fp) self.f = lambda x: np.interp(x, self.xp, self.fp)
def dQ(self, t0, t1): 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) x = np.linspace(t0, t1, 31)
return np.trapz(self.f(x), x) return np.trapz(self.f(x), x)
class ChargeSchedule: 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): 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, 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, ]) aux_start, aux_start, aux_start+aux_time, aux_start+aux_time, ])
self.fp = np.array([normal_load, service_load, service_load, normal_load, 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) self.f = lambda x: np.interp(x, self.xp, self.fp)
def to_charge(self, t0, t1): 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) return np.trapz(self.f(x), x)
def period(self, t): 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) return 24 / self.f(t)

118
build_docs.py Normal file
View file

@ -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)

48
mkdocs.yml Normal file
View file

@ -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

5
requirements-docs.txt Normal file
View file

@ -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