GA基本正常

This commit is contained in:
AgentLabCn 2025-12-01 17:51:51 +08:00
parent 52e2f4dcb1
commit b86290c331
14 changed files with 848 additions and 615 deletions

34
AGENTS.md Normal file
View File

@ -0,0 +1,34 @@
# Repository Guidelines
## Project Structure & Module Organization
- Core simulation: `simulation_model.py` (Mesa `Model`), `production_line.py`, `demand_agent.py`.
- UI & orchestration: `app.py` (Solara dashboard, matplotlib plotting, loads `data/<year>` based on `year.json`).
- Optimization helpers: `genetic_calibration.py`, `ga_two_factor_calibration.py`, `build_distance_matrix.py`.
- Data: `data/<year>/` CSV/JSON inputs and outputs; temporary artifacts can live in `output/`.
## Setup, Build, and Run
- Create env: `conda env create -f environment.yml` then `conda activate gw`.
- Launch dashboard locally: `solara run app.py --host 0.0.0.0 --port 8765` (loads year set in `year.json`).
- Recompute distance matrix when locations change: `python build_distance_matrix.py`.
- Run calibration scripts headless: `python genetic_calibration.py` or `python ga_two_factor_calibration.py` (writes tuned params under `data/<year>`).
## Coding Style & Naming Conventions
- Follow Python 3.13 + PEP 8; 4-space indents; prefer type hints and docstrings for public functions/classes.
- Use `snake_case` for variables/functions/modules, `CapWords` for classes, and descriptive names for data columns.
- Keep plotting/fonts consistent with existing matplotlib config (Chinese labels enabled); reuse helpers instead of duplicating logic.
- Guard data-file access with clear error messages and UTF-8/GBK fallbacks as seen in `app.py`.
## Testing Guidelines
- No formal suite exists yet; add `pytest`-style tests under `tests/` when feasible, mocking file I/O and using small sample CSVs.
- For quick checks, run calibration scripts on a narrowed product set and validate outputs in `output/` or `data/<year>` before committing.
- Validate UI flows by running `solara` and confirming charts render and download links work for the target year.
## Commit & Pull Request Guidelines
- Commit messages: short, imperative summaries (e.g., `Add capacity cap to Shenzhen lines`); keep body focused on rationale and data assumptions.
- PRs should include: scope summary, affected year(s) in `data/`, before/after metrics or screenshots for UI changes, and steps to reproduce or rerun (`solara run ...`, script commands).
- Link related issues/requests and note any regenerated files (e.g., `distance_matrix.csv`, calibration outputs) so reviewers can verify.
## Data & Configuration Tips
- Set `year.json` to the active dataset before running any scripts; keep year directories consistent (`data/2025`, `data/2026`, etc.).
- Preserve CSV headers and encodings; when adding factories/products, update `factory_mapping.json` and rerun distance matrix generation.
- Avoid committing large intermediate artifacts under `output/` unless they are required inputs; prefer deterministic script outputs for reviewability.

73
build_factory_data.py Normal file
View File

@ -0,0 +1,73 @@
import json
import os
import pandas as pd
from pathlib import Path
def load_factory_mapping(year: int) -> dict:
path = os.path.join("data", str(year), "factory_mapping.json")
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def load_model_params(year: int) -> dict:
path = os.path.join("data", str(year), "model_params.json")
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def load_benchmark_factories(year: int) -> list[str]:
path = Path("data") / str(year) / "benchmark.csv"
encodings = ("utf-8", "utf-8-sig", "gbk")
last_error = None
df = None
for enc in encodings:
try:
df = pd.read_csv(path, encoding=enc)
break
except UnicodeDecodeError as exc:
last_error = exc
continue
if df is None:
raise last_error if last_error else FileNotFoundError(f"Missing {path}")
# Assume first column holds factory name (aligns with factory_mapping keys)
factory_col = df.columns[0]
return df[factory_col].astype(str).str.strip().unique().tolist()
def build_factory_dataframe(year: int) -> pd.DataFrame:
mapping = load_factory_mapping(year)
params = load_model_params(year)
default_factor = params.get("factor_default", 1)
benchmark_factories = load_benchmark_factories(year)
rows = []
for cn_name in benchmark_factories:
code = mapping.get(cn_name)
if code is None:
raise KeyError(f"benchmark.csv中的工厂“{cn_name}”未在 factory_mapping.json 中找到映射。")
factor = params.get(f"factor_{code}", default_factor if default_factor is not None else 1)
rows.append(
{
"工厂中文名": cn_name,
"工厂英文名": code,
"工厂平均磨合系数": float(factor),
"最小误差": 1_000_000,
}
)
return pd.DataFrame(rows)
def write_factory_csv(df: pd.DataFrame, year: int) -> str:
out_path = os.path.join("data", str(year), "factory_data.csv")
df.to_csv(out_path, index=False, encoding="utf-8-sig")
return out_path
def main():
year = 2025
df = build_factory_dataframe(year)
out_path = write_factory_csv(df, year)
print(f"factory_data.csv generated at: {out_path}")
if __name__ == "__main__":
main()

View File

