diff --git a/src/hyddown/HydDown.code-workspace b/src/hyddown/HydDown.code-workspace new file mode 100644 index 0000000..e4e7c68 --- /dev/null +++ b/src/hyddown/HydDown.code-workspace @@ -0,0 +1,7 @@ +{ + "folders": [ + { + "path": "../.." + } + ] +} \ No newline at end of file diff --git a/src/hyddown/__pycache__/hdclass.cpython-312.pyc b/src/hyddown/__pycache__/hdclass.cpython-312.pyc deleted file mode 100644 index a82f904..0000000 Binary files a/src/hyddown/__pycache__/hdclass.cpython-312.pyc and /dev/null differ diff --git a/src/hyddown/__pycache__/validator.cpython-312.pyc b/src/hyddown/__pycache__/validator.cpython-312.pyc deleted file mode 100644 index 40a58de..0000000 Binary files a/src/hyddown/__pycache__/validator.cpython-312.pyc and /dev/null differ diff --git a/src/hyddown/examples/LPG_Droste_1.yml b/src/hyddown/examples/LPG_Droste_1.yml new file mode 100644 index 0000000..8d49b3b --- /dev/null +++ b/src/hyddown/examples/LPG_Droste_1.yml @@ -0,0 +1,29 @@ +vessel: + length: 3.6 + diameter: 1.25 + orientation: "horizontal" + heat_capacity: 500 + density: 7700 + thickness: 0.0059 + liquid_level: 0.625 #1.15 + type: "DIN" +initial: + temperature: 283 + pressure: 1350000 + fluid: "propane" +calculation: + type: "energybalance" + time_step: 1 + end_time: 900. +valve: + flow: "discharge" + type: "psv" + diameter: 0.015 + discharge_coef: 0.975 + set_pressure: 1730000 + blowdown: 0.0 + back_pressure: 101300. +heat_transfer: + type: "s-b" + fire: "scandpower_pool" + scaling: 0.7 diff --git a/src/hyddown/examples/LPG_specified_q.yml b/src/hyddown/examples/LPG_specified_q.yml new file mode 100644 index 0000000..75814d3 --- /dev/null +++ b/src/hyddown/examples/LPG_specified_q.yml @@ -0,0 +1,34 @@ +vessel: + length: 3.5 + diameter: 1.6763 + orientation: "horizontal" + type: "Hemispherical" + heat_capacity: 500 + density: 7700 + thickness: 0.01185 + liquid_level: 0.4668 +initial: + temperature: 279 + pressure: 550000 + fluid: "propane" +calculation: + type: "energybalance" + time_step: 1 + end_time: 720. +valve: + flow: "discharge" + type: "psv" + diameter: 0.0475 + discharge_coef: 0.975 + set_pressure: 1430000 + blowdown: 0.24 + back_pressure: 101300. +heat_transfer: + type: "specified_q" + h_inner: "calc" + q_outer: + time: [0.0, 120.0, 130.0, 140.0, 150.0, 160.0, 170.0, 180.0, 190.0, 200.0, 210.0, 220.0, 230.0, 240.0, 250.0, 260.0, 270.0, 280.0, 290.0, 300.0, 310.0, 320.0, 330.0, 340.0, 350.0, 360.0, 370.0, 380.0, 390.0, 400.0, 410.0, 420.0, 430.0, 440.0, 450.0, 460.0, 470.0, 480.0, 490.0, 500.0, 510.0, 520.0, 530.0, 540.0, 550.0, 560.0, 570.0, 580.0, 590.0, 600.0, 610.0, 620.0, 630.0, 640.0, 650.0, 660.0, 670.0, 680.0, 690.0, 700.0, 710.0, 720.0, 730] + heat_flux: [0, 0, 64020.08, 68626.41, 73069.31, 73570.21, 73847.06, 74417.00, 74927.83, 75194.43, 75618.54, 76055.93, 76463.23, 76864.81, 77259.94, 77695.54, 78169.01, 78484.32, 79016.89, 79549.52, 80082.12, 80614.72, 81197.03, 81752.35, 82349.76, 83005.97, 83712.95, 84350.98, 84989.33, 85627.91, 86266.67, 86970.51, 86777.99, 86144.24, 85510.49, 84925.77, 84329.11, 83798.73, 83212.07, 82560.15, 81998.49, 81405.55, 80937.47, 81351.16, 81910.74, 82362.27, 82668.13, 82959.11, 83198.99, 83401.45, 83075.37, 82536.11, 81948.55, 81460.60, 80966.35, 80534.18, 79998.41, 79364.66, 78730.91, 78245.89, 77264.09, 42895.00, 2057.16] +rupture: + material: "CS_360LT" + fire: "scandpower_pool_peak" diff --git a/src/hyddown/hdclass.py b/src/hyddown/hdclass.py index 8d75b8f..c92617a 100644 --- a/src/hyddown/hdclass.py +++ b/src/hyddown/hdclass.py @@ -289,6 +289,33 @@ def read_input(self): self.scaling = 1.0 if self.input["valve"]["flow"] == "filling": raise ValueError("Filling and Fire heat load not implemented") + if self.heat_method == "specified_q": + self.vessel_cp = self.input["vessel"]["heat_capacity"] + self.vessel_density = self.input["vessel"]["density"] + self.vessel_orientation = self.input["vessel"]["orientation"] + self.thickness = self.input["vessel"]["thickness"] + # Handle q_outer: can be a number (fixed) or dict (time-dependent) + q_outer_input = self.input["heat_transfer"]["q_outer"] + if isinstance(q_outer_input, (int, float)): + # Fixed heat flux + self.q_outer_func = lambda t: q_outer_input + elif isinstance(q_outer_input, dict): + # Time-dependent heat flux from dict + time_data = np.array(q_outer_input["time"]) + heat_flux_data = np.array(q_outer_input["heat_flux"]) + self.q_outer_func = lambda t: np.interp(t, time_data, heat_flux_data) + else: + raise ValueError("q_outer must be a number or dict with time/heat_flux") + # Handle h_inner + if "h_inner" in self.input["heat_transfer"]: + self.h_in = self.input["heat_transfer"]["h_inner"] + else: + self.h_in = "calc" + if self.input["valve"]["flow"] == "filling": + if "D_throat" in self.input["heat_transfer"]: + self.D_throat = self.input["heat_transfer"]["D_throat"] + else: + self.D_throat = self.input["vessel"]["diameter"] def initialize(self): """ @@ -802,7 +829,7 @@ def run(self, disable_pbar=True): # HEAT TRANSFER COEFFICIENT CALCULATIONS # ==================================================================== # Calculate convective heat transfer coefficients for specified_h or detailed methods - if self.heat_method == "specified_h" or self.heat_method == "detailed": + if self.heat_method == "specified_h" or self.heat_method == "detailed" or self.heat_method == "specified_q": if self.h_in == "calc": if self.vessel_orientation == "horizontal": L = self.diameter @@ -928,24 +955,33 @@ def run(self, disable_pbar=True): # Heat transfer from environment to unwetted outer wall # Use outer surface area directly (outer surface is exposed to environment) - self.Q_outer[i] = ( - (self.surf_area_outer - wetted_area_outer) - * self.h_out - * (self.Tamb - self.T_outer_wall[i - 1]) - ) - self.q_outer[i] = self.h_out * ( - self.Tamb - self.T_outer_wall[i - 1] - ) + if self.heat_method == "specified_q": + # User-defined heat flux (time-dependent or constant) + q_ext = self.q_outer_func(self.time_array[i]) + self.Q_outer[i] = q_ext * (self.surf_area_outer - wetted_area_outer) + self.q_outer[i] = q_ext + self.Q_outer_wetted[i] = q_ext * wetted_area_outer + self.q_outer_wetted[i] = q_ext + else: + # specified_h or detailed: convective heat transfer + self.Q_outer[i] = ( + (self.surf_area_outer - wetted_area_outer) + * self.h_out + * (self.Tamb - self.T_outer_wall[i - 1]) + ) + self.q_outer[i] = self.h_out * ( + self.Tamb - self.T_outer_wall[i - 1] + ) - self.Q_outer_wetted[i] = ( - wetted_area_outer - * self.h_out - * (self.Tamb - self.T_outer_wall_wetted[i - 1]) - ) + self.Q_outer_wetted[i] = ( + wetted_area_outer + * self.h_out + * (self.Tamb - self.T_outer_wall_wetted[i - 1]) + ) - self.q_outer_wetted[i] = self.h_out * ( - self.Tamb - self.T_outer_wall_wetted[i - 1] - ) + self.q_outer_wetted[i] = self.h_out * ( + self.Tamb - self.T_outer_wall_wetted[i - 1] + ) if np.isnan(self.Q_outer_wetted[i]): self.Q_outer_wetted[i] = 0 diff --git a/src/hyddown/validator.py b/src/hyddown/validator.py index 677c64d..0fa654e 100644 --- a/src/hyddown/validator.py +++ b/src/hyddown/validator.py @@ -185,12 +185,13 @@ def validate_mandatory_ruleset(input): "fire", "s-b", "D_throat", + "q_outer", "scaling", ], "schema": { "type": { "type": "string", - "allowed": ["specified_Q", "specified_h", "specified_U", "s-b"], + "allowed": ["specified_Q", "specified_h", "specified_U", "s-b", "specified_q"], }, "Q_fix": {"required": False, "type": "number"}, "U_fix": {"required": False, "type": "number", "min": 0}, @@ -208,6 +209,13 @@ def validate_mandatory_ruleset(input): ], }, "D_throat": {"required": False, "type": "number", "min": 0}, + "q_outer": { + "required": False, + "anyof": [ + {"type": "number"}, + {"type": "dict"}, + ], + }, "scaling": { "required": False, "type": "number", @@ -701,6 +709,65 @@ def heat_transfer_validation(input): }, }, } + + elif input["heat_transfer"]["type"] == "specified_q": + schema_heattransfer = { + "initial": {"required": True}, + "calculation": {"required": True}, + "validation": {"required": False}, + "valve": {"required": True}, + "rupture": {"required": False}, + "vessel": { + "required": True, + "type": "dict", + "allow_unknown": False, + "schema": { + "length": {"required": True, "type": "number"}, + "diameter": {"required": True, "type": "number"}, + "thickness": {"required": True, "type": "number", "min": 0.0}, + "heat_capacity": {"required": True, "type": "number", "min": 1}, + "density": {"required": True, "type": "number", "min": 1}, + "orientation": { + "required": True, + "type": "string", + "allowed": ["vertical", "horizontal"], + }, + "type": { + "required": False, + "type": "string", + "allowed": ["Flat-end", "DIN", "ASME F&D", "Hemispherical"], + }, + "liquid_level": { + "required": False, + "type": "number", + "min": 0, + }, + }, + }, + "heat_transfer": { + "required": True, + "type": "dict", + "allow_unknown": False, + "allowed": ["type", "q_outer", "h_inner"], + "schema": { + "type": {"type": "string", "allowed": ["specified_q"]}, + "q_outer": { + "required": True, + "anyof": [ + {"type": "number"}, + { + "type": "dict", + "schema": { + "time": {"required": True, "type": "list"}, + "heat_flux": {"required": True, "type": "list"}, + }, + }, + ], + }, + "h_inner": {"required": False, "type": ["number", "string"]}, + }, + }, + } v = Validator(schema_heattransfer) retval = v.validate(input) if v.errors: