/** * @brief Computes keff by performing a series of transport sweep and * source updates. * @details This is the main method exposed to the user through the Python * interface to run a simulation. The method makes an initial guess * for the scalar and boundary fluxes and peforms transport sweeps * and source updates until convergence. The method may be called * by the user from Python as follows: * * @code * max_iters = 1000 * solver.convergeSource(max_iters) * @endcode * * @param max_iterations the maximum number of source iterations to allow * @return the value of the computed eigenvalue \f$ k_{eff} \f$ */ FP_PRECISION Solver::convergeSource(int max_iterations) { /* Error checking */ if (_geometry == NULL) log_printf(ERROR, "The Solver is unable to converge the source " "since it does not contain a Geometry"); if (_track_generator == NULL) log_printf(ERROR, "The Solver is unable to converge the source " "since it does not contain a TrackGenerator"); log_printf(NORMAL, "Converging the source..."); /* Clear all timing data from a previous simulation run */ clearTimerSplits(); /* Start the timer to record the total time to converge the source */ _timer->startTimer(); /* Counter for the number of iterations to converge the source */ _num_iterations = 0; /* An initial guess for the eigenvalue */ _k_eff = 1.0; /* The residual on the source */ FP_PRECISION residual = 0.0; /* The old residual and k_eff */ FP_PRECISION residual_old = 1.0; FP_PRECISION keff_old = 1.0; /* Initialize data structures */ initializePolarQuadrature(); initializeFluxArrays(); initializeSourceArrays(); buildExpInterpTable(); initializeFSRs(); if (_cmfd != NULL && _cmfd->isFluxUpdateOn()) initializeCmfd(); /* Check that each FSR has at least one segment crossing it */ checkTrackSpacing(); /* Set scalar flux to unity for each region */ flattenFSRSources(1.0); flattenFSRFluxes(1.0); zeroTrackFluxes(); /* Source iteration loop */ for (int i=0; i < max_iterations; i++) { log_printf(NORMAL, "Iteration %d: \tk_eff = %1.6f" "\tres = %1.3E", i, _k_eff, residual); normalizeFluxes(); residual = computeFSRSources(); transportSweep(); addSourceToScalarFlux(); /* Solve CMFD diffusion problem and update MOC flux */ if (_cmfd != NULL && _cmfd->isFluxUpdateOn()){ _k_eff = _cmfd->computeKeff(i); _cmfd->updateBoundaryFlux(_tracks, _boundary_flux, _tot_num_tracks); } else computeKeff(); _num_iterations++; /* Check for convergence of the fission source distribution */ if (i > 1 && residual < _source_convergence_thresh) { _timer->stopTimer(); _timer->recordSplit("Total time to converge the source"); return _k_eff; } } _timer->stopTimer(); _timer->recordSplit("Total time to converge the source"); log_printf(WARNING, "Unable to converge the source after %d iterations", max_iterations); return _k_eff; }
/** * @brief This method performs one transport sweep of all azimuthal angles, * Tracks, Track segments, polar angles and energy groups. * @details The method integrates the flux along each Track and updates the * boundary fluxes for the corresponding output Track, while updating * the scalar flux in each flat source region. */ void CPUSolver::transportSweep() { int tid; int min_track, max_track; Track* curr_track; int azim_index; int num_segments; segment* curr_segment; segment* segments; FP_PRECISION* track_flux; log_printf(DEBUG, "Transport sweep with %d OpenMP threads", _num_threads); /* Initialize flux in each FSr to zero */ flattenFSRFluxes(0.0); if (_cmfd->getMesh()->getCmfdOn()) zeroSurfaceCurrents(); /* Loop over azimuthal angle halfspaces */ for (int i=0; i < 2; i++) { /* Compute the minimum and maximum Track IDs corresponding to * this azimuthal angular halfspace */ min_track = i * (_tot_num_tracks / 2); max_track = (i + 1) * (_tot_num_tracks / 2); /* Loop over each thread within this azimuthal angle halfspace */ #pragma omp parallel for private(curr_track, azim_index, num_segments, \ curr_segment, segments, track_flux, tid) schedule(guided) for (int track_id=min_track; track_id < max_track; track_id++) { tid = omp_get_thread_num(); /* Initialize local pointers to important data structures */ curr_track = _tracks[track_id]; azim_index = curr_track->getAzimAngleIndex(); num_segments = curr_track->getNumSegments(); segments = curr_track->getSegments(); track_flux = &_boundary_flux(track_id,0,0,0); /* Loop over each Track segment in forward direction */ for (int s=0; s < num_segments; s++) { curr_segment = &segments[s]; scalarFluxTally(curr_segment, azim_index, track_flux, &_thread_fsr_flux(tid),true); } /* Transfer boundary angular flux to outgoing Track */ transferBoundaryFlux(track_id, azim_index, true, track_flux); /* Loop over each Track segment in reverse direction */ track_flux += _polar_times_groups; for (int s=num_segments-1; s > -1; s--) { curr_segment = &segments[s]; scalarFluxTally(curr_segment, azim_index, track_flux, &_thread_fsr_flux(tid),false); } /* Transfer boundary angular flux to outgoing Track */ transferBoundaryFlux(track_id, azim_index, false, track_flux); } } return; }