@ -1,113 +1,113 @@
区域名,工厂名,是否新工厂,产线ID,生产型号,开始月份,结束月份
西北东部,艾郎玉门,是,艾郎玉门1,GWBD-A2,1,12
西北东部,艾郎玉门,是,艾郎玉门2,GWBD-A2,1,12
华北,艾郎张北,是,艾郎张北1,GWBD-A2,1,12
华北,艾郎张北,是,艾郎张北2,GWBD-A2,1,12
西北西部,时代巴里坤,是,时代巴里坤1,GWBD-A2,5,12
西北西部,时代巴里坤,是,时代巴里坤2,GWBD-A2,5,12
西北西部,时代巴里坤,是,时代巴里坤3,GW99A,3,4
西北西部,时代巴里坤,是,时代巴里坤4,GW99A,6,9
华南,时代百色,是,时代百色1,GWBD-A3,5,12
华南,时代百色,是,时代百色2,GWBD-A3,5,12
华东南北部,时代射阳,是,时代射阳1,GW130,9,12
华东南北部,时代射阳,是,时代射阳2,GW130,9,12
东北,时代松原,是,时代松原1,GWBD-A3,6,12
东北,时代松原,是,时代松原2,GWBD-A3,6,12
华东南南部,时代株洲,是,时代株洲1,GW99A,4,12
华东南南部,时代株洲,是,时代株洲2,GW99A,4,12
华东南北部,双瑞大丰,是,双瑞大丰1,GW110.5,2,11
华东南北部,双瑞大丰,是,双瑞大丰2,GW110.5,2,11
华东南北部,双瑞大丰,是,双瑞大丰3,GW93,1,1
华东南北部,双瑞东营,否,双瑞东营1,GWBD-A2,10,12
华东南北部,双瑞东营,否,双瑞东营2,GWBD-A2,10,12
华东南北部,双瑞东营,否,双瑞东营3,GWBD-A2,10,12
华东南北部,双瑞东营,否,双瑞东营4,GWBD-A2,10,12
华东南北部,天顺濮阳,否,天顺濮阳1,GW99A,8,12
华东南北部,天顺濮阳,否,天顺濮阳2,GW99A,8,12
华东南北部,天顺沙洋,否,天顺沙洋1,GW93,2,2
华东南北部,天顺沙洋,是,天顺沙洋2,GW93,5,5
华东南北部,天顺沙洋,否,天顺沙洋3,GW93,11,11
华东南北部,天顺沙洋,否,天顺沙洋4,GW99,10,11
华东南北部,天顺沙洋,否,天顺沙洋5,GW99A,5,12
华东南北部,天顺沙洋,否,天顺沙洋6,GW99A,5,12
东北,中材白城,否,中材白城1,GWBD-A3,8,8
华东南北部,中材阜宁,是,中材阜宁1,GW110.5,4,11
华东南北部,中材阜宁,是,中材阜宁2,GW110.5,4,11
华东南北部,中材阜宁,是,中材阜宁3,GW110.5,4,11
华东南北部,中材阜宁,是,中材阜宁4,GW83.4,1,11
华东南北部,中材阜宁,否,中材阜宁5,GW93,6,8
华东南北部,中材阜宁,否,中材阜宁6,GW99A,1,7
华东南北部,中材阜宁,否,中材阜宁7,GW99A,1,7
华东南北部,中材阜宁,否,中材阜宁8,SI90.2,1,12
华东南北部,中材阜宁,否,中材阜宁9,SI90.2,1,12
华东南北部,中材阜宁,是,中材阜宁10,SI90.2,3,12
华东南北部,中材阜宁,是,中材阜宁11,SI90.2,3,12
华东南北部,中材阜宁,是,中材阜宁12,SI90.2,3,12
华东南北部,中材阜宁,是,中材阜宁13,SI90.2,3,12
西北西部,中材哈密,否,中材哈密1,GWBD-A3,7,11
西北西部,中材哈密,否,中材哈密2,GWBD-A3,7,11
华北,中材邯郸,否,中材邯郸1,GW99A,1,12
华北,中材邯郸,否,中材邯郸2,GW99A,1,12
华北,中材邯郸,是,中材邯郸3,GWBD-A2,4,12
华北,中材邯郸,是,中材邯郸4,GWBD-A2,4,12
西北东部,中材酒泉,否,中材酒泉1,GW99,1,12
西北东部,中材酒泉,否,中材酒泉2,GW99,1,12
西北东部,中材酒泉,否,中材酒泉3,GWBD-A2,2,12
西北东部,中材酒泉,否,中材酒泉4,GWBD-A2,2,12
西北东部,中材酒泉,否,中材酒泉5,GWBD-A2,2,12
西北东部,中材酒泉,是,中材酒泉6,GWBD-A2,2,12
西北东部,中材酒泉,否,中材酒泉7,SI90.2,1,1
西北东部,中材酒泉,否,中材酒泉8,SI90.2,3,4
西北东部,中材酒泉,否,中材酒泉9,SI90.2,3,4
西北东部,中材酒泉,否,中材酒泉10,SI90.2,9,9
西北东部,中材酒泉,是,中材酒泉11,SI90.2,4,12
西北东部,中材酒泉,是,中材酒泉12,SI90.2,4,12
华东南北部,中材连云港,是,中材连云港1,GW93,4,4
华东南北部,中材连云港,否,中材连云港2,GW93,1,12
华东南北部,中材连云港,否,中材连云港3,GW93,1,12
华东南南部,中材萍乡,否,中材萍乡1,GW83.4,2,3
华东南南部,中材萍乡,是,中材萍乡2,GW93,1,10
华东南南部,中材萍乡,是,中材萍乡3,GW93,1,10
华东南南部,中材萍乡,是,中材萍乡4,GWBD-A3,2,12
华东南南部,中材萍乡,是,中材萍乡5,GWBD-A3,2,12
东北,中材锡林,否,中材锡林1,GW99,1,12
东北,中材锡林,否,中材锡林2,GW99,1,12
东北,中材锡林,是,中材锡林3,GWBD-A2,2,12
东北,中材锡林,是,中材锡林4,GWBD-A2,2,12
东北,中材锡林,是,中材锡林5,GWBD-A2,2,12
东北,中材锡林,否,中材锡林6,SI90.2,1,1
东北,中材锡林,否,中材锡林7,SI90.2,1,1
东北,中材兴安盟,是,中材兴安盟1,GW93,1,9
东北,中材兴安盟,是,中材兴安盟2,GW93,1,9
华南,中材阳江,是,中材阳江1,GW130,11,12
华南,中材阳江,是,中材阳江2,GW130,11,12
华南,中材阳江,否,中材阳江3,SI122,1,12
华南,中材阳江,否,中材阳江4,SI122,1,12
西北西部,中材伊吾,否,中材伊吾1,GWBD-A2,3,12
西北西部,中材伊吾,否,中材伊吾2,GWBD-A2,3,12
西北西部,中材伊吾,是,中材伊吾3,GWBD-A3,7,9
华南,中材玉溪,否,中材玉溪1,GW93,3,3
西北西部,重通昌吉,否,重通昌吉1,GW99,11,11
西北西部,重通昌吉,是,重通昌吉2,GW99A,2,10
西北西部,重通昌吉,否,重通昌吉3,GWBD-A2,1,12
西北西部,重通昌吉,否,重通昌吉4,GWBD-A2,1,12
西北西部,重通昌吉,否,重通昌吉5,GWBD-A2,1,12
东北,重通大安,是,重通大安1,GWBD-A2,3,12
东北,重通大安,是,重通大安2,GWBD-A2,4,12
华东南北部,重通如东,是,重通如东1,GW110.5,6,10
华东南北部,重通如东,是,重通如东2,GW110.5,6,10
华东南北部,重通如东,否,重通如东3,GW76,1,6
华东南北部,重通如东,否,重通如东4,GW76,1,6
华东南北部,重通如东,否,重通如东5,GW76,11,12
华东南北部,重通如东,否,重通如东6,GW76,11,12
华东南北部,重通如东,否,重通如东7,GW81,1,12
华东南北部,重通如东,否,重通如东8,GW81,1,12
华东南北部,重通如东,否,重通如东9,GW86,10,12
华东南北部,重通如东,否,重通如东10,GWBD-D,10,10
华东南北部,重通如东,是,重通如东11,GW83.3,7,9
西北东部,重通武威,否,重通武威1,GW83.4,1,1
西北东部,重通武威,否,重通武威2,GW99A,1,1
西北东部,重通武威,否,重通武威3,GWBD-A2,1,12
西北东部,重通武威,否,重通武威4,GWBD-A2,1,12
西北东部,重通武威,否,重通武威5,GWBD-A3,4,12
西北东部,重通武威,否,重通武威6,GWBD-A3,4,12
区域名,工厂名,是否新工厂,产线ID,生产型号,开始月份,结束月份,磨合系数,系数最小值,系数最大值
西北东部,艾郎玉门,是,艾郎玉门1,GWBD-A2,1,12,1.07380061238005,0.5,3
西北东部,艾郎玉门,是,艾郎玉门2,GWBD-A2,1,12,1.3086756673917384,0.5,3
华北,艾郎张北,是,艾郎张北1,GWBD-A2,1,12,1.0005776141701213,0.5,3
华北,艾郎张北,是,艾郎张北2,GWBD-A2,1,12,1.2558624416001731,0.5,3
西北西部,时代巴里坤,是,时代巴里坤1,GWBD-A2,2,12,2.877701634836078,0.5,3
西北西部,时代巴里坤,是,时代巴里坤2,GWBD-A2,2,12,2.148476381702223,0.5,3
西北西部,时代巴里坤,是,时代巴里坤3,GW99A,1,4,2.8216375801071467,0.5,3
西北西部,时代巴里坤,是,时代巴里坤4,GW99A,1,9,3.0,0.5,3
华南,时代百色,是,时代百色1,GWBD-A3,2,12,1.2209565088767191,0.5,3
华南,时代百色,是,时代百色2,GWBD-A3,2,12,2.13505523373714,0.5,3
华东南北部,时代射阳,是,时代射阳1,GW130,6,12,2.977100640862942,0.5,3
华东南北部,时代射阳,是,时代射阳2,GW130,8,12,1.4108697454394656,0.5,3
东北,时代松原,是,时代松原1,GWBD-A3,4,12,1.085052120762868,0.5,3
东北,时代松原,是,时代松原2,GWBD-A3,4,12,1.4804124608992453,0.5,3
华东南南部,时代株洲,是,时代株洲1,GW99A,2,12,0.8320501509514534,0.5,3
华东南南部,时代株洲,是,时代株洲2,GW99A,2,12,1.4984461065581405,0.5,3
华东南北部,双瑞大丰,是,双瑞大丰1,GW110.5,2,11,0.6454103640653669,0.5,3
华东南北部,双瑞大丰,是,双瑞大丰2,GW110.5,2,11,0.6530684517974175,0.5,3
华东南北部,双瑞大丰,否,双瑞大丰3,GW93,1,1,2.085029344273428,0.5,3
华东南北部,双瑞东营,是,双瑞东营1,GWBD-A2,6,12,2.925213291566798,0.5,3
华东南北部,双瑞东营,是,双瑞东营2,GWBD-A2,6,12,2.975043017037072,0.5,3
华东南北部,双瑞东营,是,双瑞东营3,GWBD-A2,7,12,2.9397351966791594,0.5,3
华东南北部,双瑞东营,是,双瑞东营4,GWBD-A2,7,12,2.616967714504933,0.5,3
华东南北部,天顺濮阳,否,天顺濮阳1,GW99A,8,12,2.790744177175357,0.5,3
华东南北部,天顺濮阳,否,天顺濮阳2,GW99A,8,12,0.6411549575195108,0.5,3
华东南北部,天顺沙洋,否,天顺沙洋1,GW93,2,2,0.8340035880432236,0.5,3
华东南北部,天顺沙洋,是,天顺沙洋2,GW93,5,5,1.1301574312833809,0.5,3
华东南北部,天顺沙洋,否,天顺沙洋3,GW93,11,11,2.025384406078239,0.5,3
华东南北部,天顺沙洋,否,天顺沙洋4,GW99,10,11,1.0429815540113208,0.5,3
华东南北部,天顺沙洋,否,天顺沙洋5,GW99A,5,12,1.123628778712014,0.5,3
华东南北部,天顺沙洋,否,天顺沙洋6,GW99A,5,12,0.979164298651741,0.5,3
东北,中材白城,否,中材白城1,GWBD-A3,8,8,1.763581199916676,0.5,3
华东南北部,中材阜宁,否,中材阜宁1,GW110.5,1,11,2.959144662478874,0.5,3
华东南北部,中材阜宁,否,中材阜宁2,GW110.5,1,11,2.92540109166913,0.5,3
华东南北部,中材阜宁,否,中材阜宁3,GW110.5,1,11,1.1010708194893164,0.5,3
华东南北部,中材阜宁,否,中材阜宁4,GW83.4,1,11,2.694547075119285,0.5,3
华东南北部,中材阜宁,否,中材阜宁5,GW93,6,8,2.3745893939183,0.5,3
华东南北部,中材阜宁,否,中材阜宁6,GW99A,1,7,1.8223796976955784,0.5,3
华东南北部,中材阜宁,否,中材阜宁7,GW99A,1,7,2.468575784243612,0.5,3
华东南北部,中材阜宁,否,中材阜宁8,SI90.2,1,12,0.7272902159645727,0.5,3
华东南北部,中材阜宁,否,中材阜宁9,SI90.2,1,12,2.181467973934617,0.5,3
华东南北部,中材阜宁,是,中材阜宁10,SI90.2,1,12,1.5244257562540415,0.5,3
华东南北部,中材阜宁,是,中材阜宁11,SI90.2,1,12,1.4259873327753854,0.5,3
华东南北部,中材阜宁,是,中材阜宁12,SI90.2,2,12,1.253548054276315,0.5,3
华东南北部,中材阜宁,是,中材阜宁13,SI90.2,2,12,0.6644941460935946,0.5,3
西北西部,中材哈密,否,中材哈密1,GWBD-A3,7,11,2.3686587068604954,0.5,3
西北西部,中材哈密,否,中材哈密2,GWBD-A3,7,11,1.1015355190681149,0.5,3
华北,中材邯郸,否,中材邯郸1,GW99A,1,12,1.925686336774216,0.5,3
华北,中材邯郸,否,中材邯郸2,GW99A,1,12,1.166441845882635,0.5,3
华北,中材邯郸,是,中材邯郸3,GWBD-A2,2,12,2.1539372175131355,0.5,3
华北,中材邯郸,是,中材邯郸4,GWBD-A2,3,12,0.6366978576009893,0.5,3
西北东部,中材酒泉,否,中材酒泉1,GW99,1,12,1.1568681400683591,0.5,3
西北东部,中材酒泉,否,中材酒泉2,GW99,1,12,1.1064271136987875,0.5,3
西北东部,中材酒泉,否,中材酒泉3,GWBD-A2,1,12,0.9954163068389692,0.5,3
西北东部,中材酒泉,是,中材酒泉4,GWBD-A2,1,12,1.2193407437521984,0.5,3
西北东部,中材酒泉,是,中材酒泉5,GWBD-A2,2,12,0.959238907870878,0.5,3
西北东部,中材酒泉,是,中材酒泉6,GWBD-A2,3,12,1.218588133158403,0.5,3
西北东部,中材酒泉,否,中材酒泉7,SI90.2,1,1,1.2323208798346863,0.5,3
西北东部,中材酒泉,否,中材酒泉8,SI90.2,1,4,1.1083005779929354,0.5,3
西北东部,中材酒泉,否,中材酒泉9,SI90.2,3,4,1.0979168774354644,0.5,3
西北东部,中材酒泉,否,中材酒泉10,SI90.2,9,9,0.6134714785903335,0.5,3
西北东部,中材酒泉,否,中材酒泉11,SI90.2,4,12,0.9060377208252096,0.5,3
西北东部,中材酒泉,否,中材酒泉12,SI90.2,4,12,0.8588420476065148,0.5,3
华东南北部,中材连云港,否,中材连云港1,GW93,1,4,0.6959224018718012,0.5,3
华东南北部,中材连云港,否,中材连云港2,GW93,1,12,2.388840113565708,0.5,3
华东南北部,中材连云港,否,中材连云港3,GW93,1,12,2.023924901276327,0.5,3
华东南南部,中材萍乡,否,中材萍乡1,GW83.4,1,3,2.7904582895129884,0.5,3
华东南南部,中材萍乡,否,中材萍乡2,GW93,1,10,1.6925578180285972,0.5,3
华东南南部,中材萍乡,否,中材萍乡3,GW93,1,10,0.8982597799795793,0.5,3
华东南南部,中材萍乡,否,中材萍乡4,GWBD-A3,1,12,1.3711168227538089,0.5,3
华东南南部,中材萍乡,否,中材萍乡5,GWBD-A3,1,12,1.219691503974376,0.5,3
东北,中材锡林,否,中材锡林1,GW99,1,12,2.9557253831441828,0.5,3
东北,中材锡林,否,中材锡林2,GW99,1,12,2.462362813243516,0.5,3
东北,中材锡林,是,中材锡林3,GWBD-A2,1,12,0.5982166642652442,0.5,3
东北,中材锡林,是,中材锡林4,GWBD-A2,1,12,2.644080826558284,0.5,3
东北,中材锡林,是,中材锡林5,GWBD-A2,2,12,0.5145312169222124,0.5,3
东北,中材锡林,否,中材锡林6,SI90.2,1,1,0.9769481674143102,0.5,3
东北,中材锡林,否,中材锡林7,SI90.2,1,1,1.238170985113167,0.5,3
东北,中材兴安盟,否,中材兴安盟1,GW93,1,9,1.0683346058426026,0.5,3
东北,中材兴安盟,否,中材兴安盟2,GW93,1,9,0.942117080768859,0.5,3
华南,中材阳江,是,中材阳江1,GW130,1,12,1.633431132348781,0.5,3
华南,中材阳江,是,中材阳江2,GW130,11,12,1.081038974714554,0.5,3
华南,中材阳江,否,中材阳江3,SI122,1,12,2.975783026146584,0.5,3
华南,中材阳江,否,中材阳江4,SI122,1,12,1.6018405358345469,0.5,3
西北西部,中材伊吾,否,中材伊吾1,GWBD-A2,1,12,2.1810444878994515,0.5,3
西北西部,中材伊吾,否,中材伊吾2,GWBD-A2,1,12,1.613919507763036,0.5,3
西北西部,中材伊吾,是,中材伊吾3,GWBD-A3,7,9,0.5,0.5,3
华南,中材玉溪,否,中材玉溪1,GW93,3,3,1.890269922258364,0.5,3
西北西部,重通昌吉,否,重通昌吉1,GW99,11,11,0.5574628792523443,0.5,3
西北西部,重通昌吉,是,重通昌吉2,GW99A,1,10,2.1349045617509983,0.5,3
西北西部,重通昌吉,否,重通昌吉3,GWBD-A2,1,12,2.3607990485978285,0.5,3
西北西部,重通昌吉,否,重通昌吉4,GWBD-A2,1,12,0.5733216621135748,0.5,3
西北西部,重通昌吉,是,重通昌吉5,GWBD-A2,1,12,1.6300547095229956,0.5,3
东北,重通大安,是,重通大安1,GWBD-A2,1,12,2.9190166685770587,0.5,3
东北,重通大安,是,重通大安2,GWBD-A2,2,12,0.8731389130785212,0.5,3
华东南北部,重通如东,否,重通如东1,GW110.5,6,10,1.6258348677031254,0.5,3
华东南北部,重通如东,否,重通如东2,GW110.5,6,10,1.376510869994889,0.5,3
华东南北部,重通如东,否,重通如东3,GW76,1,6,1.221180288569112,0.5,3
华东南北部,重通如东,否,重通如东4,GW76,11,12,0.9304707434434764,0.5,3
华东南北部,重通如东,否,重通如东5,GW76,11,12,1.0654063065239228,0.5,3
华东南北部,重通如东,否,重通如东6,GW81,1,12,1.1315290493015806,0.5,3
华东南北部,重通如东,否,重通如东7,GW81,1,12,1.1404930117692385,0.5,3
华东南北部,重通如东,否,重通如东8,GW81,1,12,1.3014091708393016,0.5,3
华东南北部,重通如东,是,重通如东9,GW86,10,12,0.5738779043252348,0.5,3
华东南北部,重通如东,是,重通如东10,GWBD-D,4,10,0.9593048041664796,0.5,3
华东南北部,重通如东,是,重通如东11,GW83.3,7,9,2.1135988495299616,0.5,3
西北东部,重通武威,否,重通武威1,GW83.4,1,1,1.0090685780114246,0.5,3
西北东部,重通武威,否,重通武威2,GW99A,1,1,0.9928464185174258,0.5,3
西北东部,重通武威,否,重通武威3,GWBD-A2,1,12,0.7701006517043388,0.5,3
西北东部,重通武威,否,重通武威4,GWBD-A2,1,12,2.1830368275462857,0.5,3
西北东部,重通武威,是,重通武威5,GWBD-A3,2,12,0.6427854133739357,0.5,3
西北东部,重通武威,是,重通武威6,GWBD-A3,2,12,0.7212222730295578,0.5,3

