/* SPDX-FileCopyrightText: Copyright (c) 2020 - 2026, the adamantine authors. * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ #include #include #include #include namespace adamantine { template ElectronBeamHeatSource::ElectronBeamHeatSource( boost::property_tree::ptree const &beam_database, boost::optional const &units_optional_database) : HeatSource(beam_database, units_optional_database), _five_axis(this->_scan_path.is_five_axis()) { } template void ElectronBeamHeatSource::update_time(double time) { double const segment_power_modifier = this->_scan_path.get_power_modifier(time); this->_source_on = (segment_power_modifier > 0.0); dealii::Point<3> const &path = this->_scan_path.value(time); _quaternion = this->_scan_path.get_quaternion(time); // Copy the scalar value of path to the vectorized data type for (unsigned int d = 0; d < 3; ++d) { _beam_center(d) = path(d); } _alpha = -this->_beam.absorption_efficiency * this->_beam.max_power * segment_power_modifier * _log_01 / (dealii::numbers::PI * this->_beam.radius_squared * this->_beam.depth); _depth = this->_beam.depth; _inv_depth = 1.0 / _depth; _radius_squared = this->_beam.radius_squared; _inv_radius_squared = 1.0 / _radius_squared; } template double ElectronBeamHeatSource::value(dealii::Point const &point) const { dealii::Point rotated_point; if constexpr (dim == 2) { rotated_point = point; } else { rotated_point = _five_axis ? _quaternion.rotate(point) : point; } double const z = rotated_point[axis::z] - _beam_center[axis::z][0]; if ((z + _depth[0]) < 0.) { return 0.; } else { double const z_rel = z * _inv_depth[0]; double const distribution_z = -3. * dealii::Utilities::fixed_power<2>(z_rel) - 2. * z_rel + 1.; double xpy_squared = dealii::Utilities::fixed_power<2>( rotated_point[axis::x] - _beam_center[axis::x][0]); if constexpr (dim == 3) { xpy_squared += dealii::Utilities::fixed_power<2>( rotated_point[axis::y] - _beam_center[axis::y][0]); } // Evaluating the exponential is very expensive. Return early if we know // that the heat source will be small. if (xpy_squared > 5. * _radius_squared[0]) { return 0.; } // Electron beam heat source equation double heat_source = _alpha[0] * std::exp(_log_01 * xpy_squared * _inv_radius_squared[0]) * distribution_z; return heat_source; } } template dealii::VectorizedArray ElectronBeamHeatSource::value( dealii::Point> const &points) const { dealii::Point> rotated_points; if constexpr (dim == 2) { rotated_points = points; } else { rotated_points = _five_axis ? _quaternion.rotate(points) : points; } auto const z = rotated_points[axis::z] - _beam_center[axis::z]; auto const z_depth = z + _depth; dealii::VectorizedArray depth_mask; for (unsigned int i = 0; i < depth_mask.size(); ++i) { depth_mask[i] = z_depth[i] < 0. ? 0. : 1.; } // If all the lanes of depth_mask are zero, we can return early if (depth_mask.sum() == 0) { return depth_mask; } auto xpy_squared = dealii::Utilities::fixed_power<2>( rotated_points[axis::x] - _beam_center[axis::x]); if constexpr (dim == 3) { auto const y_squared = dealii::Utilities::fixed_power<2>( rotated_points[axis::y] - _beam_center[axis::y]); xpy_squared += y_squared; } // Evaluating the exponential is very expensive. Return early if we know // that the heat source will be small. auto const xpy_area = 5. * _radius_squared - xpy_squared; dealii::VectorizedArray xpy_mask; for (unsigned int i = 0; i < xpy_mask.size(); ++i) { xpy_mask[i] = xpy_area[i] < 0. ? 0. : 1.; } // If all the lanes of xpy_mask are zero, we can return early if (xpy_mask.sum() == 0) { return xpy_mask; } dealii::VectorizedArray minus_three = -3.; dealii::VectorizedArray one = 1.; dealii::VectorizedArray z_rel = z * _inv_depth; dealii::VectorizedArray distribution_z = dealii::Utilities::fixed_power<2>(z_rel); distribution_z *= minus_three; distribution_z -= z_rel; distribution_z -= z_rel; distribution_z += one; dealii::VectorizedArray exponent = _log_01 * xpy_squared; exponent *= _inv_radius_squared; return depth_mask * _alpha * std::exp(exponent) * distribution_z; } template dealii::BoundingBox ElectronBeamHeatSource::get_bounding_box(double const time, double const scaling_factor) const { dealii::Point<3> const &beam_center = this->_scan_path.value(time, false); if constexpr (dim == 2) { return {{{beam_center[axis::x] - scaling_factor * this->_beam.radius, beam_center[axis::z] - scaling_factor * this->_beam.depth}, {beam_center[axis::x] + scaling_factor * this->_beam.radius, beam_center[axis::z]}}}; } else { dealii::Point<3> max_corner( beam_center[axis::x] + scaling_factor * this->_beam.radius, beam_center[axis::y] + scaling_factor * this->_beam.radius, beam_center[axis::z]); dealii::Point<3> min_corner( beam_center[axis::x] - scaling_factor * this->_beam.radius, beam_center[axis::y] - scaling_factor * this->_beam.radius, beam_center[axis::z] - scaling_factor * this->_beam.depth); if (this->_scan_path.is_five_axis()) { // We need to rotate the box to match the scan path Quaternion quaternion = this->_scan_path.get_quaternion(time); dealii::Point<3> rotated_max_corner = quaternion.rotate(max_corner); dealii::Point<3> rotated_min_corner = quaternion.rotate(min_corner); // We need to recompute the min and max corners after rotation. dealii::Point<3> new_max_corner; dealii::Point<3> new_min_corner; for (int d = 0; d < 3; ++d) { new_max_corner[d] = std::max(rotated_max_corner[d], rotated_min_corner[d]); new_min_corner[d] = std::min(rotated_max_corner[d], rotated_min_corner[d]); } return {{new_min_corner, new_max_corner}}; } else { return {{min_corner, max_corner}}; } } } } // namespace adamantine INSTANTIATE_DIM(ElectronBeamHeatSource)