/* SPDX-FileCopyrightText: Copyright (c) 2026, the adamantine authors. * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ #include #include #include #include namespace adamantine { template GaussianHeatSource::GaussianHeatSource( 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()) { // PropertyTreeInput sources.beam_X.A _A = beam_database.get("A"); // PropertyTreeInput sources.beam_X.B _B = beam_database.get("B"); } template void GaussianHeatSource::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); for (unsigned int d = 0; d < 3; ++d) { _beam_center(d) = path(d); } _depth = this->_beam.depth; _radius_squared = this->_beam.radius_squared; double const aspect_ratio = std::max(1.0, _depth[0] / this->_beam.radius); double const n = std::min(std::max(0.0, _A * std::log2(aspect_ratio) + _B), 9.0); _k = std::pow(2.0, n); double const V0 = 0.5 * dealii::numbers::PI * _radius_squared[0] * _depth[0] * std::tgamma(1.0 / _k) / (_k * std::pow(3.0, 1.0 / _k)); double const effective_power = this->_beam.absorption_efficiency * this->_beam.max_power * segment_power_modifier; _alpha = effective_power / V0; } template double GaussianHeatSource::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 > 0. || z < -_depth[0]) { return 0.; } 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]); } if (xpy_squared > 5. * _radius_squared[0]) { return 0.; } double const radial_component = std::exp(-2.0 * xpy_squared / _radius_squared[0]); double const depth_component = std::exp(-3.0 * std::pow(std::abs(z / _depth[0]), _k)); return _alpha[0] * radial_component * depth_component; } template dealii::VectorizedArray GaussianHeatSource::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]; dealii::VectorizedArray depth_mask; for (unsigned int i = 0; i < depth_mask.size(); ++i) { depth_mask[i] = (z[i] > 0. || z[i] < -_depth[i]) ? 0. : 1.; } 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; } 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 (xpy_mask.sum() == 0) { return xpy_mask; } dealii::VectorizedArray shape = _k; auto const radial_component = std::exp(-2.0 * xpy_squared / _radius_squared); auto const depth_component = std::exp(-3.0 * std::pow(std::abs(z / _depth), shape)); return depth_mask * xpy_mask * _alpha * radial_component * depth_component; } template dealii::BoundingBox GaussianHeatSource::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()) { 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); 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(GaussianHeatSource)