1 区域名 工厂名 是否新工厂 产线ID 生产型号 开始月份 结束月份 磨合系数 系数最小值 系数最大值
2 西北东部 艾郎玉门 艾郎玉门1 GWBD-A2 1 12 1.07380061238005 0.5 3
3 西北东部 艾郎玉门 艾郎玉门2 GWBD-A2 1 12 1.3086756673917384 0.5 3
4 华北 艾郎张北 艾郎张北1 GWBD-A2 1 12 1.0005776141701213 0.5 3
5 华北 艾郎张北 艾郎张北2 GWBD-A2 1 12 1.2558624416001731 0.5 3
6 西北西部 时代巴里坤 时代巴里坤1 GWBD-A2 5 2 12 2.877701634836078 0.5 3
7 西北西部 时代巴里坤 时代巴里坤2 GWBD-A2 5 2 12 2.148476381702223 0.5 3
8 西北西部 时代巴里坤 时代巴里坤3 GW99A 3 1 4 2.8216375801071467 0.5 3
9 西北西部 时代巴里坤 时代巴里坤4 GW99A 6 1 9 3.0 0.5 3
10 华南 时代百色 时代百色1 GWBD-A3 5 2 12 1.2209565088767191 0.5 3
11 华南 时代百色 时代百色2 GWBD-A3 5 2 12 2.13505523373714 0.5 3
12 华东南北部 时代射阳 时代射阳1 GW130 9 6 12 2.977100640862942 0.5 3
13 华东南北部 时代射阳 时代射阳2 GW130 9 8 12 1.4108697454394656 0.5 3
14 东北 时代松原 时代松原1 GWBD-A3 6 4 12 1.085052120762868 0.5 3
15 东北 时代松原 时代松原2 GWBD-A3 6 4 12 1.4804124608992453 0.5 3
16 华东南南部 时代株洲 时代株洲1 GW99A 4 2 12 0.8320501509514534 0.5 3
17 华东南南部 时代株洲 时代株洲2 GW99A 4 2 12 1.4984461065581405 0.5 3
18 华东南北部 双瑞大丰 双瑞大丰1 GW110.5 2 11 0.6454103640653669 0.5 3
19 华东南北部 双瑞大丰 双瑞大丰2 GW110.5 2 11 0.6530684517974175 0.5 3
20 华东南北部 双瑞大丰 双瑞大丰3 GW93 1 1 2.085029344273428 0.5 3
21 华东南北部 双瑞东营 双瑞东营1 GWBD-A2 10 6 12 2.925213291566798 0.5 3
22 华东南北部 双瑞东营 双瑞东营2 GWBD-A2 10 6 12 2.975043017037072 0.5 3
23 华东南北部 双瑞东营 双瑞东营3 GWBD-A2 10 7 12 2.9397351966791594 0.5 3
24 华东南北部 双瑞东营 双瑞东营4 GWBD-A2 10 7 12 2.616967714504933 0.5 3
25 华东南北部 天顺濮阳 天顺濮阳1 GW99A 8 12 2.790744177175357 0.5 3
26 华东南北部 天顺濮阳 天顺濮阳2 GW99A 8 12 0.6411549575195108 0.5 3
27 华东南北部 天顺沙洋 天顺沙洋1 GW93 2 2 0.8340035880432236 0.5 3
28 华东南北部 天顺沙洋 天顺沙洋2 GW93 5 5 1.1301574312833809 0.5 3
29 华东南北部 天顺沙洋 天顺沙洋3 GW93 11 11 2.025384406078239 0.5 3
30 华东南北部 天顺沙洋 天顺沙洋4 GW99 10 11 1.0429815540113208 0.5 3
31 华东南北部 天顺沙洋 天顺沙洋5 GW99A 5 12 1.123628778712014 0.5 3
32 华东南北部 天顺沙洋 天顺沙洋6 GW99A 5 12 0.979164298651741 0.5 3
33 东北 中材白城 中材白城1 GWBD-A3 8 8 1.763581199916676 0.5 3
34 华东南北部 中材阜宁 中材阜宁1 GW110.5 4 1 11 2.959144662478874 0.5 3
35 华东南北部 中材阜宁 中材阜宁2 GW110.5 4 1 11 2.92540109166913 0.5 3
36 华东南北部 中材阜宁 中材阜宁3 GW110.5 4 1 11 1.1010708194893164 0.5 3
37 华东南北部 中材阜宁 中材阜宁4 GW83.4 1 11 2.694547075119285 0.5 3
38 华东南北部 中材阜宁 中材阜宁5 GW93 6 8 2.3745893939183 0.5 3
39 华东南北部 中材阜宁 中材阜宁6 GW99A 1 7 1.8223796976955784 0.5 3
40 华东南北部 中材阜宁 中材阜宁7 GW99A 1 7 2.468575784243612 0.5 3
41 华东南北部 中材阜宁 中材阜宁8 SI90.2 1 12 0.7272902159645727 0.5 3
42 华东南北部 中材阜宁 中材阜宁9 SI90.2 1 12 2.181467973934617 0.5 3
43 华东南北部 中材阜宁 中材阜宁10 SI90.2 3 1 12 1.5244257562540415 0.5 3
44 华东南北部 中材阜宁 中材阜宁11 SI90.2 3 1 12 1.4259873327753854 0.5 3
45 华东南北部 中材阜宁 中材阜宁12 SI90.2 3 2 12 1.253548054276315 0.5 3
46 华东南北部 中材阜宁 中材阜宁13 SI90.2 3 2 12 0.6644941460935946 0.5 3
47 西北西部 中材哈密 中材哈密1 GWBD-A3 7 11 2.3686587068604954 0.5 3
48 西北西部 中材哈密 中材哈密2 GWBD-A3 7 11 1.1015355190681149 0.5 3
49 华北 中材邯郸 中材邯郸1 GW99A 1 12 1.925686336774216 0.5 3
50 华北 中材邯郸 中材邯郸2 GW99A 1 12 1.166441845882635 0.5 3
51 华北 中材邯郸 中材邯郸3 GWBD-A2 4 2 12 2.1539372175131355 0.5 3
52 华北 中材邯郸 中材邯郸4 GWBD-A2 4 3 12 0.6366978576009893 0.5 3
53 西北东部 中材酒泉 中材酒泉1 GW99 1 12 1.1568681400683591 0.5 3
54 西北东部 中材酒泉 中材酒泉2 GW99 1 12 1.1064271136987875 0.5 3
55 西北东部 中材酒泉 中材酒泉3 GWBD-A2 2 1 12 0.9954163068389692 0.5 3
56 西北东部 中材酒泉 中材酒泉4 GWBD-A2 2 1 12 1.2193407437521984 0.5 3
57 西北东部 中材酒泉 中材酒泉5 GWBD-A2 2 12 0.959238907870878 0.5 3
58 西北东部 中材酒泉 中材酒泉6 GWBD-A2 2 3 12 1.218588133158403 0.5 3
59 西北东部 中材酒泉 中材酒泉7 SI90.2 1 1 1.2323208798346863 0.5 3
60 西北东部 中材酒泉 中材酒泉8 SI90.2 3 1 4 1.1083005779929354 0.5 3
61 西北东部 中材酒泉 中材酒泉9 SI90.2 3 4 1.0979168774354644 0.5 3
62 西北东部 中材酒泉 中材酒泉10 SI90.2 9 9 0.6134714785903335 0.5 3
63 西北东部 中材酒泉 中材酒泉11 SI90.2 4 12 0.9060377208252096 0.5 3
64 西北东部 中材酒泉 中材酒泉12 SI90.2 4 12 0.8588420476065148 0.5 3
65 华东南北部 中材连云港 中材连云港1 GW93 4 1 4 0.6959224018718012 0.5 3
66 华东南北部 中材连云港 中材连云港2 GW93 1 12 2.388840113565708 0.5 3
67 华东南北部 中材连云港 中材连云港3 GW93 1 12 2.023924901276327 0.5 3
68 华东南南部 中材萍乡 中材萍乡1 GW83.4 2 1 3 2.7904582895129884 0.5 3
69 华东南南部 中材萍乡 中材萍乡2 GW93 1 10 1.6925578180285972 0.5 3
70 华东南南部 中材萍乡 中材萍乡3 GW93 1 10 0.8982597799795793 0.5 3
71 华东南南部 中材萍乡 中材萍乡4 GWBD-A3 2 1 12 1.3711168227538089 0.5 3
72 华东南南部 中材萍乡 中材萍乡5 GWBD-A3 2 1 12 1.219691503974376 0.5 3
73 东北 中材锡林 中材锡林1 GW99 1 12 2.9557253831441828 0.5 3
74 东北 中材锡林 中材锡林2 GW99 1 12 2.462362813243516 0.5 3
75 东北 中材锡林 中材锡林3 GWBD-A2 2 1 12 0.5982166642652442 0.5 3
76 东北 中材锡林 中材锡林4 GWBD-A2 2 1 12 2.644080826558284 0.5 3
77 东北 中材锡林 中材锡林5 GWBD-A2 2 12 0.5145312169222124 0.5 3
78 东北 中材锡林 中材锡林6 SI90.2 1 1 0.9769481674143102 0.5 3
79 东北 中材锡林 中材锡林7 SI90.2 1 1 1.238170985113167 0.5 3
80 东北 中材兴安盟 中材兴安盟1 GW93 1 9 1.0683346058426026 0.5 3
81 东北 中材兴安盟 中材兴安盟2 GW93 1 9 0.942117080768859 0.5 3
82 华南 中材阳江 中材阳江1 GW130 11 1 12 1.633431132348781 0.5 3
83 华南 中材阳江 中材阳江2 GW130 11 12 1.081038974714554 0.5 3
84 华南 中材阳江 中材阳江3 SI122 1 12 2.975783026146584 0.5 3
85 华南 中材阳江 中材阳江4 SI122 1 12 1.6018405358345469 0.5 3
86 西北西部 中材伊吾 中材伊吾1 GWBD-A2 3 1 12 2.1810444878994515 0.5 3
87 西北西部 中材伊吾 中材伊吾2 GWBD-A2 3 1 12 1.613919507763036 0.5 3
88 西北西部 中材伊吾 中材伊吾3 GWBD-A3 7 9 0.5 0.5 3
89 华南 中材玉溪 中材玉溪1 GW93 3 3 1.890269922258364 0.5 3
90 西北西部 重通昌吉 重通昌吉1 GW99 11 11 0.5574628792523443 0.5 3
91 西北西部 重通昌吉 重通昌吉2 GW99A 2 1 10 2.1349045617509983 0.5 3
92 西北西部 重通昌吉 重通昌吉3 GWBD-A2 1 12 2.3607990485978285 0.5 3
93 西北西部 重通昌吉 重通昌吉4 GWBD-A2 1 12 0.5733216621135748 0.5 3
94 西北西部 重通昌吉 重通昌吉5 GWBD-A2 1 12 1.6300547095229956 0.5 3
95 东北 重通大安 重通大安1 GWBD-A2 3 1 12 2.9190166685770587 0.5 3
96 东北 重通大安 重通大安2 GWBD-A2 4 2 12 0.8731389130785212 0.5 3
97 华东南北部 重通如东 重通如东1 GW110.5 6 10 1.6258348677031254 0.5 3
98 华东南北部 重通如东 重通如东2 GW110.5 6 10 1.376510869994889 0.5 3
99 华东南北部 重通如东 重通如东3 GW76 1 6 1.221180288569112 0.5 3
100 华东南北部 重通如东 重通如东4 GW76 1 11 6 12 0.9304707434434764 0.5 3
101 华东南北部 重通如东 重通如东5 GW76 11 12 1.0654063065239228 0.5 3
102 华东南北部 重通如东 重通如东6 GW76 GW81 11 1 12 1.1315290493015806 0.5 3
103 华东南北部 重通如东 重通如东7 GW81 1 12 1.1404930117692385 0.5 3
104 华东南北部 重通如东 重通如东8 GW81 1 12 1.3014091708393016 0.5 3
105 华东南北部 重通如东 重通如东9 GW86 10 12 0.5738779043252348 0.5 3
106 华东南北部 重通如东 重通如东10 GWBD-D 10 4 10 0.9593048041664796 0.5 3
107 华东南北部 重通如东 重通如东11 GW83.3 7 9 2.1135988495299616 0.5 3
108 西北东部 重通武威 重通武威1 GW83.4 1 1 1.0090685780114246 0.5 3
109 西北东部 重通武威 重通武威2 GW99A 1 1 0.9928464185174258 0.5 3
110 西北东部 重通武威 重通武威3 GWBD-A2 1 12 0.7701006517043388 0.5 3
111 西北东部 重通武威 重通武威4 GWBD-A2 1 12 2.1830368275462857 0.5 3
112 西北东部 重通武威 重通武威5 GWBD-A3 4 2 12 0.6427854133739357 0.5 3
113 西北东部 重通武威 重通武威6 GWBD-A3 4 2 12 0.7212222730295578 0.5 3

View File

@ -1,106 +0,0 @@
区域名,工厂名,是否新工厂,产线ID,生产型号,开始月份,结束月份
西北东部,艾郎玉门,否,艾郎玉门1,GWBD-A2,1,12
西北东部,艾郎玉门,否,艾郎玉门2,GWBD-A2,1,12
华北,艾郎张北,否,艾郎张北1,GWBD-A2,1,12
华北,艾郎张北,否,艾郎张北2,GWBD-A2,1,12
西北西部,时代巴里坤,是,时代巴里坤1,GWBD-A2,5,12
西北西部,时代巴里坤,是,时代巴里坤2,GWBD-A2,5,12
西北西部,时代巴里坤,是,时代巴里坤3,GW99A,3,9
华南,时代百色,是,时代百色1,GWBD-A3,5,12
华南,时代百色,是,时代百色2,GWBD-A3,5,12
华东南北部,时代射阳,是,时代射阳1,GW130,9,12
华东南北部,时代射阳,是,时代射阳2,GW130,9,12
东北,时代松原,是,时代松原1,GWBD-A3,5,12
东北,时代松原,是,时代松原2,GWBD-A3,5,12
华东南南部,时代株洲,否,时代株洲1,GW99A,4,12
华东南南部,时代株洲,否,时代株洲2,GW99A,4,12
华东南北部,双瑞大丰,否,双瑞大丰1,GW110.5,2,11
华东南北部,双瑞大丰,否,双瑞大丰2,GW110.5,2,11
华东南北部,双瑞大丰,否,双瑞大丰3,GW93,1,1
华东南北部,双瑞东营,是,双瑞东营1,GWBD-A2,9,12
华东南北部,双瑞东营,是,双瑞东营2,GWBD-A2,9,12
华东南北部,双瑞东营,是,双瑞东营3,GWBD-A2,9,12
华东南北部,双瑞东营,是,双瑞东营4,GWBD-A2,9,12
华东南北部,天顺濮阳,是,天顺濮阳1,GW99A,8,12
华东南北部,天顺濮阳,是,天顺濮阳2,GW99A,8,12
华东南北部,天顺沙洋,否,天顺沙洋1,GW93,2,11
华东南北部,天顺沙洋,否,天顺沙洋2,GW99,10,11
华东南北部,天顺沙洋,是,天顺沙洋3,GW99A,5,12
华东南北部,天顺沙洋,是,天顺沙洋4,GW99A,5,12
东北,中材白城,是,中材白城1,GWBD-A3,8,8
华东南北部,中材阜宁,否,中材阜宁1,GW110.5,4,12
华东南北部,中材阜宁,是,中材阜宁2,GW110.5,4,12
华东南北部,中材阜宁,是,中材阜宁3,GW110.5,4,12
华东南北部,中材阜宁,否,中材阜宁4,GW83.4,1,11
华东南北部,中材阜宁,否,中材阜宁5,GW93,1,8
华东南北部,中材阜宁,否,中材阜宁6,GW99A,1,7
华东南北部,中材阜宁,否,中材阜宁7,GW99A,1,7
华东南北部,中材阜宁,否,中材阜宁8,SI90.2,1,12
华东南北部,中材阜宁,否,中材阜宁9,SI90.2,1,12
华东南北部,中材阜宁,否,中材阜宁10,SI90.2,1,12
华东南北部,中材阜宁,否,中材阜宁11,SI90.2,1,12
华东南北部,中材阜宁,是,中材阜宁12,SI90.2,3,12
华东南北部,中材阜宁,是,中材阜宁13,SI90.2,3,12
西北西部,中材哈密,是,中材哈密1,GWBD-A3,7,12
西北西部,中材哈密,是,中材哈密2,GWBD-A3,7,12
华北,中材邯郸,否,中材邯郸1,GW99A,1,12
华北,中材邯郸,否,中材邯郸2,GW99A,1,12
华北,中材邯郸,是,中材邯郸3,GWBD-A2,4,12
华北,中材邯郸,是,中材邯郸4,GWBD-A2,4,12
西北东部,中材酒泉,否,中材酒泉1,GW99,1,12
西北东部,中材酒泉,否,中材酒泉2,GW99,1,12
西北东部,中材酒泉,否,中材酒泉3,GWBD-A2,1,12
西北东部,中材酒泉,否,中材酒泉4,GWBD-A2,1,12
西北东部,中材酒泉,否,中材酒泉5,GWBD-A2,1,12
西北东部,中材酒泉,是,中材酒泉6,GWBD-A2,2,12
西北东部,中材酒泉,否,中材酒泉7,SI90.2,1,12
西北东部,中材酒泉,否,中材酒泉8,SI90.2,1,12
西北东部,中材酒泉,否,中材酒泉9,SI90.2,4,12
西北东部,中材酒泉,否,中材酒泉10,SI90.2,4,12
华东南北部,中材连云港,否,中材连云港1,GW93,4,4
华东南北部,中材连云港,否,中材连云港2,GW93,1,12
华东南北部,中材连云港,否,中材连云港3,GW93,1,12
华东南南部,中材萍乡,否,中材萍乡1,GW83.4,2,6
华东南南部,中材萍乡,否,中材萍乡2,GW93,1,10
华东南南部,中材萍乡,否,中材萍乡3,GW93,1,10
华东南南部,中材萍乡,否,中材萍乡4,GWBD-A3,2,12
华东南南部,中材萍乡,否,中材萍乡5,GWBD-A3,2,12
东北,中材锡林,否,中材锡林1,GW99,1,12
东北,中材锡林,否,中材锡林2,GW99,1,12
东北,中材锡林,否,中材锡林3,GWBD-A2,1,12
东北,中材锡林,否,中材锡林4,GWBD-A2,1,12
东北,中材锡林,是,中材锡林5,GWBD-A2,4,12
东北,中材锡林,否,中材锡林6,SI90.2,1,1
东北,中材锡林,否,中材锡林7,SI90.2,1,1
东北,中材兴安盟,否,中材兴安盟1,GW93,1,9
东北,中材兴安盟,否,中材兴安盟2,GW93,1,9
华南,中材阳江,是,中材阳江1,GW130,11,12
华南,中材阳江,是,中材阳江2,GW130,11,12
华南,中材阳江,否,中材阳江3,SI122,1,12
华南,中材阳江,否,中材阳江4,SI122,1,12
西北西部,中材伊吾,否,中材伊吾1,GWBD-A2,3,12
西北西部,中材伊吾,否,中材伊吾2,GWBD-A2,3,12
西北西部,中材伊吾,是,中材伊吾3,GWBD-A3,7,9
华南,中材玉溪,否,中复玉溪1,GW93,3,3
西北西部,重通昌吉,否,重通昌吉1,GW99,11,11
西北西部,重通昌吉,否,重通昌吉2,GW99A,2,10
西北西部,重通昌吉,否,重通昌吉3,GWBD-A2,1,12
西北西部,重通昌吉,否,重通昌吉4,GWBD-A2,1,12
西北西部,重通昌吉,是,重通昌吉5,GWBD-A2,11,12
东北,重通大安,是,重通大安1,GWBD-A2,3,12
东北,重通大安,是,重通大安2,GWBD-A2,4,12
华东南北部,重通如东,是,重通如东1,GW110.5,6,10
华东南北部,重通如东,是,重通如东2,GW110.5,6,10
华东南北部,重通如东,否,重通如东3,GW76,1,12
华东南北部,重通如东,否,重通如东4,GW76,1,12
华东南北部,重通如东,否,重通如东5,GW81,1,12
华东南北部,重通如东,否,重通如东6,GW81,1,12
华东南北部,重通如东,否,重通如东7,GW86,3,12
华东南北部,重通如东,是,重通如东8,GWBD-D,6,11
华东南北部,重通如东,否,重通如东9,GW83.3,7,9
西北东部,重通武威,否,重通武威1,GW83.4,1,1
西北东部,重通武威,否,重通武威2,GW99A,1,1
西北东部,重通武威,否,重通武威3,GWBD-A2,1,12
西北东部,重通武威,否,重通武威4,GWBD-A2,1,12
西北东部,重通武威,否,重通武威5,GWBD-A3,4,12
西北东部,重通武威,否,重通武威6,GWBD-A3,4,12
1 区域名 工厂名 是否新工厂 产线ID 生产型号 开始月份 结束月份
2 西北东部 艾郎玉门 艾郎玉门1 GWBD-A2 1 12
3 西北东部 艾郎玉门 艾郎玉门2 GWBD-A2 1 12
4 华北 艾郎张北 艾郎张北1 GWBD-A2 1 12
5 华北 艾郎张北 艾郎张北2 GWBD-A2 1 12
6 西北西部 时代巴里坤 时代巴里坤1 GWBD-A2 5 12
7 西北西部 时代巴里坤 时代巴里坤2 GWBD-A2 5 12
8 西北西部 时代巴里坤 时代巴里坤3 GW99A 3 9
9 华南 时代百色 时代百色1 GWBD-A3 5 12
10 华南 时代百色 时代百色2 GWBD-A3 5 12
11 华东南北部 时代射阳 时代射阳1 GW130 9 12
12 华东南北部 时代射阳 时代射阳2 GW130 9 12
13 东北 时代松原 时代松原1 GWBD-A3 5 12
14 东北 时代松原 时代松原2 GWBD-A3 5 12
15 华东南南部 时代株洲 时代株洲1 GW99A 4 12
16 华东南南部 时代株洲 时代株洲2 GW99A 4 12
17 华东南北部 双瑞大丰 双瑞大丰1 GW110.5 2 11
18 华东南北部 双瑞大丰 双瑞大丰2 GW110.5 2 11
19 华东南北部 双瑞大丰 双瑞大丰3 GW93 1 1
20 华东南北部 双瑞东营 双瑞东营1 GWBD-A2 9 12
21 华东南北部 双瑞东营 双瑞东营2 GWBD-A2 9 12
22 华东南北部 双瑞东营 双瑞东营3 GWBD-A2 9 12
23 华东南北部 双瑞东营 双瑞东营4 GWBD-A2 9 12
24 华东南北部 天顺濮阳 天顺濮阳1 GW99A 8 12
25 华东南北部 天顺濮阳 天顺濮阳2 GW99A 8 12
26 华东南北部 天顺沙洋 天顺沙洋1 GW93 2 11
27 华东南北部 天顺沙洋 天顺沙洋2 GW99 10 11
28 华东南北部 天顺沙洋 天顺沙洋3 GW99A 5 12
29 华东南北部 天顺沙洋 天顺沙洋4 GW99A 5 12
30 东北 中材白城 中材白城1 GWBD-A3 8 8
31 华东南北部 中材阜宁 中材阜宁1 GW110.5 4 12
32 华东南北部 中材阜宁 中材阜宁2 GW110.5 4 12
33 华东南北部 中材阜宁 中材阜宁3 GW110.5 4 12
34 华东南北部 中材阜宁 中材阜宁4 GW83.4 1 11
35 华东南北部 中材阜宁 中材阜宁5 GW93 1 8
36 华东南北部 中材阜宁 中材阜宁6 GW99A 1 7
37 华东南北部 中材阜宁 中材阜宁7 GW99A 1 7
38 华东南北部 中材阜宁 中材阜宁8 SI90.2 1 12
39 华东南北部 中材阜宁 中材阜宁9 SI90.2 1 12
40 华东南北部 中材阜宁 中材阜宁10 SI90.2 1 12
41 华东南北部 中材阜宁 中材阜宁11 SI90.2 1 12
42 华东南北部 中材阜宁 中材阜宁12 SI90.2 3 12
43 华东南北部 中材阜宁 中材阜宁13 SI90.2 3 12
44 西北西部 中材哈密 中材哈密1 GWBD-A3 7 12
45 西北西部 中材哈密 中材哈密2 GWBD-A3 7 12
46 华北 中材邯郸 中材邯郸1 GW99A 1 12
47 华北 中材邯郸 中材邯郸2 GW99A 1 12
48 华北 中材邯郸 中材邯郸3 GWBD-A2 4 12
49 华北 中材邯郸 中材邯郸4 GWBD-A2 4 12
50 西北东部 中材酒泉 中材酒泉1 GW99 1 12
51 西北东部 中材酒泉 中材酒泉2 GW99 1 12
52 西北东部 中材酒泉 中材酒泉3 GWBD-A2 1 12
53 西北东部 中材酒泉 中材酒泉4 GWBD-A2 1 12
54 西北东部 中材酒泉 中材酒泉5 GWBD-A2 1 12
55 西北东部 中材酒泉 中材酒泉6 GWBD-A2 2 12
56 西北东部 中材酒泉 中材酒泉7 SI90.2 1 12
57 西北东部 中材酒泉 中材酒泉8 SI90.2 1 12
58 西北东部 中材酒泉 中材酒泉9 SI90.2 4 12
59 西北东部 中材酒泉 中材酒泉10 SI90.2 4 12
60 华东南北部 中材连云港 中材连云港1 GW93 4 4
61 华东南北部 中材连云港 中材连云港2 GW93 1 12
62 华东南北部 中材连云港 中材连云港3 GW93 1 12
63 华东南南部 中材萍乡 中材萍乡1 GW83.4 2 6
64 华东南南部 中材萍乡 中材萍乡2 GW93 1 10
65 华东南南部 中材萍乡 中材萍乡3 GW93 1 10
66 华东南南部 中材萍乡 中材萍乡4 GWBD-A3 2 12
67 华东南南部 中材萍乡 中材萍乡5 GWBD-A3 2 12
68 东北 中材锡林 中材锡林1 GW99 1 12
69 东北 中材锡林 中材锡林2 GW99 1 12
70 东北 中材锡林 中材锡林3 GWBD-A2 1 12
71 东北 中材锡林 中材锡林4 GWBD-A2 1 12
72 东北 中材锡林 中材锡林5 GWBD-A2 4 12
73 东北 中材锡林 中材锡林6 SI90.2 1 1
74 东北 中材锡林 中材锡林7 SI90.2 1 1
75 东北 中材兴安盟 中材兴安盟1 GW93 1 9
76 东北 中材兴安盟 中材兴安盟2 GW93 1 9
77 华南 中材阳江 中材阳江1 GW130 11 12
78 华南 中材阳江 中材阳江2 GW130 11 12
79 华南 中材阳江 中材阳江3 SI122 1 12
80 华南 中材阳江 中材阳江4 SI122 1 12
81 西北西部 中材伊吾 中材伊吾1 GWBD-A2 3 12
82 西北西部 中材伊吾 中材伊吾2 GWBD-A2 3 12
83 西北西部 中材伊吾 中材伊吾3 GWBD-A3 7 9
84 华南 中材玉溪 中复玉溪1 GW93 3 3
85 西北西部 重通昌吉 重通昌吉1 GW99 11 11
86 西北西部 重通昌吉 重通昌吉2 GW99A 2 10
87 西北西部 重通昌吉 重通昌吉3 GWBD-A2 1 12
88 西北西部 重通昌吉 重通昌吉4 GWBD-A2 1 12
89 西北西部 重通昌吉 重通昌吉5 GWBD-A2 11 12
90 东北 重通大安 重通大安1 GWBD-A2 3 12
91 东北 重通大安 重通大安2 GWBD-A2 4 12
92 华东南北部 重通如东 重通如东1 GW110.5 6 10
93 华东南北部 重通如东 重通如东2 GW110.5 6 10
94 华东南北部 重通如东 重通如东3 GW76 1 12
95 华东南北部 重通如东 重通如东4 GW76 1 12
96 华东南北部 重通如东 重通如东5 GW81 1 12
97 华东南北部 重通如东 重通如东6 GW81 1 12
98 华东南北部 重通如东 重通如东7 GW86 3 12
99 华东南北部 重通如东 重通如东8 GWBD-D 6 11
100 华东南北部 重通如东 重通如东9 GW83.3 7 9
101 西北东部 重通武威 重通武威1 GW83.4 1 1
102 西北东部 重通武威 重通武威2 GW99A 1 1
103 西北东部 重通武威 重通武威3 GWBD-A2 1 12
104 西北东部 重通武威 重通武威4 GWBD-A2 1 12
105 西北东部 重通武威 重通武威5 GWBD-A3 4 12
106 西北东部 重通武威 重通武威6 GWBD-A3 4 12

View File

