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