Line data Source code
1 : //* This file is part of the MOOSE framework 2 : //* https://mooseframework.inl.gov 3 : //* 4 : //* All rights reserved, see COPYRIGHT for full restrictions 5 : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT 6 : //* 7 : //* Licensed under LGPL 2.1, please see LICENSE for details 8 : //* https://www.gnu.org/licenses/lgpl-2.1.html 9 : 10 : #include "SCMTriPowerIC.h" 11 : #include "Function.h" 12 : #include "TriSubChannelMesh.h" 13 : 14 : registerMooseObject("SubChannelApp", SCMTriPowerIC); 15 : registerMooseObjectRenamed("SubChannelApp", TriPowerIC, "06/30/2025 24:00", SCMTriPowerIC); 16 : 17 : InputParameters 18 348 : SCMTriPowerIC::validParams() 19 : { 20 348 : InputParameters params = TriSubChannelBaseIC::validParams(); 21 348 : params.addClassDescription( 22 : "Computes axial power rate (W/m) that goes into the subchannel cells " 23 : "or is assigned to the fuel pins, in a triangular lattice arrangement"); 24 696 : params.addRequiredParam<PostprocessorName>( 25 : "power", "The postprocessor or Real to use for the total power of the subassembly [W]"); 26 696 : params.addRequiredParam<std::string>( 27 : "filename", "name of radial power profile .txt file (should be a single column) [UnitLess]."); 28 696 : params.addParam<FunctionName>("axial_heat_rate", 29 : "1.0", 30 : "user provided normalized function of axial heat rate [Unitless]. " 31 : "The integral over pin length should equal the heated length"); 32 348 : return params; 33 0 : } 34 : 35 195 : SCMTriPowerIC::SCMTriPowerIC(const InputParameters & params) 36 : : TriSubChannelBaseIC(params), 37 195 : _power(getPostprocessorValue("power")), 38 195 : _numberoflines(0), 39 585 : _filename(getParam<std::string>("filename")), 40 390 : _axial_heat_rate(getFunction("axial_heat_rate")) 41 : { 42 195 : if (processor_id() > 0) 43 65 : return; 44 : 45 130 : auto n_pins = _mesh.getNumOfPins(); 46 130 : auto heated_length = _mesh.getHeatedLength(); 47 : 48 130 : _power_dis.resize(n_pins, 1); 49 130 : _power_dis.setZero(); 50 130 : _pin_power_correction.resize(n_pins, 1); 51 130 : _pin_power_correction.setOnes(); 52 : 53 : Real vin; 54 130 : std::ifstream inFile; 55 : 56 130 : inFile.open(_filename); 57 130 : if (!inFile) 58 0 : mooseError(name(), ": Unable to open file: ", _filename); 59 : 60 4496 : while (inFile >> vin) 61 4366 : _numberoflines += 1; 62 : 63 130 : if (inFile.fail() && !inFile.eof()) 64 0 : mooseError(name(), ": Non numerical input at line: ", _numberoflines); 65 : 66 130 : if (_numberoflines != n_pins) 67 0 : mooseError(name(), ": Radial profile file doesn't have correct size: ", n_pins); 68 130 : inFile.close(); 69 : 70 130 : inFile.open(_filename); 71 : int i = 0; 72 4496 : while (inFile >> vin) 73 : { 74 4366 : _power_dis(i, 0) = vin; 75 4366 : i++; 76 : } 77 130 : inFile.close(); 78 : 79 130 : auto sum = _power_dis.sum(); 80 : // full (100%) power of one pin [W] 81 130 : auto fpin_power = _power / sum; 82 : // actual pin power [W] 83 130 : _ref_power = _power_dis * fpin_power; 84 : // Convert the actual pin power to a linear heat rate [W/m] 85 130 : _ref_qprime = _ref_power / heated_length; 86 130 : } 87 : 88 : void 89 195 : SCMTriPowerIC::initialSetup() 90 : { 91 195 : if (processor_id() > 0) 92 65 : return; 93 130 : auto n_pins = _mesh.getNumOfPins(); 94 130 : auto nz = _mesh.getNumOfAxialCells(); 95 130 : auto z_grid = _mesh.getZGrid(); 96 130 : auto heated_length = _mesh.getHeatedLength(); 97 130 : auto unheated_length_entry = _mesh.getHeatedLengthEntry(); 98 : 99 130 : _estimate_power.resize(n_pins, 1); 100 130 : _estimate_power.setZero(); 101 3734 : for (unsigned int iz = 1; iz < nz + 1; iz++) 102 : { 103 : // Compute axial location of nodes. 104 3604 : auto z2 = z_grid[iz]; 105 3604 : auto z1 = z_grid[iz - 1]; 106 3604 : Point p1(0, 0, z1 - unheated_length_entry); 107 3604 : Point p2(0, 0, z2 - unheated_length_entry); 108 3604 : auto heat1 = _axial_heat_rate.value(_t, p1); 109 3604 : auto heat2 = _axial_heat_rate.value(_t, p2); 110 3604 : if (MooseUtils::absoluteFuzzyGreaterThan(z2, unheated_length_entry) && 111 2780 : MooseUtils::absoluteFuzzyLessThan(z1, unheated_length_entry + heated_length)) 112 : { 113 : // cycle through pins 114 66908 : for (unsigned int i_pin = 0; i_pin < n_pins; i_pin++) 115 : { 116 : // Compute the height of this element. 117 64720 : auto dz = z2 - z1; 118 : 119 : // calculation of power for the first heated segment if nodes don't align 120 64720 : if (MooseUtils::absoluteFuzzyGreaterThan(z2, unheated_length_entry) && 121 : MooseUtils::absoluteFuzzyLessThan(z1, unheated_length_entry)) 122 : { 123 : heat1 = 0.0; 124 : } 125 : 126 : // calculation of power for the last heated segment if nodes don't align 127 64720 : if (MooseUtils::absoluteFuzzyGreaterThan(z2, unheated_length_entry + heated_length) && 128 : MooseUtils::absoluteFuzzyLessThan(z1, unheated_length_entry + heated_length)) 129 : { 130 : heat2 = 0.0; 131 : } 132 64720 : _estimate_power(i_pin) += _ref_qprime(i_pin) * (heat1 + heat2) * dz / 2.0; 133 : } 134 : } 135 : } 136 : 137 : // if a Pin has zero power (_ref_qprime(i_pin) = 0) then I need to avoid dividing by zero. I 138 : // divide by a wrong non-zero number which is not correct but this error doesn't mess things cause 139 : // _ref_qprime(i_pin) = 0.0 140 130 : auto total_power = 0.0; 141 4496 : for (unsigned int i_pin = 0; i_pin < n_pins; i_pin++) 142 : { 143 4366 : total_power += _estimate_power(i_pin); 144 4366 : if (_estimate_power(i_pin) == 0.0) 145 344 : _estimate_power(i_pin) = 1.0; 146 : } 147 : // We need to correct the linear power assigned to the nodes of each pin 148 : // so that the total power calculated by the trapezoidal rule agrees with the power assigned by 149 : // the user. 150 130 : _pin_power_correction = _ref_power.cwiseQuotient(_estimate_power); 151 130 : _console << "###########################################" << std::endl; 152 130 : _console << "Total power estimation by IC kernel before correction: " << total_power << " [W] " 153 130 : << std::endl; 154 130 : _console << "IC Power correction vector :\n" << _pin_power_correction << " \n"; 155 130 : } 156 : 157 : Real 158 258876 : SCMTriPowerIC::value(const Point & p) 159 : { 160 : auto heat_rate = 0.0; 161 258876 : auto heated_length = _mesh.getHeatedLength(); 162 258876 : auto unheated_length_entry = _mesh.getHeatedLengthEntry(); 163 : Point p1(0, 0, unheated_length_entry); 164 : Point P = p - p1; 165 258876 : auto pin_mesh_exist = _mesh.pinMeshExist(); 166 : 167 : /// assign power to the nodes located inside the heated section 168 258876 : if (MooseUtils::absoluteFuzzyGreaterEqual(p(2), unheated_length_entry) && 169 212382 : MooseUtils::absoluteFuzzyLessEqual(p(2), unheated_length_entry + heated_length)) 170 : { 171 167766 : if (pin_mesh_exist) 172 : { 173 : // project axial heat rate on pins 174 52278 : auto i_pin = _mesh.getPinIndexFromPoint(p); 175 52278 : return _ref_qprime(i_pin) * _pin_power_correction(i_pin) * _axial_heat_rate.value(_t, P); 176 : } 177 : else 178 : { 179 : // Determine which subchannel this point is in. 180 115488 : auto i_ch = _mesh.getSubchannelIndexFromPoint(p); 181 115488 : auto subch_type = _mesh.getSubchannelType(i_ch); 182 : // project axial heat rate on subchannels 183 : { 184 : double factor; 185 115488 : switch (subch_type) 186 : { 187 : case EChannelType::CENTER: 188 : factor = 1.0 / 6.0; 189 : break; 190 31968 : case EChannelType::EDGE: 191 : factor = 1.0 / 4.0; 192 31968 : break; 193 : case EChannelType::CORNER: 194 : factor = 1.0 / 6.0; 195 : break; 196 : default: 197 : return 0.0; // handle invalid subch_type if needed 198 : } 199 399456 : for (auto i_pin : _mesh.getChannelPins(i_ch)) 200 : { 201 283968 : heat_rate += factor * _ref_qprime(i_pin) * _pin_power_correction(i_pin) * 202 283968 : _axial_heat_rate.value(_t, P); 203 : } 204 115488 : return heat_rate; 205 : } 206 : } 207 : } 208 : else 209 : return 0.0; 210 : }