GA基本正常
This commit is contained in:
parent
52e2f4dcb1
commit
b86290c331
|
|
@ -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.
|
||||
|
|
@ -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()
|
||||
|
|
@ -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,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,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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,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",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
173
schedule_pulp.py
173
schedule_pulp.py
|
|
@ -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 '异常'}")
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue