1. 개요[편집]
| FloPy | |
|---|---|
| 개발 | 미국 지질조사국(USGS) + 커뮤니티 |
| 언어·라이선스 | Python · CC0 (퍼블릭 도메인) |
| 대상 | MODFLOW 계열 · MT3D · SEAWAT · MODPATH |
| 하는 일 | 입력 생성 → 실행 → 바이너리 출력 파싱 → 가시화 |
| API 두 갈래 | flopy.modflow(2005/NWT) · flopy.mf6(MODFLOW 6) |
| mf6 특이점 | 패키지 클래스가 MODFLOW 6 정의파일(DFN)에서 자동 생성됨 |
| 대표 논문 | Bakker 등 (2016), Groundwater |
지하수 모형 하나를 만드는 데 GUI로 사흘이 걸린다. 그 모형을 조도만 바꿔 500번 돌려야 한다는 사실은 사흘째 저녁에 알게 된다.
FloPy는 USGS가 배포하는 파이썬 패키지로, MODFLOW 계열 코드의 입력 파일을 객체 조립으로 생성하고, 실행하고, 바이너리 결과를 읽어 후처리·가시화까지 하나의 스크립트 안에서 처리하는 도구다.
MODFLOW 문서가 다루는 것은 엔진이다 — 어떤 이산화를 쓰고, 셀이 마르면 무슨 일이 벌어지고, 어떤 솔버가 붙는가. 이 문서가 다루는 것은 그 엔진을 어떻게 부리는가, 즉 툴체인이다. 둘의 관심사는 겹치지 않는다. FloPy는 방정식을 하나도 풀지 않는다. 텍스트 입력 파일을 쓰고, 실행 파일을 호출하고, 나온 바이너리를 파싱한다. 그게 전부다.
그런데 지하수 모델링이라는 일의 실체를 보면 이 “그게 전부”가 작업의 대부분이다. 격자를 바꾸면 층별 물성 배열, 우물 셀 인덱스, 경계 셀 목록이 전부 다시 만들어져야 한다. GUI에서는 이것이 마우스 노동이고, 스크립트에서는 반복문 하나다. 지금 나오는 MODFLOW 논문의 재현 가능한 부록은 사실상 전부 FloPy 스크립트다.1
2. 왜 스크립트인가[편집]
GUI가 나쁘다는 이야기가 아니다. USGS 자신이 ModelMuse라는 훌륭한 무료 GUI를 만든다. 다만 다음 세 가지 일에서는 GUI가 원리적으로 불리하다.
- 앙상블. 수리전도도 장 200개를 뽑아 각각 모형을 만들어 돌리는 불확실성 정량화 작업에서 GUI는 선택지가 아니다.
- 재현성. “격자를 50 m에서 25 m로 바꿔 다시 돌렸다”를 3년 뒤에 증명해야 하는데, GUI 클릭 이력은 남지 않는다. 스크립트는 그 자체가 절차서다.
- 결합. 지표수 모형이나 최적화 루프 안에서 지하수 모형을 목적함수 평가기로 부르려면 프로그래밍 인터페이스가 있어야 한다.
FloPy가 CC0 퍼블릭 도메인이라는 점도 실무에서 중요하다. 정부 용역과 상용 워크플로에 그대로 넣을 수 있다.
3. 모형 조립 — 객체 하나가 패키지 하나[편집]
FloPy의 설계는 MODFLOW의 패키지 구조를 그대로 파이썬 클래스로 옮긴 것이다. 패키지 하나가 클래스 하나이고, 객체를 만들면 그 패키지 파일이 생긴다. MODFLOW 6 API의 골격은 이렇다.
import flopy
sim = flopy.mf6.MFSimulation(sim_name="ex", exe_name="mf6", sim_ws="./run")
tdis = flopy.mf6.ModflowTdis(sim, nper=2, perioddata=[(1.0, 1, 1.0), (365.0, 12, 1.2)])
ims = flopy.mf6.ModflowIms(sim, complexity="MODERATE", outer_maximum=100)
gwf = flopy.mf6.ModflowGwf(sim, modelname="ex", newtonoptions="NEWTON UNDER_RELAXATION")
dis = flopy.mf6.ModflowGwfdis(gwf, nlay=3, nrow=40, ncol=60, delr=50.0, delc=50.0,
top=100.0, botm=[60.0, 30.0, 0.0])
npf = flopy.mf6.ModflowGwfnpf(gwf, icelltype=1, k=[10.0, 0.5, 25.0], k33=1.0)
ic = flopy.mf6.ModflowGwfic(gwf, strt=95.0)
chd = flopy.mf6.ModflowGwfchd(gwf, stress_period_data=[[(0, 0, 0), 95.0]])
wel = flopy.mf6.ModflowGwfwel(gwf, stress_period_data={1: [[(2, 20, 30), -1500.0]]})
rch = flopy.mf6.ModflowGwfrcha(gwf, recharge=1.0e-4)
oc = flopy.mf6.ModflowGwfoc(gwf, head_filerecord="ex.hds", budget_filerecord="ex.cbc",
saverecord=[("HEAD", "ALL"), ("BUDGET", "ALL")])
sim.write_simulation()
success, buff = sim.run_simulation()
읽어 보면 구조가 그대로 드러난다. MFSimulation 이 최상위 컨테이너이고 그 안에 시간 이산화(TDIS)와 솔버(IMS)가 붙는다. 모형(ModflowGwf)은 그 아래에 있고, 공간 이산화(DIS)·물성(NPF)·초기조건(IC)·경계(CHD/WEL/RCH)·출력 제어(OC)가 모형에 붙는다. 하나의 시뮬레이션 안에 여러 모형을 두고 교환(exchange)으로 잇는 MODFLOW 6의 구조가 객체 계층에 그대로 반영되어 있어서, 지하수 유동 모형과 용질 수송 모형(GWT)을 같은 sim 아래 두고 연결하는 것도 같은 방식이다.
구형 API(flopy.modflow.Modflow)는 MODFLOW-2005/NWT/LGR용이고 계층이 한 단계 얕다 — 시뮬레이션 개념이 없어 모형 객체가 최상위다. 유산 모형이 여전히 많아 둘 다 유지된다.
3.1. 정의파일에서 자동 생성되는 API[편집]
flopy.mf6 의 패키지 클래스들은 사람이 손으로 쓴 것이 아니다. MODFLOW 6은 각 패키지의 입력 항목을 정의파일(DFN, definition file)이라는 형식으로 기술하고, 이 파일에서 입력 지침서와 파서를 함께 생성한다. FloPy는 같은 DFN을 읽어 파이썬 클래스와 인자 목록, 독스트링을 생성한다.
이 설계의 값어치는 유지보수에서 나온다. MODFLOW 6에 새 패키지가 들어가거나 기존 패키지에 옵션이 추가되면, FloPy 쪽은 생성기를 다시 돌리는 것으로 거의 따라잡는다. 입력 형식과 인터페이스가 같은 원본에서 나오므로 둘이 어긋날 여지가 구조적으로 줄어든다. 대신 인자 이름이 포트란 입력 항목 이름을 그대로 물려받아, 파이썬 코드에 k33overk 나 rewet_record 같은 이름이 튀어나온다. 파이썬스럽지 않다는 불평은 정당하지만, 그 대가로 지침서 페이지와 코드가 1:1로 대응한다.
4. 격자를 다루는 층[편집]
MODFLOW 6이 비구조 격자를 받아들이면서 FloPy에도 격자 객체가 생겼다.
StructuredGrid— 행·열·층(DIS).VertexGrid— 평면 다각형 + 층(DISV).UnstructuredGrid— 셀과 연결 목록(DISU).
이 객체들이 하는 일은 인덱스와 좌표 사이의 번역이다. 모형 좌표계, 회전각, 원점을 들고 있어서 셀 중심 좌표를 실좌표로 내보내고, 그 반대도 한다. 여기에 GridIntersect 유틸리티가 붙어 점·선·면 도형과 격자의 교차를 계산한다 — 하천 선형과 만나는 셀 목록, 관정 위치가 속한 셀 인덱스, 오염원 폴리곤이 덮는 셀과 그 면적비를 뽑는 일이 한 줄이 된다. 지형 자료가 도형이고 모형 입력이 인덱스인 이상, 이 번역 계층이 없으면 격자를 바꿀 때마다 손으로 다시 찍어야 한다.
삼각망·보로노이 격자 생성기를 감싼 도우미도 있어서, 우물 주변만 조밀한 DISV 격자를 스크립트로 만들 수 있다. MODFLOW 문서에서 본 XT3D 옵션이 필요해지는 그 격자다.
5. 결과 읽기[편집]
MODFLOW의 출력은 포트란 비형식 바이너리다. 헤더 구조를 알아야 읽히고, 컴파일러에 따라 레코드 구분자가 다르다. FloPy의 flopy.utils 가 이 부분을 감춘다.
HeadFile— 수두 파일. 시각별·층별 배열을 꺼내고, 특정 셀의 시계열을 뽑는다.CellBudgetFile— 셀별 유량 파일. 면 유량과 각 패키지의 기여를 항목별로 준다.- 목록 파일(
.lst) 파서 — 물수지표와 불일치율, 반복 이력을 표로 읽는다.
여기서 실무적으로 가장 자주 쓰는 것이 물수지 불일치율의 자동 확인이다. 앙상블 500개를 돌리면 그중 몇 개는 수렴에 실패하거나 불일치율이 튀는데, 사람이 목록 파일을 500번 열어 볼 수는 없다. run_simulation() 의 성공 여부와 목록 파일 파싱을 후처리 앞단에 끼워 넣어 실패한 실현을 자동으로 걸러내는 것이 앙상블 워크플로의 기본 위생이다. 이걸 안 하면 수렴 실패한 결과가 통계에 조용히 섞인다.
가시화는 PlotMapView(평면)와 PlotCrossSection(단면)이 담당한다. 격자 객체를 알고 있으므로 비구조 격자에서도 그대로 그려지고, 등수두선·경계 셀·유속 벡터를 겹칠 수 있다. 결과를 shapefile·래스터·VTK로 내보내 GIS나 ParaView로 넘기는 경로도 있다.
6. 보정과 대량 시나리오[편집]
FloPy가 진짜 힘을 내는 곳이다.
PEST/PEST++와의 결합. 지하수 모형의 보정은 관측 수두·유량에 맞춰 물성을 조정하는 역문제이고, 업계 표준 도구는 모델 독립적으로 동작하는 PEST 계열이다. PEST는 “템플릿 파일에서 입력을 만들고, 모형을 돌리고, 지시 파일로 출력을 읽는” 방식이라 어떤 코드에도 붙지만, 그 템플릿과 지시 파일을 만드는 일이 노동이다. 파이썬 쪽 동반 패키지인 pyEMU가 FloPy 모형 객체를 받아 매개변수화(파일럿 포인트, 격자 단위 배열, 다중 스케일)와 PEST 제어 파일 생성을 자동화한다. 파일럿 포인트를 크리깅으로 보간하고 티코노프 정칙화를 거는 표준 절차가 스크립트 몇 줄로 구성된다.
앙상블 계열(PESTPP-IES)은 앙상블 칼만 필터의 반복형 변종이고, 여기서는 애초에 수백 개 모형 인스턴스를 동시에 만들어야 하므로 GUI로는 손도 못 댄다.
시나리오 대량 생성. 양수량 조합, 함양 시나리오, 기후 입력, 격자 해상도를 반복문으로 돌려 각각 별도 작업 디렉터리에 쓰고 실행한다. 모형 실행끼리 서로 독립이라 완전 병렬이고, 이 정도의 병렬성은 그냥 프로세스를 여러 개 띄우면 된다. FloPy의 역할은 “각 실행이 자기 디렉터리와 자기 실행 파일을 갖게 하는 것”이다.
7. 함정[편집]
- 0 기반 인덱스. 파이썬 쪽은 층·행·열이 0부터, MODFLOW 입력 파일은 1부터다. FloPy가 쓸 때 변환해 준다. 즉 스크립트에
(2, 20, 30)이라고 쓰면 입력 파일에는3 21 31이 찍힌다. 지침서를 보며 디버깅할 때 이 한 칸 차이가 사람을 잡는다. 가장 흔한 FloPy 버그이며, 특히 GUI로 만든 모형을 스크립트로 옮길 때 터진다. - 응력기간을 생략하는 것과 빈 목록을 주는 것은 다르다.
stress_period_data딕셔너리에서 어떤 응력기간의 키를 아예 안 넣으면 MODFLOW의 관행대로 직전 기간의 자료를 그대로 이어 쓴다. 반면 빈 목록[]을 넣으면 그 기간에는 해당 응력이 없다. “양수를 껐다”고 생각하며 키를 지웠는데 우물이 계속 돌고 있는 사고가 여기서 난다. - 왕복이 무손실이 아니다. 기존 모형을
load()해서 고치고 다시 쓰면 파일이 정규화되어 다시 나온다. 주석과 서식이 사라지고, FloPy가 모르는 비표준 옵션은 유실될 수 있다. 인허가에 제출한 모형을 이 방식으로 손보면 diff가 통째로 뒤집혀 심의에서 설명하기 곤란해진다. - 실행 파일 경로.
exe_name이 안 맞으면 입력 파일은 멀쩡히 써 놓고 실행만 실패한다. USGS는 실행 파일을 내려받는 보조 명령(get-modflow)을 FloPy와 함께 제공해 이 문제를 줄였지만, 어느 버전의 MODFLOW로 돌렸는지를 결과와 함께 기록하지 않으면 재현성은 그 자리에서 깨진다. - FloPy는 모형을 검증하지 않는다. 물리적으로 말이 안 되는 입력도 그대로 파일에 쓴다. 격자 밖 우물, 바닥보다 낮은 상단고, 자릿수가 어긋난 전도도 — 전부 MODFLOW가 뱉거나 조용히 이상한 답을 준다. 스크립트로 만드는 모형은 틀리기도 그만큼 빠르다.
8. 계보와 위치[편집]
FloPy는 원래 MODFLOW-2005 입력을 만드는 소박한 스크립트 모음에서 출발했고, 3.x에서 MODFLOW 6 지원(flopy.mf6)이 들어오면서 성격이 바뀌었다. 지금은 유동뿐 아니라 MT3DMS·SEAWAT·MODPATH의 입출력까지 다루므로, 사실상 USGS 지하수 코드 생태계 전체의 파이썬 정면 창구다.
같은 자리를 노리는 것으로는 파이썬 기반 격자 전처리기 여럿과 상용 GUI의 스크립팅 기능이 있지만, USGS 본체가 만들고 정의파일을 공유한다는 점에서 FloPy의 위치는 견고하다. 그리고 이 자리는 코드 품질보다 생태계 표준의 문제라는 점에서, MODFLOW 자신이 지배력을 얻은 방식과 정확히 같다.2 EPANET 위의 WNTR, HEC-RAS 위의 자동화 스크립트들과 비교해 보면 이 계층의 필요성이 분야를 가리지 않는다는 것도 보인다.3
9. 관련 문서[편집]
- MODFLOW · 지하수 유동 · 다르시 법칙 · 수리전도도
- SEAWAT · MT3DMS · MODPATH · 해수 침투
- 유한차분법 · 유한체적법 · 격자 · 희소행렬
- 역문제 · PEST · 크리깅 · 앙상블 칼만 필터
- 불확실성 정량화 · 민감도 해석 · 몬테카를로 방법 · 검증 및 확인
- ParaView · OpenFOAM · EPANET · WNTR
10. Footnotes[편집]
-
이 변화가 조용히 만든 부수 효과가 하나 있다. 논문 부록이 스크립트가 되면서 심사자가 모형을 실제로 돌려 볼 수 있게 됐다. 예전에는 “격자 50 m, 3개 층, 정상상태” 같은 서술만 보고 판단했는데, 그 서술로는 어떤 경계조건이 어디에 걸렸는지 알 수 없었다. 물론 심사자가 실제로 돌려 보느냐는 별개의 문제다. ↩
-
USGS 소프트웨어가 퍼블릭 도메인이라는 사실이 여기서도 결정적이다. FloPy가 CC0이므로 상용 GUI 벤더가 내부에서 갖다 쓰든, 컨설팅 회사가 사내 도구에 통째로 넣든 아무 문제가 없다. GPL이었다면 지금의 생태계는 없었을 것이다. 라이선스 한 줄이 도구의 운명을 정하는 사례를 MODFLOW 문서에서 한 번 보고 여기서 또 본다. ↩
-
“코드를 감싸는 파이썬 층”은 지난 10년 수치해석 도구 전반의 공통 패턴이다. 포트란·C 엔진은 그대로 두고 그 위에 스크립트 계층을 얹는다. 엔진을 파이썬으로 다시 쓰지 않는 이유는 명백하다 — 느리기도 하고, 무엇보다 40년치 검증 이력이 그 포트란에 붙어 있다. 규제 문서에 제출할 수 있는 것은 검증 이력이지 우아함이 아니다. ↩