@ -1,28 +0,0 @@
工厂,1月,2月,3月,4月,5月,6月,7月,8月,9月,10月,11月,12月,全年
艾郎玉门,2,6,12,13,12,12,9,14,12,12,12,12,128
艾郎张北,3,6,12,14,12,12,12,12,12,12,12,12,131
时代巴里坤,0,0,7,2,2,9,6,16,14,13,12,12,93
时代百色,0,0,0,0,5,9,12,8,15,13,12,12,86
时代射阳,0,0,0,0,0,0,0,0,1,0,6,6,13
时代松原,0,0,0,0,0,6,5,10,15,16,12,12,76
时代株洲,0,0,0,7,9,14,14,14,14,14,14,14,114
双瑞大丰,1,2,11,6,7,10,8,10,10,10,6,0,81
双瑞东营,0,0,0,0,0,0,0,0,3,15,14,14,46
天顺濮阳,2,0,0,0,0,0,0,12,11,9,10,11,55
天顺沙洋,1,4,0,0,9,13,13,13,13,17,19,11,113
中材白城,0,0,0,0,0,0,0,3,0,0,0,0,3
中材阜宁,34,18,31,50,42,45,52,48,46,43,49,31,489
中材哈密,0,0,0,0,0,0,8,9,9,6,7,0,39
中材邯郸,12,6,3,17,18,27,18,21,23,19,20,14,198
中材酒泉,11,20,52,56,43,45,50,53,59,52,52,40,533
中材连云港,6,9,13,16,13,12,5,5,5,0,0,7,91
中材萍乡,2,17,21,19,24,27,29,28,17,13,14,5,216
中材锡林,28,6,17,25,36,32,27,36,27,30,32,21,317
中材兴安盟,2,8,10,18,14,18,12,11,14,0,0,0,107
中材阳江,4,1,5,5,5,5,5,5,5,5,7,7,59
中材伊吾,0,0,10,14,5,4,8,16,16,8,15,10,106
中材玉溪,0,0,3,0,0,0,0,0,0,0,0,0,3
重通昌吉,13,9,19,21,22,23,23,28,27,18,23,19,245
重通大安,0,0,2,9,6,12,11,10,11,11,13,12,97
重通如东,24,7,24,19,23,26,27,24,30,33,34,35,306
重通武威,22,7,14,27,31,29,29,28,30,28,29,29,303
1 工厂 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月 全年
2 艾郎玉门 2 6 12 13 12 12 9 14 12 12 12 12 128
3 艾郎张北 3 6 12 14 12 12 12 12 12 12 12 12 131
4 时代巴里坤 0 0 7 2 2 9 6 16 14 13 12 12 93
5 时代百色 0 0 0 0 5 9 12 8 15 13 12 12 86
6 时代射阳 0 0 0 0 0 0 0 0 1 0 6 6 13
7 时代松原 0 0 0 0 0 6 5 10 15 16 12 12 76
8 时代株洲 0 0 0 7 9 14 14 14 14 14 14 14 114
9 双瑞大丰 1 2 11 6 7 10 8 10 10 10 6 0 81
10 双瑞东营 0 0 0 0 0 0 0 0 3 15 14 14 46
11 天顺濮阳 2 0 0 0 0 0 0 12 11 9 10 11 55
12 天顺沙洋 1 4 0 0 9 13 13 13 13 17 19 11 113
13 中材白城 0 0 0 0 0 0 0 3 0 0 0 0 3
14 中材阜宁 34 18 31 50 42 45 52 48 46 43 49 31 489
15 中材哈密 0 0 0 0 0 0 8 9 9 6 7 0 39
16 中材邯郸 12 6 3 17 18 27 18 21 23 19 20 14 198
17 中材酒泉 11 20 52 56 43 45 50 53 59 52 52 40 533
18 中材连云港 6 9 13 16 13 12 5 5 5 0 0 7 91
19 中材萍乡 2 17 21 19 24 27 29 28 17 13 14 5 216
20 中材锡林 28 6 17 25 36 32 27 36 27 30 32 21 317
21 中材兴安盟 2 8 10 18 14 18 12 11 14 0 0 0 107
22 中材阳江 4 1 5 5 5 5 5 5 5 5 7 7 59
23 中材伊吾 0 0 10 14 5 4 8 16 16 8 15 10 106
24 中材玉溪 0 0 3 0 0 0 0 0 0 0 0 0 3
25 重通昌吉 13 9 19 21 22 23 23 28 27 18 23 19 245
26 重通大安 0 0 2 9 6 12 11 10 11 11 13 12 97
27 重通如东 24 7 24 19 23 26 27 24 30 33 34 35 306
28 重通武威 22 7 14 27 31 29 29 28 30 28 29 29 303

View File

@ -0,0 +1,28 @@
工厂中文名,工厂英文名,工厂平均磨合系数,最小误差
艾郎玉门,AilangYumen,1.1912381398858942,0.0383592943400758
艾郎张北,AilangZhangbei,1.1282200278851473,0.0383834069943968
时代巴里坤,ShidaiBalikun,2.711953899161362,0.0372831200518159
时代百色,ShidaiBaise,1.6780058713069297,0.0383834069943968
时代射阳,ShidaiSheyang,2.193985193151204,0.0381459900903132
时代松原,ShidaiSongyuan,1.2827322908310568,0.0352284680862539
时代株洲,ShidaiZhuzhou,1.1652481287547969,0.0351743203712874
双瑞大丰,ShuangruiDafeng,1.1278360533787375,0.0345299766068848
双瑞东营,ShuangruiDongying,2.8642398049469904,0.0345680805544538
天顺濮阳,TianshunPuyang,1.715949567347434,0.0345299766068848
天顺沙洋,TianshunShayang,1.1892200094633198,0.0350377531255757
中材白城,ZhongcaiBaicheng,1.763581199916676,0.0352284680862539
中材阜宁,ZhongcaiFuning,1.855609384916356,0.0344731713353556
中材哈密,ZhongcaiHami,1.7350971129643051,0.0344731713353556
中材邯郸,ZhongcaiHandan,1.4706908144427435,0.03437964346405
中材酒泉,ZhongcaiJiuquan,1.0393974106393948,0.0335524068644557
中材连云港,ZhongcaiLianyungang,1.7028958055712788,0.034108309859383
中材萍乡,ZhongcaiPingxiang,1.59441684284987,0.0334380950217488
中材锡林,ZhongcaiXilin,1.6271480080944165,0.0313354095754113
中材兴安盟,ZhongcaiXinganmeng,1.0052258433057308,0.0334092499773274
中材阳江,ZhongcaiYangjiang,1.8230234172611164,0.0302713991840371
中材伊吾,ZhongcaiYiwu,1.4316546652208293,0.0332345469724357
中材玉溪,ZhongcaiYuxi,1.890269922258364,0.0302713991840371
重通昌吉,ChongtongChangji,1.4513085722475485,0.0303973755004896
重通大安,ChongtongDaan,1.89607779082779,0.0301428254177608
重通如东,ChongtongRudong,1.2217832605605747,0.0383834069943968
重通武威,ChongtongWuwei,1.0531766936971614,0.028380612159397973
1 工厂中文名 工厂英文名 工厂平均磨合系数 最小误差
2 艾郎玉门 AilangYumen 1.1912381398858942 0.0383592943400758
3 艾郎张北 AilangZhangbei 1.1282200278851473 0.0383834069943968
4 时代巴里坤 ShidaiBalikun 2.711953899161362 0.0372831200518159
5 时代百色 ShidaiBaise 1.6780058713069297 0.0383834069943968
6 时代射阳 ShidaiSheyang 2.193985193151204 0.0381459900903132
7 时代松原 ShidaiSongyuan 1.2827322908310568 0.0352284680862539
8 时代株洲 ShidaiZhuzhou 1.1652481287547969 0.0351743203712874
9 双瑞大丰 ShuangruiDafeng 1.1278360533787375 0.0345299766068848
10 双瑞东营 ShuangruiDongying 2.8642398049469904 0.0345680805544538
11 天顺濮阳 TianshunPuyang 1.715949567347434 0.0345299766068848
12 天顺沙洋 TianshunShayang 1.1892200094633198 0.0350377531255757
13 中材白城 ZhongcaiBaicheng 1.763581199916676 0.0352284680862539
14 中材阜宁 ZhongcaiFuning 1.855609384916356 0.0344731713353556
15 中材哈密 ZhongcaiHami 1.7350971129643051 0.0344731713353556
16 中材邯郸 ZhongcaiHandan 1.4706908144427435 0.03437964346405
17 中材酒泉 ZhongcaiJiuquan 1.0393974106393948 0.0335524068644557
18 中材连云港 ZhongcaiLianyungang 1.7028958055712788 0.034108309859383
19 中材萍乡 ZhongcaiPingxiang 1.59441684284987 0.0334380950217488
20 中材锡林 ZhongcaiXilin 1.6271480080944165 0.0313354095754113
21 中材兴安盟 ZhongcaiXinganmeng 1.0052258433057308 0.0334092499773274
22 中材阳江 ZhongcaiYangjiang 1.8230234172611164 0.0302713991840371
23 中材伊吾 ZhongcaiYiwu 1.4316546652208293 0.0332345469724357
24 中材玉溪 ZhongcaiYuxi 1.890269922258364 0.0302713991840371
25 重通昌吉 ChongtongChangji 1.4513085722475485 0.0303973755004896
26 重通大安 ChongtongDaan 1.89607779082779 0.0301428254177608
27 重通如东 ChongtongRudong 1.2217832605605747 0.0383834069943968
28 重通武威 ChongtongWuwei 1.0531766936971614 0.028380612159397973

View File

@ -0,0 +1,28 @@
工厂中文名,工厂英文名,工厂磨合系数,最小误差
艾郎玉门,AilangYumen,1.120967033,0.0596663792039552
艾郎张北,AilangZhangbei,1.08697518,0.0833197284386998
时代巴里坤,ShidaiBalikun,1.0187437400861858,0.0842943873080955
时代百色,ShidaiBaise,0.9004477644319165,0.0596663792039552
时代射阳,ShidaiSheyang,0.8752331051886796,0.060527705646677
时代松原,ShidaiSongyuan,0.8553030538583956,0.0833197284386998
时代株洲,ShidaiZhuzhou,0.8,0.0464062054499543
双瑞大丰,ShuangruiDafeng,0.8017981082680407,0.0464062054499543
双瑞东营,ShuangruiDongying,1.4195340001397942,0.0454229212808294
天顺濮阳,TianshunPuyang,1.11666927,0.0454229212808294
天顺沙洋,TianshunShayang,1.038912690288306,0.0454229212808294
中材白城,ZhongcaiBaicheng,1.6168654948182766,0.0454229212808294
中材阜宁,ZhongcaiFuning,1.1496044423912932,0.0409542399205325
中材哈密,ZhongcaiHami,1.5404408015710631,0.0406113043924118
中材邯郸,ZhongcaiHandan,1.1753569549522398,0.0386749691227432
中材酒泉,ZhongcaiJiuquan,0.979817498,10000.0
中材连云港,ZhongcaiLianyungang,1.5319296291566933,0.039797303578411
中材萍乡,ZhongcaiPingxiang,1.11513483685553,0.0406113043924118
中材锡林,ZhongcaiXilin,1.029797177,0.0386749691227432
中材兴安盟,ZhongcaiXinganmeng,0.9871605854669068,0.0379826880566304
中材阳江,ZhongcaiYangjiang,1.2807141141229423,0.0198011028048272
中材伊吾,ZhongcaiYiwu,1.241739515,0.0198011028048272
中材玉溪,ZhongcaiYuxi,1.705602538488603,0.0198011028048272
重通昌吉,ChongtongChangji,1.07645347634814,0.0373780017376583
重通大安,ChongtongDaan,0.9935803675177745,0.0186556274325477
重通如东,ChongtongRudong,0.9317437521783424,0.0149035093013446
重通武威,ChongtongWuwei,0.8658294473858272,0.014659040410011074
1 工厂中文名 工厂英文名 工厂磨合系数 最小误差
2 艾郎玉门 AilangYumen 1.120967033 0.0596663792039552
3 艾郎张北 AilangZhangbei 1.08697518 0.0833197284386998
4 时代巴里坤 ShidaiBalikun 1.0187437400861858 0.0842943873080955
5 时代百色 ShidaiBaise 0.9004477644319165 0.0596663792039552
6 时代射阳 ShidaiSheyang 0.8752331051886796 0.060527705646677
7 时代松原 ShidaiSongyuan 0.8553030538583956 0.0833197284386998
8 时代株洲 ShidaiZhuzhou 0.8 0.0464062054499543
9 双瑞大丰 ShuangruiDafeng 0.8017981082680407 0.0464062054499543
10 双瑞东营 ShuangruiDongying 1.4195340001397942 0.0454229212808294
11 天顺濮阳 TianshunPuyang 1.11666927 0.0454229212808294
12 天顺沙洋 TianshunShayang 1.038912690288306 0.0454229212808294
13 中材白城 ZhongcaiBaicheng 1.6168654948182766 0.0454229212808294
14 中材阜宁 ZhongcaiFuning 1.1496044423912932 0.0409542399205325
15 中材哈密 ZhongcaiHami 1.5404408015710631 0.0406113043924118
16 中材邯郸 ZhongcaiHandan 1.1753569549522398 0.0386749691227432
17 中材酒泉 ZhongcaiJiuquan 0.979817498 10000.0
18 中材连云港 ZhongcaiLianyungang 1.5319296291566933 0.039797303578411
19 中材萍乡 ZhongcaiPingxiang 1.11513483685553 0.0406113043924118
20 中材锡林 ZhongcaiXilin 1.029797177 0.0386749691227432
21 中材兴安盟 ZhongcaiXinganmeng 0.9871605854669068 0.0379826880566304
22 中材阳江 ZhongcaiYangjiang 1.2807141141229423 0.0198011028048272
23 中材伊吾 ZhongcaiYiwu 1.241739515 0.0198011028048272
24 中材玉溪 ZhongcaiYuxi 1.705602538488603 0.0198011028048272
25 重通昌吉 ChongtongChangji 1.07645347634814 0.0373780017376583
26 重通大安 ChongtongDaan 0.9935803675177745 0.0186556274325477
27 重通如东 ChongtongRudong 0.9317437521783424 0.0149035093013446
28 重通武威 ChongtongWuwei 0.8658294473858272 0.014659040410011074

