Source code for pycomus.ComusDis.CmsDis

# --------------------------------------------------------------
# CmsDis.py
# Version: 1.0.0
# Author: Zhenjiang Wu
# Description: Set COMUS Model LPF Or BCF Layer Property.
# --------------------------------------------------------------
import os
from typing import List, Union, Tuple

import pycomus
from pycomus.ComusDis.GridCell import GridCell
from pycomus.ComusDis.GridLyr import LpfLayers, BcfLayers
from pycomus.Utils.CONSTANTS import LPF_LYR_FILE_NAME, BCF_LYR_FILE_NAME, LPF_LYR_PKG_NAME, BCF_LYR_PKG_NAME, \
    GRID_SPACE_FILE_NAME, CON_PKG_NAME


[docs] class ComusDis: """ Base Class """ def __init__(self, model, num_lyr: int = 1, num_row: int = 1, num_col: int = 1, x_coord: Union[float, int] = 0, y_coord: Union[float, int] = 0, row_space: Union[float, int, List[float]] = 1, col_space: Union[float, int, List[float]] = 1): # Check layer,row,col if isinstance(num_lyr, int): self.num_lyr: int = num_lyr if num_lyr < 1: raise ValueError("num_lyr should be greater than 0!") else: raise ValueError("num_lyr should be int") if isinstance(num_row, int): self.num_row: int = num_row if num_row < 1: raise ValueError("num_row should be greater than 0!") else: raise ValueError("num_row should be int") if isinstance(num_col, int): self.num_col: int = num_col if num_col < 1: raise ValueError("num_col should be greater than 0!") else: raise ValueError("num_col should be int") # Check x_coord and y_coord if isinstance(x_coord, (float, int)): self.x_coord: Union[float, int] = x_coord else: raise ValueError("x_coord should be an integer or a float.") if isinstance(y_coord, (float, int)): self.y_coord: Union[float, int] = y_coord else: raise ValueError("y_coord should be an integer or a float.") # Check row_space and col_space if isinstance(row_space, (float, int)): self.row_space: List[Union[int, float]] = [row_space] * num_row elif isinstance(row_space, list) and num_row != len(row_space): raise ValueError("row_space grid spacing length should be the same as num_row!") else: self.row_space: List[Union[int, float]] = row_space if not all(x > 0 for x in self.row_space): raise ValueError("row_space should be greater than 0") if isinstance(col_space, (float, int)): self.col_space: List[Union[int, float]] = [col_space] * num_col elif isinstance(col_space, list) and num_col != len(col_space): raise ValueError("col_space grid spacing length should be the same as num_col!") else: self.col_space: List[Union[int, float]] = col_space if not all(x > 0 for x in self.col_space): raise ValueError("col_space should be greater than 0") self._model = model @classmethod def _load_control_params(cls, ctrl_params_file: str) -> Tuple: with open(ctrl_params_file, 'r') as file: lines: List[str] = file.readlines() if len(lines) != 2: raise ValueError("The Control Params file should have exactly two lines of data.") if len(lines[0].strip().split()) != 30: raise ValueError("The Control Params file should have 30 fields.") data: List[str] = lines[1].strip().split() num_lyr: int = int(data[0]) num_row: int = int(data[1]) num_col: int = int(data[2]) x_coord: float = float(data[5]) y_coord: float = float(data[6]) if num_lyr < 1: raise ValueError("num_lyr should be greater than 0!") if num_row < 1: raise ValueError("num_row should be greater than 0!") if num_col < 1: raise ValueError("num_col should be greater than 0!") return num_lyr, num_row, num_col, x_coord, y_coord @classmethod def _load_grid_space(cls, grd_space_file: str, num_row: int, num_col: int) -> Tuple: with open(grd_space_file, 'r') as file: lines: List[str] = file.readlines()[1:] expectLength: int = num_row + num_col if len(lines) != expectLength: raise ValueError( f"The Grid Space file should have exactly {expectLength} lines of data(not include header).") if len(lines[0].strip().split()) != 3: raise ValueError("The Control Params file should have 3 fields(ATTI NUMID DELT).") row_space: List = [] col_space: List = [] for line in lines: data: List[str] = line.strip().split() if float(data[2]) <= 0: raise ValueError("Row space or col space should be greater than 0") if data[0] == "R": col_space.append(float(data[2])) else: row_space.append(float(data[2])) return row_space, col_space
[docs] def write_file(self, folder_path): with open(os.path.join(folder_path, GRID_SPACE_FILE_NAME), "w") as file: file.write("ATTI NUMID DELT\n") index: int = 1 for rowSpace in self.row_space: file.write(f"C {index} {rowSpace}\n") index += 1 index: int = 1 for colSpace in self.col_space: file.write(f"R {index} {colSpace}\n") index += 1
def __str__(self): return f"Grid And Layer:\n Number of layers : {self.num_lyr}\n Number of rows : {self.num_row}\n Number of cols : {self.num_col} \n" \ f" RowSpace : {self.row_space} \n ColSpace : {self.col_space} \n XCoord : {self.x_coord}\n " \ f"YCoord : {self.y_coord}"
[docs] class ComusDisLpf(ComusDis): """ COMUS Layer Property Flow Package Class(LPF). Attributes: ----------- model : pycomus.ComusModel COMUS Model Object num_lyr : int Number of layers num_row : int Number of rows num_col : int Number of columns row_space : Union[float, int, List[float]] A float, int, or List representing row spacing col_space : Union[float, int, List[float]] A float, int, or List representing column spacing x_coord : float Top left corner X coordinate y_coord : float Top left corner Y coordinate lyr_type : List[int] The data in lyr_type should be in [0: Confined, 1: Convertible] lyr_cbd : List[int] The data in lyr_cbd should be in [0: Quasi Three Dimensions-Disable, 1: Quasi Three Dimensions-Enable] lyr_ibs : List[int] The data in lyr_ibs should be in [0: IBS-Disable, 1: IBS-Enable] Methods: -------- __init__(self, model, num_lyr: int = 1, num_row: int = 1, num_col: int = 1, row_space: Union[float, int, List[float]] = 1, col_space: Union[float, int, List[float]] = 1, x_coord: float = 0, y_coord: float = 0, lyr_type: List[int] = None, lyr_cbd: List[int] = None, lyr_ibs: List[int] = None) Instantiate an instance of ComusDisLpf. load(cls, model, ctrl_params_file: str, grd_space_file: str, lpf_lyr_file: str) Load parameters from a LpfLyr.in file and create a ComusDisLpf instance. write_file(self, folder_path: str) Typically used as an internal function but can also be called directly, it outputs the `pycomus.ComusDisLpf` module to the specified path as <LpfLyr.in>. Returns: -------- instance: pycomus.ComusDisLpf COMUS LPF Layer Params Object. Example: -------- >>> import pycomus >>> model1 = pycomus.ComusModel(model_name="test") >>> modelDis = pycomus.ComusDisLpf(model1, 1, 20, 20, row_space=1, col_space=1, lyr_type=[1 for _ in range(1)], y_coord=1) """ def __init__(self, model, num_lyr: int = 1, num_row: int = 1, num_col: int = 1, row_space: Union[float, int, List[float]] = 1, col_space: Union[float, int, List[float]] = 1, x_coord: float = 0, y_coord: float = 0, lyr_type: List[int] = None, lyr_cbd: List[int] = None, lyr_ibs: List[int] = None): super().__init__(model, num_lyr, num_row, num_col, x_coord, y_coord, row_space, col_space) cms_pars: pycomus.ComusConPars = model.package[CON_PKG_NAME] if cms_pars.intblkm == 1: raise ValueError("In BCF format has been selected, it is not possible to add layers in LPF format.") if not lyr_type: lyr_type: List[int] = [0] * num_lyr if not lyr_cbd: lyr_cbd: List[int] = [0] * num_lyr if not lyr_ibs: lyr_ibs: List[int] = [0] * num_lyr if num_lyr != len(lyr_type): raise ValueError("lyr_type length should be the same as num_lyr!") if not all(x in [0, 1] for x in lyr_type): raise ValueError("The data in lyr_type should be in [0: Confined, 1: Convertible]!") if num_lyr != len(lyr_cbd): raise ValueError("lyr_cbd length should be the same as num_lyr!") if not all(x in [0, 1] for x in lyr_cbd): raise ValueError( "The data in lyr_cbd should be in [0: Quasi Three Dimensions-Disable, 1: Quasi Three Dimensions-Enable]!") if num_lyr != len(lyr_ibs): raise ValueError("lyr_ibs length should be the same as num_lyr!") if not all(x in [0, 1] for x in lyr_ibs): raise ValueError("The data in lyr_ibs should be in [0: IBS-Disable, 1: IBS-Enable]!") model.layers = [] for i in range(num_lyr): grid_cell: List[List[GridCell]] = [[GridCell() for _ in range(num_col)] for _ in range(num_row)] model.layers.append( LpfLayers(i + 1, lyr_type=lyr_type[i], lyr_cbd=lyr_cbd[i], lyr_ibs=lyr_ibs[i], grid_cells=grid_cell)) self.lyr_type = lyr_type self.lyr_cbd = lyr_cbd self.lyr_ibs = lyr_ibs model.package[LPF_LYR_PKG_NAME] = self
[docs] @classmethod def load(cls, model, ctrl_params_file: str, grd_space_file: str, lpf_lyr_file: str): """ Load parameters from a LpfLyr.in file and create a ComusDisLpf instance. Parameters: ---------------------------- model: pycomus.ComusModel COMUS Model Object. ctrl_params_file: str Control Params File(CtrlPar.in) grd_space_file: str Grid Space File(GrdSpace.in) lpf_lyr_file: str Lpf Layer Attribute File(LpfLyr.in) Returns: -------- instance: pycomus.ComusDisLpf COMUS Lpf Layer Attribute Object. Example: -------- >>> import pycomus >>> model1 = pycomus.ComusModel(model_name="test") >>> modelDis = pycomus.ComusDisLpf.load(model1, "./InputFiles/CtrlPar.in", "./InputFiles/GrdSpace.in", "./InputFiles/LpfLyr.in") """ # Check INTBLKM cms_pars: pycomus.ComusConPars = model.package[CON_PKG_NAME] if cms_pars.intblkm == 1: raise ValueError("In BCF format has been selected, it is not possible to add layers in LPF format.") # Check Control Params num_lyr, num_row, num_col, x_coord, y_coord = ComusDis._load_control_params(ctrl_params_file) # Check Grid Space Params row_space, col_space = ComusDis._load_grid_space(grd_space_file, num_row, num_col) # Check Lpf Layer Attribute with open(lpf_lyr_file, 'r') as file: lines: List[str] = file.readlines()[1:] if len(lines) != num_lyr: raise ValueError(f"The LPF Layer Params file should have exactly {num_lyr} lines of data.") if len(lines[0].strip().split()) != 6: raise ValueError("The LPF Layer Params file should have 6 fields.") idx_list: List[int] = [int(line.strip().split()[0]) for line in lines] if sorted(idx_list) != [i for i in range(1, num_lyr + 1)]: raise ValueError(f"Layer id should start from 1 and continue consecutively to {num_lyr + 1}.") lyr_type: List[int] = [int(line.strip().split()[1]) for line in lines] lyr_cbd: List[int] = [int(line.strip().split()[4]) for line in lines] lyr_ibs: List[int] = [int(line.strip().split()[5]) for line in lines] instance = cls(model, num_lyr, num_row, num_col, row_space, col_space, x_coord, y_coord, lyr_type, lyr_cbd, lyr_ibs) return instance
def __str__(self): res: str = super().__str__() return res + f"\n Layer TYPE : {self.lyr_type}\n Layer CBD : {self.lyr_cbd}\n Layer IBS : {self.lyr_ibs}"
[docs] def write_file(self, folder_path: str): """ Typically used as an internal function but can also be called directly, it outputs the `pycomus.ComusDisLpf` module to the specified path as <LpfLyr.in>. :param folder_path: Output folder path. """ super().write_file(folder_path) with open(os.path.join(folder_path, LPF_LYR_FILE_NAME), "w") as file: file.write("LYRID LYRTYPE LYRHANI LYRVKA LYRCBD LYRIBS\n") for i in range(self.num_lyr): file.write( f"{self._model.layers[i].lyr_id} {self._model.layers[i].lyr_type} -1 0 " f"{self._model.layers[i].lyr_cbd} {self._model.layers[i].lyr_ibs}\n")
[docs] class ComusDisBcf(ComusDis): """ COMUS Grid And Layer Property Flow Package Class(BCF). Attributes: ---------------------------- model: pycomus.ComusModel COMUS model object num_lyr: int Number of layers num_row: int Number of rows num_col: int Number of cols row_space: Union[float, int, List[float]] A float or List data that represents row spacing col_space: Union[float, int, List[float]] A float or List data that represents col spacing x_coord: float, optional Top left corner X coordinate, by default 0 y_coord: float, optional Top left corner Y coordinate, by default 0 lyr_type: List[int] The data in lyr_type should be in [0:Confined,1:Unconfined,2:Limited Convertible,3:Full Convertible] lyr_trpy: List[float], optional ky/kx, by default None lyr_ibs: List[int], optional The data in lyr_ibs should be in [0:IBS-Disable,1:IBS-Enable]!, by default None Methods: -------- __init__(self, model, num_lyr: int = 1, num_row: int = 1, num_col: int = 1, row_space: Union[float, int, List[float]] = 1, col_space: Union[float, int, List[float]] = 1, x_coord: float = 0, y_coord: float = 0, lyr_type: List[int] = None, lyr_trpy: List[float] = None, lyr_ibs: List[int] = None) Instantiate an instance of ComusDisBcf. load(cls, model, ctrl_params_file: str, grd_space_file: str, bcf_lyr_file: str) Load parameters from a LpfLyr.in file and create a ComusDisLpf instance. write_file(self, folder_path: str) Typically used as an internal function but can also be called directly, it outputs the `pycomus.ComusDisBcf` module to the specified path as <BcfLyr.in>. Returns: -------- instance: pycomus.ComusDisBcf COMUS BCF Layer Params Object. Example: -------- >>> import pycomus >>> model1 = pycomus.ComusModel(model_name="test") >>> modelDis = pycomus.ComusDisBcf(model1, 1, 20, 20, row_space=1, col_space=1, lyr_type=[1 for _ in range(1)], y_coord=1) """ def __init__(self, model, num_lyr: int = 1, num_row: int = 1, num_col: int = 1, row_space: Union[float, int, List[float]] = 1, col_space: Union[float, int, List[float]] = 1, x_coord: float = 0, y_coord: float = 0, lyr_type: List[int] = None, lyr_trpy: List[float] = None, lyr_ibs: List[int] = None) -> None: super().__init__(model, num_lyr, num_row, num_col, x_coord, y_coord, row_space, col_space) cms_pars = model.package[CON_PKG_NAME] if cms_pars.intblkm == 2: raise ValueError("In LPF format has been selected, it is not possible to add layers in BCF format.") if not lyr_type: lyr_type = [0] * num_lyr if not lyr_trpy: lyr_trpy = [1.0] * num_lyr if not lyr_ibs: lyr_ibs = [0] * num_lyr if num_lyr != len(lyr_type): raise ValueError("lyr_type length should be the same as num_lyr!") if not all(x in [0, 1, 2, 3] for x in lyr_type): raise ValueError( "The data in lyr_type should be in [0:Confined,1:Unconfined,2:Limited Convertible,3:Full Convertible]!") if num_lyr != len(lyr_trpy): raise ValueError("lyr_trpy length should be the same as num_lyr!") if num_lyr != len(lyr_ibs): raise ValueError("lyr_ibs length should be the same as num_lyr!") if not all(x in [0, 1] for x in lyr_ibs): raise ValueError("The data in lyr_ibs should be in [0:IBS-Disable,1:IBS-Enable]!") model.layers = [] self._model = model for i in range(num_lyr): gridCell = [[GridCell() for _ in range(num_col)] for _ in range(num_row)] model.layers.append( BcfLayers(i + 1, lyr_type=lyr_type[i], lyr_trpy=lyr_trpy[i], lyr_ibs=lyr_ibs[i], grid_cells=gridCell)) model.package[BCF_LYR_PKG_NAME] = self self.lyr_type = lyr_type self.lyr_trpy = lyr_trpy self.lyr_ibs = lyr_ibs
[docs] @classmethod def load(cls, model, ctrl_params_file: str, grd_space_file: str, bcf_lyr_file: str): """ Load parameters from a BcfLyr.in file and create a ComusDisBcf instance. Parameters: ---------------------------- model: pycomus.ComusModel COMUS Model Object. ctrl_params_file: str Control Params File(CtrlPar.in) grd_space_file: str Grid Space File(GrdSpace.in) bcf_lyr_file: str Bcf Layer Attribute File(BcfLyr.in) Returns: -------- instance: pycomus.ComusDisLpf COMUS Bcf Layer Attribute Object. Example: -------- >>> import pycomus >>> model1 = pycomus.ComusModel(model_name="test") >>> modelDis = pycomus.ComusDisBcf.load(model1, "./InputFiles/CtrlPar.in", "./InputFiles/GrdSpace.in", "./InputFiles/BcfLyr.in") """ # Check INTBLKM cms_pars = model.package[CON_PKG_NAME] if cms_pars.intblkm == 2: raise ValueError("In LPF format has been selected, it is not possible to add layers in BCF format.") # Check Control Params num_lyr, num_row, num_col, x_coord, y_coord = ComusDis._load_control_params(ctrl_params_file) # Check Grid Space Params row_space, col_space = ComusDis._load_grid_space(grd_space_file, num_row, num_col) # Check Bcf Layer Attribute with open(bcf_lyr_file, 'r') as file: lines = file.readlines()[1:] if len(lines) != num_lyr: raise ValueError(f"The BCF Layer Params file should have exactly {num_lyr} lines of data.") if len(lines[0].strip().split()) != 4: raise ValueError("The BCF Layer Params file should have 4 fields.") idx_list = [int(line.strip().split()[0]) for line in lines] if sorted(idx_list) != [i for i in range(1, num_lyr + 1)]: raise ValueError(f"Layer id should start from 1 and continue consecutively to {num_lyr + 1}.") lyr_type = [int(line.strip().split()[1]) for line in lines] lyr_trpy = [float(line.strip().split()[2]) for line in lines] lyr_ibs = [int(line.strip().split()[3]) for line in lines] instance = cls(model, num_lyr, num_row, num_col, row_space, col_space, x_coord, y_coord, lyr_type, lyr_trpy, lyr_ibs) return instance
def __str__(self): res: str = super().__str__() return res + f"\n Layer TYPE : {self.lyr_type}\n Layer TRPY : {self.lyr_trpy}\n Layer IBS : {self.lyr_ibs}"
[docs] def write_file(self, folder_path: str): """ Typically used as an internal function but can also be called directly, it outputs the `pycomus.ComusDisBcf` module to the specified path as <BcfLyr.in>. :param folder_path: Output folder path. """ super().write_file(folder_path) with open(os.path.join(folder_path, BCF_LYR_FILE_NAME), "w") as file: file.write("LYRID LYRCON LYRTRPY LYRIBS\n") for i in range(self.num_lyr): file.write( f"{self._model.layers[i].lyr_id} {self._model.layers[i].lyr_type} " f"{self._model.layers[i].lyr_trpy} {self._model.layers[i].lyr_ibs}\n")