View File

@ -1,6 +1,6 @@
{
"holiday_days_1": 5,
"holiday_days_2": 12,
"holiday_days_1": 10,
"holiday_days_2": 6,
"holiday_days_3": 2,
"holiday_days_4": 2,
"holiday_days_5": 2,
@ -11,37 +11,6 @@
"holiday_days_10": 7,
"holiday_days_11": 2,
"holiday_days_12": 2,
"month1": 235.2024236560018,
"month2": 80.13430957369465,
"month3": 40.81815120444523,
"month4": 35.573307322844045,
"factor_AilangYumen": 1.1209670325377996,
"factor_AilangZhangbei": 1.0869751796074336,
"factor_ShidaiBalikun": 1.064344356385733,
"factor_ShidaiBaise": 0.925644739081089,
"factor_ShidaiSheyang": 2.2354053357800834,
"factor_ShidaiSongyuan": 0.8850280359740502,
"factor_ShidaiZhuzhou": 0.8,
"factor_ShuangruiDafeng": 1.3485391587995974,
"factor_ShuangruiDongying": 1.4720254802974653,
"factor_TianshunPuyang": 1.1166692701637255,
"factor_TianshunShayang": 1.060367420503057,
"factor_ZhongcaiBaicheng": 1.8461095046937892,
"factor_ZhongcaiFuning": 1.3124938381864457,
"factor_ZhongcaiHami": 1.4843503931169255,
"factor_ZhongcaiHandan": 1.210604184057787,
"factor_ZhongcaiJiuquan": 0.9798174978126551,
"factor_ZhongcaiLianyungang": 1.6036280376162886,
"factor_ZhongcaiPingxiang": 1.132040522281658,
"factor_ZhongcaiXilin": 1.0297971770720127,
"factor_ZhongcaiXinganmeng": 0.9769716839603864,
"factor_ZhongcaiYangjiang": 2.386768108574698,
"factor_ZhongcaiYiwu": 1.2417395146436796,
"factor_ZhongcaiYuxi": 1.7093165019081604,
"factor_ChongtongChangji": 1.0895870479908367,
"factor_ChongtongDaan": 1.0187316107483142,
"factor_ChongtongRudong": 1.0270541553649646,
"factor_ChongtongWuwei": 0.8811374580269447,
"product_set": [
"GWBD-A2",
"GWBD-A3",

View File

@ -0,0 +1,75 @@
import argparse
import json
import subprocess
import sys
from concurrent.futures import ProcessPoolExecutor, as_completed
from pathlib import Path
import pandas as pd
def load_factory_codes(year: int) -> list[str]:
csv_path = Path("data") / str(year) / "factory_data.csv"
encodings = ("utf-8", "utf-8-sig", "gbk")
last_error = None
df = None
for enc in encodings:
try:
df = pd.read_csv(csv_path, encoding=enc)
break
except UnicodeDecodeError as exc:
last_error = exc
continue
if df is None:
raise last_error if last_error else FileNotFoundError(f"Missing {csv_path}")
col_map = {c.strip(): c for c in df.columns}
if "工厂英文名" not in col_map:
raise ValueError(f"{csv_path} 缺少字段: 工厂英文名")
return df[col_map["工厂英文名"]].astype(str).str.strip().tolist()
def run_single(factory_code: str, python_exe: str, script_path: Path) -> tuple[str, int, str]:
cmd = [python_exe, str(script_path), "--factory", factory_code]
result = subprocess.run(cmd, capture_output=True, text=True)
output = (result.stdout or "") + (result.stderr or "")
return factory_code, result.returncode, output
def main():
parser = argparse.ArgumentParser(description="Parallel GA calibration for all factories.")
parser.add_argument("--max-workers", type=int, default=6, help="并行进程数上限(默认 6")
args = parser.parse_args()
year = json.load(open("year.json", "r", encoding="utf-8"))["year"]
codes = load_factory_codes(year)
script_path = Path("ga_calibration_by_one_factory.py")
if not script_path.exists():
raise FileNotFoundError(f"找不到 {script_path}")
python_exe = sys.executable
results = []
with ProcessPoolExecutor(max_workers=args.max_workers) as executor:
future_map = {
executor.submit(run_single, code, python_exe, script_path): code for code in codes
}
for future in as_completed(future_map):
code = future_map[future]
try:
factory, rc, out = future.result()
except Exception as exc:
results.append((code, 1, f"Exception: {exc}"))
continue
results.append((factory, rc, out))
status = "OK" if rc == 0 else f"FAIL({rc})"
print(f"[{status}] {factory}")
print("\n详细输出:")
for factory, rc, out in results:
status = "OK" if rc == 0 else f"FAIL({rc})"
print(f"--- {factory} [{status}] ---")
print(out.strip())
print("---------------")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,280 @@
import argparse
import json
import random
from pathlib import Path
from typing import List, Tuple
import pandas as pd
import time
import os
from simulation_model import SimulationModel
POP_SIZE = 20
GENERATIONS = 50
MUTATION_RATE = 0.2
MUTATION_STD = 0.05 # for factors
LOCK_TIMEOUT = 120 # seconds
STAGNATION_WINDOW = 5 # generations without improvement before injecting LHS samples
def clip(val: float, bounds: Tuple[float, float]) -> float:
lo, hi = bounds
return max(lo, min(hi, val))
def load_factory_row(csv_path: Path, factory_code: str) -> tuple[pd.Series, pd.DataFrame, dict]:
df, cols, _ = read_csv_with_encoding(csv_path, required={"工厂中文名", "工厂英文名", "工厂平均磨合系数", "最小误差"})
row = df.loc[df[cols["工厂英文名"]].astype(str).str.strip() == factory_code]
if row.empty:
raise ValueError(f"{csv_path} 中找不到工厂英文名: {factory_code}")
return row.iloc[0], df, cols
def _csv_lock_path(csv_path: Path) -> Path:
return csv_path.with_suffix(csv_path.suffix + ".lock")
def _acquire_lock(lock_path: Path, timeout: float = LOCK_TIMEOUT, interval: float = 0.5):
start = time.time()
while True:
try:
fd = os.open(lock_path, os.O_CREAT | os.O_EXCL | os.O_RDWR)
os.write(fd, str(os.getpid()).encode())
return fd
except FileExistsError:
if time.time() - start > timeout:
raise TimeoutError(f"获取锁超时: {lock_path}")
time.sleep(interval)
def _release_lock(lock_path: Path, fd: int):
try:
os.close(fd)
finally:
if lock_path.exists():
try:
lock_path.unlink()
except Exception:
pass
def update_factory_csv(csv_path: Path, factory_code: str, new_factor: float, new_error: float) -> None:
lock_path = _csv_lock_path(csv_path)
fd = _acquire_lock(lock_path)
try:
df, cols, enc = read_csv_with_encoding(csv_path, required={"工厂中文名", "工厂英文名", "工厂平均磨合系数", "最小误差"})
mask = df[cols["工厂英文名"]].astype(str).str.strip() == factory_code
if not mask.any():
raise ValueError(f"{csv_path} 中找不到工厂英文名: {factory_code}")
df.loc[mask, cols["工厂平均磨合系数"]] = float(new_factor)
df.loc[mask, cols["最小误差"]] = float(new_error)
df.to_csv(csv_path, index=False, encoding=enc)
finally:
_release_lock(lock_path, fd)
def update_production_line_csv(csv_path: Path, factory_name_cn: str, line_ids: List[str], best_genes: List[float]) -> None:
lock_path = _csv_lock_path(csv_path)
fd = _acquire_lock(lock_path)
try:
df, cols, enc = read_csv_with_encoding(csv_path, required={"工厂名", "产线ID", "磨合系数"})
mask = df[cols["工厂名"]].astype(str).str.strip() == factory_name_cn
if not mask.any():
raise ValueError(f"{csv_path} 中找不到工厂名: {factory_name_cn}")
line_to_factor = dict(zip(line_ids, best_genes))
df.loc[mask, cols["产线ID"]] = df[cols["产线ID"]].astype(str)
for idx, row in df[mask].iterrows():
lid = str(row[cols["产线ID"]]).strip()
if lid in line_to_factor:
df.at[idx, cols["磨合系数"]] = float(line_to_factor[lid])
df.to_csv(csv_path, index=False, encoding=enc)
finally:
_release_lock(lock_path, fd)
def evaluate(factory_code: str, line_ids: List[str], genes: List[float]) -> float:
factory_factors = {}
model = SimulationModel(
factory_factors=factory_factors,
output_enabled=False,
is_calibration_mode=True,
)
# Override per-line factors
for lid, val in zip(line_ids, genes):
model.line_factor[lid] = float(val)
while model.running:
model.step()
return model.mean_abs_error
def mutate(genes: List[float]) -> List[float]:
new = genes.copy()
for i in range(len(new)):
if random.random() < MUTATION_RATE:
jitter = random.gauss(0, MUTATION_STD)
new[i] = new[i] + jitter
return new
def crossover(p1: List[float], p2: List[float]) -> Tuple[List[float], List[float]]:
if len(p1) == 1:
return [p1[0]], [p2[0]]
point = random.randint(1, len(p1) - 1)
c1 = p1[:point] + p2[point:]
c2 = p2[:point] + p1[point:]
return c1, c2
def init_population(seed_vals: List[float]) -> List[List[float]]:
pop = []
for idx in range(POP_SIZE):
if idx == 0:
pop.append([float(v) for v in seed_vals])
continue
indiv = [float(v) for v in seed_vals]
for j in range(len(indiv)):
jitter = random.uniform(-0.1, 0.1)
indiv[j] = indiv[j] + jitter
pop.append(indiv)
return pop
def read_csv_with_encoding(path: Path, required: set[str]):
encodings = ("utf-8", "utf-8-sig", "gbk")
last_error = None
df = None
for enc in encodings:
try:
df = pd.read_csv(path, encoding=enc)
break
except UnicodeDecodeError as exc:
last_error = exc
continue
if df is None:
raise last_error if last_error else FileNotFoundError(f"Missing {path}")
cols = {c.strip(): c for c in df.columns}
missing = required - set(cols)
if missing:
raise ValueError(f"{path} 缺少字段: {', '.join(sorted(missing))}")
return df, cols, enc
def load_factory_lines(year: int, factory_name_cn: str):
path = Path("data") / str(year) / "ProductionLine.csv"
df, cols, enc = read_csv_with_encoding(path, required={"工厂名", "产线ID", "磨合系数", "系数最小值", "系数最大值"})
mask = df[cols["工厂名"]].astype(str).str.strip() == factory_name_cn
if not mask.any():
raise ValueError(f"ProductionLine.csv 中未找到工厂 {factory_name_cn}")
lines = []
for _, row in df[mask].iterrows():
line_id = str(row[cols["产线ID"]]).strip()
seed = float(row[cols["磨合系数"]])
min_b = float(row[cols["系数最小值"]])
max_b = float(row[cols["系数最大值"]])
lines.append((line_id, seed, min_b, max_b))
return lines
def apply_bounds(genes: List[float], bounds: List[Tuple[float, float]]) -> List[float]:
return [clip(val, b) for val, b in zip(genes, bounds)]
def latin_hypercube_samples(n_samples: int, bounds: List[Tuple[float, float]]) -> List[List[float]]:
if n_samples <= 0:
return []
dims = len(bounds)
samples = []
# Latin hypercube with per-dimension random permutations and jitter inside each stratum
strata = [list(range(n_samples)) for _ in range(dims)]
for s in strata:
random.shuffle(s)
for i in range(n_samples):
point = []
for d in range(dims):
lo, hi = bounds[d]
# random point inside the i-th stratum of dimension d
u = random.random()
stratum_idx = strata[d][i]
frac = (stratum_idx + u) / n_samples
val = lo + frac * (hi - lo)
point.append(val)
samples.append(point)
random.shuffle(samples)
return samples
def main():
parser = argparse.ArgumentParser(description="GA calibration for a single factory factor.")
parser.add_argument(
"--factory",
required=True,
help="Factory English code (matches '工厂英文名' in factory_data.csv).",
)
args = parser.parse_args()
# set year
year = json.load(open("year.json", "r", encoding="utf-8"))["year"]
filename = f"{year}"
csv_path = Path("data") / filename / "factory_data.csv"
line_csv_path = Path("data") / filename / "ProductionLine.csv"
factory_row, factory_df, factory_cols = load_factory_row(csv_path, args.factory)
factory_name_cn = str(factory_row[factory_cols["工厂中文名"]]).strip()
seed_lines = load_factory_lines(year, factory_name_cn)
line_ids = [lid for lid, _, _, _ in seed_lines]
seed_vals = [seed for _, seed, _, _ in seed_lines]
bounds = [(mn, mx) for _, _, mn, mx in seed_lines]
prev_best_error = float(factory_row["最小误差"]) if pd.notna(factory_row["最小误差"]) else float("inf")
print(f"[START] 校准工厂 {args.factory} / {factory_name_cn} (产线数={len(line_ids)}, baseline_error={prev_best_error:.6f})")
best_genes = None
best_score = float("inf")
last_improve_gen = -1
population = init_population(seed_vals)
for gen in range(GENERATIONS):
scored = []
for indiv in population:
indiv = apply_bounds(indiv, bounds)
score = evaluate(args.factory, line_ids, indiv)
# print(f"[{args.factory}] Gen {gen+1} try factors={indiv} -> error={score:.6f}")
scored.append((score, indiv))
if score < best_score:
best_score = score
best_genes = indiv
last_improve_gen = gen
scored.sort(key=lambda x: x[0])
next_pop = [scored[0][1]]
while len(next_pop) < POP_SIZE:
parents = random.sample(scored[:max(3, len(scored))], 2)
c1, c2 = crossover(parents[0][1], parents[1][1])
next_pop.append(apply_bounds(mutate(c1), bounds))
if len(next_pop) < POP_SIZE:
next_pop.append(apply_bounds(mutate(c2), bounds))
# Stagnation: inject Latin Hypercube samples to escape local optima
if last_improve_gen >= 0 and (gen - last_improve_gen) >= STAGNATION_WINDOW:
lhs_samples = latin_hypercube_samples(max(POP_SIZE // 2, 2), bounds)
lhs_samples = [apply_bounds(s, bounds) for s in lhs_samples]
next_pop = next_pop[: POP_SIZE // 4] + lhs_samples
next_pop = next_pop[:POP_SIZE]
last_improve_gen = gen
print(f"[{args.factory}] Stagnation detected ({STAGNATION_WINDOW} gens). Injected {len(lhs_samples)} LHS samples.")
population = next_pop
print(f"[{args.factory}] Generation {gen+1}/{GENERATIONS}: best_error={best_score:.6f}")
best_genes = apply_bounds(best_genes, bounds)
best_avg_factor = sum(best_genes) / len(best_genes)
print(f"[DONE] {args.factory}: best_error={best_score:.6f} (prev best {prev_best_error:.6f})")
if best_score < prev_best_error:
update_factory_csv(csv_path, args.factory, best_avg_factor, best_score)
update_production_line_csv(line_csv_path, factory_name_cn, line_ids, best_genes)
print(f"[UPDATE] {args.factory} / {factory_name_cn}: avg_factor={best_avg_factor:.6f}, error={best_score:.6f} 已写入 {csv_path} 与 ProductionLine.csv")
else:
print(f"[SKIP] {args.factory}: 未优于历史最小误差CSV 未更新。")
if __name__ == "__main__":
main()

View File

@ -1,121 +0,0 @@
import json
import random
from pathlib import Path
from typing import List, Tuple
from simulation_model import SimulationModel
# Decision variables: two factory factors only
TARGET_FACTORIES = ["ZhongcaiBaicheng", "ZhongcaiHami"]
# set year
year = json.load(open('year.json', 'r', encoding='utf-8'))['year']
filename = f"{year}"
# Bounds
FACTOR_BOUNDS = (0.8, 3.0)
POP_SIZE = 20
GENERATIONS = 50
MUTATION_RATE = 0.2
MUTATION_STD = 0.05 # for factors
def clip(val: float, bounds: Tuple[float, float]) -> float:
lo, hi = bounds
return max(lo, min(hi, val))
def evaluate(genes: List[float]) -> float:
factory_factors = {fid: val for fid, val in zip(TARGET_FACTORIES, genes)}
model = SimulationModel(factory_factors=factory_factors, output_enabled=False)
while model.running:
model.step()
return model.mean_abs_error
def mutate(genes: List[float]) -> List[float]:
new = genes.copy()
for i in range(len(new)):
if random.random() < MUTATION_RATE:
jitter = random.gauss(0, MUTATION_STD)
new[i] = clip(new[i] + jitter, FACTOR_BOUNDS)
return new
def crossover(p1: List[float], p2: List[float]) -> Tuple[List[float], List[float]]:
point = random.randint(1, len(p1) - 1)
c1 = p1[:point] + p2[point:]
c2 = p2[:point] + p1[point:]
return c1, c2
def init_population() -> List[List[float]]:
pop = []
best_path = Path("data") / filename / "ga_two_best_params.json"
seed_indiv = None
if best_path.exists():
try:
best = json.loads(best_path.read_text(encoding="utf-8"))
seed_indiv = [float(best.get(f"factor_{fid}", random.uniform(*FACTOR_BOUNDS))) for fid in TARGET_FACTORIES]
except Exception:
seed_indiv = None
for idx in range(POP_SIZE):
if seed_indiv is not None and idx == 0:
pop.append(seed_indiv)
continue
indiv = [random.uniform(*FACTOR_BOUNDS) for _ in TARGET_FACTORIES]
pop.append(indiv)
return pop
def main():
best_genes = None
best_score = float("inf")
population = init_population()
for gen in range(GENERATIONS):
scored = []
for indiv in population:
score = evaluate(indiv)
scored.append((score, indiv))
if score < best_score:
best_score = score
best_genes = indiv
scored.sort(key=lambda x: x[0])
next_pop = [scored[0][1], scored[1][1]]
while len(next_pop) < POP_SIZE:
parents = random.sample(scored[:max(3, len(scored))], 2)
c1, c2 = crossover(parents[0][1], parents[1][1])
next_pop.append(mutate(c1))
if len(next_pop) < POP_SIZE:
next_pop.append(mutate(c2))
population = next_pop
print(f"Generation {gen+1}: best={best_score:.4f}")
result = {}
for fid, val in zip(TARGET_FACTORIES, best_genes):
result[f"factor_{fid}"] = val
data_path = Path("data") / filename / "ga_two_best_params.json"
prev_score = float("inf")
if data_path.exists():
try:
prev = json.loads(data_path.read_text(encoding="utf-8"))
prev_score = float(prev.get("best_score", float("inf")))
except Exception:
prev_score = float("inf")
if best_score < prev_score:
data_path.write_text(
json.dumps({"best_score": best_score, **result}, ensure_ascii=False, indent=2),
encoding="utf-8",
)
print(f"New best mean abs error: {best_score:.4f}, saved to {data_path}")
else:
print(f"Best mean abs error: {best_score:.4f} (not better than {prev_score:.4f}, not saved)")
if __name__ == "__main__":
main()

View File

@ -4,23 +4,30 @@ from mesa.agent import Agent
class ProductionLineAgent(Agent):
"""A production line agent with basic production scheduling."""
def __init__(self, model, line_id, region, factory, is_new_factory, schedule, ramp_ranges):
def __init__(self, model, line_id, region, factory, is_new_factory, schedule, ramp_ranges, line_factor: float):
super().__init__(model)
self.line_id = line_id
self.region = region
self.factory = factory
self.is_new_factory = is_new_factory
self.line_factor = float(line_factor)
# List of dicts: {"product": str, "start_month": int, "end_month": int}
self.schedule = schedule
# ramp_ranges: {1: value, 2: value, 3: value, 4: value}
# ramp_ranges: {product: {1: value, 2: value, 3: value, 4: value}}
self.ramp_ranges = ramp_ranges
self.blade_stock = {} # 临时叶片库存 {product: blades not yet assembled}
self.unit_stock = {} # 叶片机组库存 {product: assembled units}
def _sample_cycle_hours(self, product, month_index):
idx = min(max(month_index, 1), 4)
base = self.ramp_ranges[idx]
factor = self.model.get_factory_factor(self.factory)
prod_key = str(product).strip()
base_by_month = self.ramp_ranges.get(prod_key)
if base_by_month is None:
raise KeyError(f"未找到产品 {prod_key} 的生产效率。")
base = base_by_month.get(idx, base_by_month.get(4))
if base is None:
raise KeyError(f"产品 {prod_key} 在 month{idx} 缺少生产效率。")
factor = self.model.get_factory_factor(self.factory, self.line_id, self.line_factor)
return base * factor
def step(self):
@ -45,6 +52,14 @@ class ProductionLineAgent(Agent):
cycle_hours = self._sample_cycle_hours(product, month_index)
produced_blades = available_hours / cycle_hours
self.model.record_blade_production(
line_id=self.line_id,
factory=self.factory,
region=self.region,
month=month,
product=product,
blades=produced_blades,
)
# Update blade and unit inventories
current_blades = self.blade_stock.get(product, 0) + produced_blades
@ -53,6 +68,14 @@ class ProductionLineAgent(Agent):
if units_to_add > 0:
self.unit_stock[product] = self.unit_stock.get(product, 0) + units_to_add
self.blade_stock[product] = remaining_blades
self.model.record_blade_stock(
line_id=self.line_id,
factory=self.factory,
region=self.region,
month=month,
product=product,
blades_stock=remaining_blades,
)
self.model.record_production(
line_id=self.line_id,

View File

@ -1,173 +0,0 @@
# -*- coding: utf-8 -*-
import pandas as pd
import pulp
from math import radians, sin, cos, sqrt, atan2
# 1. 读取数据
inventory_df = pd.read_excel('可调拨库存表.xlsx')
factory_loc_df = pd.read_excel('工厂位置.xlsx')
demand_df = pd.read_excel('需求表.xlsx') # 型号、市、需求
city_loc_df = pd.read_excel('地名经纬度.xlsx') # 市、经度、纬度
# 2. 数据合并
inventory_df['可调拨库存'] = pd.to_numeric(inventory_df['可调拨库存'], errors='coerce').fillna(0)
demand_df['需求'] = pd.to_numeric(demand_df['需求'], errors='coerce').fillna(0)
# 工厂库存合并
factory_capacity = {}
for _, row in inventory_df.groupby(['工厂', '型号'])['可调拨库存'].sum().reset_index().iterrows():
f, p, cap = row['工厂'], row['型号'], int(row['可调拨库存'])
if cap > 0:
factory_capacity.setdefault(f, {})[p] = cap
# 城市需求合并
city_demand = {}
for _, row in demand_df.iterrows():
city, p, dem = row[''], row['型号'], int(row['需求'])
if dem > 0:
city_demand.setdefault(city, {})
city_demand[city][p] = city_demand[city].get(p, 0) + dem
factory_lonlat = dict(zip(factory_loc_df['工厂'], zip(factory_loc_df['经度'], factory_loc_df['纬度'])))
city_lonlat = dict(zip(city_loc_df[''], zip(city_loc_df['经度'], city_loc_df['纬度'])))
# missing = set(city_demand) - set(city_lonlat)
# if missing:
# raise ValueError(f"缺少经纬度:{missing}")
# 3. 距离
dist = {}
for f in factory_capacity:
for c in city_demand:
lon1, lat1 = factory_lonlat[f]
lon2, lat2 = city_lonlat[c]
dlon = radians(lon2-lon1)
dlat = radians(lat2-lat1)
a = sin(dlat/2)**2 + cos(radians(lat1))*cos(radians(lat2))*sin(dlon/2)**2
dist[(f,c)] = 6371 * 2 * atan2(sqrt(a), sqrt(1-a))
# 4. 单位运费函数
def get_real_unit_cost(distance_km, product):
d = distance_km
p = str(product).strip()
if p in ['GWBD-A2', 'GWBD-A3', 'GWBD-B']:
if distance_km <= 100:
return 15802.31
elif distance_km <= 200:
return 18445.12
elif distance_km <= 300:
return 22391.35
elif distance_km <= 600:
return 62.29 * d
elif distance_km <= 900:
return 44.75 * d
elif distance_km <= 1200:
return 38.23 * d
elif distance_km <= 1500:
return 36.08 * d
elif distance_km <= 2000:
return 33.44 * d
elif distance_km <= 2500:
return 32.76 * d
elif distance_km <= 3500:
return 26.68 * d
else:
return 24.27
# 其他型号用第二套
else:
if distance_km <= 100:
return 12859.46
elif distance_km <= 200:
return 14946.73
elif distance_km <= 300:
return 18662.46
elif distance_km <= 600:
return 52.83 * d
elif distance_km <= 900:
return 38.36 * d
elif distance_km <= 1200:
return 32.89 * d
elif distance_km <= 1500:
return 30.69 * d
elif distance_km <= 2000:
return 28.49 * d
elif distance_km <= 2500:
return 27.76 * d
elif distance_km <= 3500:
return 22.87 * d
else:
return 21.02 * d
# 5. 规划模型
prob = pulp.LpProblem("最小化运费", pulp.LpMinimize)
x = {}
for f in factory_capacity:
for p in factory_capacity[f]:
for city in city_demand:
if city_demand[city].get(p, 0) > 0:
x[(f,city,p)] = pulp.LpVariable(f"ship_{f}_{city}_{p}", lowBound=0, cat="Integer")
# 缺货惩罚
shortage = {(city,p): pulp.LpVariable(f"short_{city}_{p}", lowBound=0, cat="Continuous")
for city in city_demand for p in city_demand[city]}
BIG_M = 1e10
prob += pulp.lpSum(
x[k] * get_real_unit_cost(dist[(k[0],k[1])], k[2]) # 真实单位运费
for k in x
) + pulp.lpSum(BIG_M * s for s in shortage.values())
# 库存硬约束
for f in factory_capacity:
for p in factory_capacity[f]:
prob += pulp.lpSum(x.get((f,c,p), 0) for c in city_demand) <= factory_capacity[f][p]
# 需求软约束
for city in city_demand:
for p, dem in city_demand[city].items():
prob += pulp.lpSum(x.get((f,city,p), 0) for f in factory_capacity) + shortage[(city,p)] >= dem
# 6. 求解
status = prob.solve(pulp.PULP_CBC_CMD(msg=True, timeLimit=600))
print(f"\n求解状态: {pulp.LpStatus[status]}")
if status != 1:
print("无解(请检查总库存)")
else:
print("有解")
# real_cost = pulp.value(prob.objective)
# print(f"实际运输总费用: {real_cost:,.2f} 元")
# ====================== 7. 输出 ======================
result = []
for (f,city,p), var in x.items():
q = int(pulp.value(var) or 0)
if q >= 1:
d = dist[(f,city)]
# unit = get_real_unit_cost(d, p)
result.append({
"工厂": f,
"型号": p,
"需求方": city,
"调拨数量": q,
# "距离_km": round(d, 1),
# "真实单位运费": round(unit, 3),
# "总运费": round(q * unit, 2)
})
df = pd.DataFrame(result)
if not df.empty:
df = df.sort_values(["工厂", "型号"]) # 按单位运费排序
df[["工厂","型号","需求方","调拨数量"]].to_excel("调拨方案.xlsx", index=False)
# print("\n调拨方案已保存真实单位运费最优优先满足单价更低的城市")
# print(df[["工厂","型号","需求方","调拨数量"]].to_string(index=False))
else:
print("\n无调拨记录")
# 校验报告
# print("\n各城市需求满足情况")
# for city in city_demand:
# for p, dem in city_demand[city].items():
# sup = sum(r["调拨数量"] for r in result if r["需求方"]==city and r["型号"]==p)
# print(f" {city} | {p}: 需求 {dem} → 调拨 {sup} → {'完美' if sup==dem else '异常'}")

View File

@ -8,6 +8,9 @@ import matplotlib.pyplot as plt
from mesa.datacollection import DataCollector
from mesa.model import Model
import pulp
import warnings
import sys
import traceback
from production_line import ProductionLineAgent
from demand_agent import DemandAgent
@ -19,6 +22,36 @@ matplotlib.rcParams["axes.unicode_minus"] = False
# set year
year = json.load(open('year.json', 'r', encoding='utf-8'))['year']
filename = f"{year}"
# Silence noisy deprecation warnings from dependencies; prefer explicit conversions.
warnings.filterwarnings("ignore", category=FutureWarning)
class _FilterMessageTracer:
"""Silence and trace unexpected 'filter element' messages."""
def __init__(self, stream, log_path):
self._stream = stream
self._log_path = log_path
def write(self, msg):
lower_msg = msg.lower()
if "filter element" in lower_msg:
# Record stack once per message and swallow it from console.
with open(self._log_path, "a", encoding="utf-8") as f:
f.write(msg)
traceback.print_stack(file=f)
return len(msg)
return self._stream.write(msg)
def flush(self):
return self._stream.flush()
# Capture mysterious "filter element" prints while keeping stdout/stderr usable.
_filter_log = os.path.join("output", filename, "debug_filter.log")
os.makedirs(os.path.dirname(_filter_log), exist_ok=True)
sys.stdout = _FilterMessageTracer(sys.stdout, _filter_log)
sys.stderr = _FilterMessageTracer(sys.stderr, _filter_log)
class SimulationModel(Model):
"""
@ -28,35 +61,26 @@ class SimulationModel(Model):
def __init__(
self,
month1: float | None = None,
month2: float | None = None,
month3: float | None = None,
month4: float | None = None,
factory_factors: dict | None = None,
output_enabled: bool = False,
is_within_region_allocation_only: bool | None = None,
product_set: tuple | list | set | str | None = None,
is_calibration_mode: bool = False,
**kwargs,
):
super().__init__()
cfg = self._load_model_params()
# Apply overrides if provided
self.month_holiday_days = self._load_month_holiday_days(cfg)
self.ramp_ranges = {
1: float(month1 if month1 is not None else cfg["month1"]),
2: float(month2 if month2 is not None else cfg["month2"]),
3: float(month3 if month3 is not None else cfg["month3"]),
4: float(month4 if month4 is not None else cfg["month4"]),
}
self.ramp_ranges = self._load_product_month_efficiency()
self.factory_mapping = self._load_factory_mapping()
self.factory_factors = self._load_factory_factors(cfg)
self.factory_factors = {}
merged_factors = {}
merged_factors.update({k: v for k, v in kwargs.items() if k.startswith("factor_")})
if factory_factors:
merged_factors.update(factory_factors)
if merged_factors:
self._merge_factory_factors(merged_factors)
self.default_new_factory_factor = cfg.get("factor_default", 1.3)
self.within_region_only = self._to_bool(
is_within_region_allocation_only if is_within_region_allocation_only is not None else cfg.get("is_within_region_allocation_only", False)
)
@ -91,6 +115,10 @@ class SimulationModel(Model):
self.product_names = set()
self.product_list = []
self.monthly_allocation_summary = []
self.blade_production_log = []
self.blade_stock_log = []
self.calibration_mode = bool(is_calibration_mode)
self.line_factor = {}
self._load_month_hours()
self._load_agents_from_csv()
@ -194,6 +222,32 @@ class SimulationModel(Model):
with open(f"data/{filename}/model_params.json", "r", encoding="utf-8") as f:
return json.load(f)
def _load_product_month_efficiency(self) -> dict:
path = f"data/{filename}/month_efficiency.xlsx"
if not os.path.exists(path):
raise FileNotFoundError(f"Missing month efficiency file: {path}")
df = pd.read_excel(path)
df.columns = [str(c).strip().lower() for c in df.columns]
required = {"product_id", "month1", "month2", "month3", "month4"}
missing = required - set(df.columns)
if missing:
raise ValueError(f"month_efficiency.xlsx is missing columns: {', '.join(sorted(missing))}")
ramp = {}
for _, row in df.iterrows():
product = str(row["product_id"]).strip()
if not product:
continue
values = {}
for idx in range(1, 5):
val = row[f"month{idx}"]
if pd.isna(val):
raise ValueError(f"产品 {product} 的 month{idx} 为空,请在 month_efficiency.xlsx 中补充。")
values[idx] = float(val)
ramp[product] = values
if not ramp:
raise ValueError("month_efficiency.xlsx contains no product rows.")
return ramp
def _load_month_holiday_days(self, cfg: dict) -> dict:
# Default 2 days off per month unless overridden
holidays = {m: 2 for m in range(1, 13)}
@ -211,7 +265,8 @@ class SimulationModel(Model):
def _load_factory_mapping(self) -> dict:
with open(f"data/{filename}/factory_mapping.json", "r", encoding="utf-8") as f:
return json.load(f)
mapping = json.load(f)
return mapping
def _parse_product_set(self, val) -> tuple:
if val is None:
@ -231,13 +286,9 @@ class SimulationModel(Model):
return val.strip().lower() in {"true", "1", "yes", "y", "", ""}
return bool(val)
def _load_factory_factors(self, cfg: dict) -> dict:
factors = {}
for key, val in cfg.items():
if key.startswith("factor_"):
suffix = key[len("factor_") :]
factors[suffix] = float(val)
return factors
def _load_factory_factors(self) -> dict:
# Deprecated: factors now provided per production line; keep for compatibility with overrides.
return {}
def _merge_factory_factors(self, overrides: dict):
for key, val in overrides.items():
@ -249,9 +300,20 @@ class SimulationModel(Model):
def get_factory_code(self, factory_name: str) -> str:
return self.factory_mapping.get(factory_name, self._sanitize_product(factory_name))
def get_factory_factor(self, factory_name: str) -> float:
def get_factory_factor(self, factory_name: str, line_id: str | None = None, line_factor: float | None = None) -> float:
code = self.get_factory_code(factory_name)
return self.factory_factors.get(code, self.default_new_factory_factor)
if line_id and line_id in self.line_factor:
base = self.line_factor.get(line_id)
else:
base = None
override = self.factory_factors.get(code)
if override is not None:
return float(override)
if base is not None:
return float(base)
if line_factor is not None:
return float(line_factor)
raise KeyError(f"未找到工厂 {factory_name} (line_id={line_id}) 的磨合系数。")
def _load_agents_from_csv(self):
encodings = ("utf-8", "utf-8-sig", "gbk")
@ -275,8 +337,11 @@ class SimulationModel(Model):
for line_id, group in df.groupby("产线ID"):
first = group.iloc[0]
schedule = []
line_factor_val = None
for _, row in group.iterrows():
product = row["生产型号"]
product = str(row["生产型号"]).strip()
if product not in self.ramp_ranges:
raise KeyError(f"缺少产品 {product} 的生产效率,请在 month_efficiency.xlsx 中补充。")
schedule.append(
{
"product": product,
@ -284,10 +349,17 @@ class SimulationModel(Model):
"end_month": int(row["结束月份"]),
}
)
self.product_names.add(str(product).strip())
self.product_names.add(product)
is_new_factory = str(first["是否新工厂"]).strip() in {"", "Yes", "True", "true", "1"}
self.line_factory[line_id] = first["工厂名"]
self.line_region[line_id] = first["区域名"]
if "磨合系数" not in first:
raise KeyError("ProductionLine.csv 缺少磨合系数字段。")
try:
line_factor_val = float(first.get("磨合系数"))
except Exception:
raise ValueError(f"无法解析产线 {line_id} 的磨合系数。")
self.line_factor[line_id] = line_factor_val
ProductionLineAgent(
model=self,
line_id=line_id,
@ -296,6 +368,7 @@ class SimulationModel(Model):
is_new_factory=is_new_factory,
schedule=schedule,
ramp_ranges=self.ramp_ranges,
line_factor=line_factor_val,
)
def _load_demand_agents_from_csv(self):
@ -462,9 +535,43 @@ class SimulationModel(Model):
self.cumulative_production += units
self.region_totals[region] = self.region_totals.get(region, 0) + units
def record_blade_production(self, line_id, factory, region, month, product, blades):
self.blade_production_log.append(
{
"line_id": line_id,
"factory": factory,
"region": region,
"month": month,
"product": product,
"blades": float(blades),
}
)
def record_blade_stock(self, line_id, factory, region, month, product, blades_stock):
self.blade_stock_log.append(
{
"line_id": line_id,
"factory": factory,
"region": region,
"month": month,
"product": product,
"blades_stock": float(blades_stock),
}
)
def step(self):
for agent in list(self.agents):
agent.step()
if self.calibration_mode:
if self.current_month >= 12:
self.running = False
self._finalize_factory_errors(write_files=self.output_enabled)
if self.output_enabled:
self._write_report()
self.datacollector.collect(self)
self.current_month += 1
return
# Allocation after production for current month
self._run_allocation()
self._update_region_inventory()
@ -513,6 +620,40 @@ class SimulationModel(Model):
os.makedirs(output_dir, exist_ok=True)
out_path = os.path.join(output_dir, f"production_report_{timestamp}.csv")
pivot.to_csv(out_path, index=False, encoding="utf-8-sig")
self._write_blade_reports(timestamp, output_dir)
def _write_blade_reports(self, timestamp: str, output_dir: str):
def build_and_write(log, value_col, filename_prefix, value_name):
if not log:
return
df = pd.DataFrame(log)
pivot = (
df.groupby(["product", "line_id", "factory", "month"])[value_col]
.sum()
.reset_index()
.pivot_table(
index=["product", "line_id", "factory"],
columns="month",
values=value_col,
fill_value=0.0,
)
)
for m in range(1, 13):
if m not in pivot.columns:
pivot[m] = 0.0
pivot = pivot[sorted([c for c in pivot.columns if isinstance(c, int)])]
pivot["total"] = pivot.sum(axis=1)
pivot.reset_index(inplace=True)
pivot.columns = (
["生产型号", "产线名称", "工厂名"]
+ [f"{m}" for m in range(1, 13)]
+ ["总计"]
)
out_path = os.path.join(output_dir, f"{filename_prefix}_{timestamp}.csv")
pivot.to_csv(out_path, index=False, encoding="utf-8-sig")
build_and_write(self.blade_production_log, "blades", "production_blade_current_report", "blades")
build_and_write(self.blade_stock_log, "blades_stock", "production_blade_stock_report", "blades_stock")
def _write_allocation_workbook(self):
if not self.monthly_allocation_summary:
@ -694,23 +835,33 @@ class SimulationModel(Model):
except UnicodeDecodeError:
benchmark = pd.read_csv(f"data/{filename}/benchmark.csv", encoding="gbk")
benchmark_sorted = benchmark.sort_values(by=benchmark.columns[0])
total_col = "总计"
month_cols = [f"{m}" for m in range(1, 13)]
if total_col not in benchmark_sorted.columns:
benchmark_sorted[total_col] = benchmark_sorted[month_cols].sum(axis=1)
bench_total = benchmark_sorted[total_col].astype(float).reset_index(drop=True)
prod_total = factory_pivot["总计"].astype(float).reset_index(drop=True)
min_len = min(len(bench_total), len(prod_total))
if min_len == 0:
errors = []
bench_name_col = benchmark_sorted.columns[0]
for _, prow in factory_pivot.iterrows():
fname = prow["工厂名称"]
brow = benchmark_sorted[benchmark_sorted[bench_name_col] == fname]
if brow.empty:
continue
prod_months = pd.Series([float(prow[f"{m}"]) for m in range(1, 13)])
bench_months = pd.Series([float(brow.iloc[0][f"{m}"]) for m in range(1, 13)])
prod_cum = prod_months.cumsum()
bench_cum = bench_months.cumsum()
actual_total = float(brow.iloc[0][total_col]) if total_col in brow.columns else float(bench_cum.iloc[-1])
if actual_total <= 0:
continue
diff = prod_cum - bench_cum
pct = diff.abs() / actual_total
err = pct.mean()
errors.append(float(err))
if not errors:
self.mean_abs_error = float("inf")
return
bench_total = bench_total.iloc[:min_len]
prod_total = prod_total.iloc[:min_len]
bench_total_safe = bench_total.replace(0, pd.NA)
rel_errors = (prod_total - bench_total_safe) / bench_total_safe
rel_errors = rel_errors.fillna(0).astype(float)
abs_rel_errors = rel_errors.abs()
self.mean_abs_error = abs_rel_errors.mean()
self.mean_abs_error = sum(errors) / len(errors)
if not write_files:
return
@ -739,7 +890,7 @@ class SimulationModel(Model):
bench_months = benchmark_sorted[[f"{m}" for m in range(1, 13)]].iloc[:min_len].astype(float)
bench_months_safe = bench_months.replace(0, pd.NA)
month_pct_errors = (prod_months - bench_months_safe) / bench_months_safe * 100
month_pct_errors = month_pct_errors.fillna(0).convert_dtypes().astype(float)
month_pct_errors = month_pct_errors.fillna(0).infer_objects(copy=False).astype(float)
month_error_means = pd.Series(month_pct_errors.mean(axis=0), index=[f"{m}" for m in range(1, 13)])
plt.figure(figsize=(10, 5))
@ -753,7 +904,7 @@ class SimulationModel(Model):
bench_totals_safe = bench_total.replace(0, pd.NA)
factory_pct_errors = (prod_total - bench_totals_safe) / bench_totals_safe * 100
factory_pct_errors = factory_pct_errors.fillna(0).convert_dtypes().astype(float)
factory_pct_errors = factory_pct_errors.fillna(0).infer_objects(copy=False).astype(float)
factory_names = factory_pivot["工厂名称"].iloc[:min_len].reset_index(drop=True)
factory_df = pd.DataFrame({"name": factory_names, "error_pct": factory_pct_errors}).sort_values(
by="error_pct"