OpenPFC  0.1.4
Phase Field Crystal simulation framework
Loading...
Searching...
No Matches
discrete_field.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2025 VTT Technical Research Centre of Finland Ltd
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
51#ifndef PFC_DISCRETE_FIELD_HPP
52#define PFC_DISCRETE_FIELD_HPP
53
54#include "array.hpp"
55#include "constants.hpp"
57#include "utils/show.hpp"
58#include <array>
59#include <cmath>
60#include <cstddef>
61#include <functional>
62#include <ostream>
63
64namespace pfc {
65
256template <typename T, size_t D> class DiscreteField {
257private:
258 Array<T, D> m_array;
259 const std::array<double, D> m_origin;
260 const std::array<double, D>
261 m_discretization;
262 const std::array<double, D> m_coords_low;
263 const std::array<double, D> m_coords_high;
269 std::array<double, D> calculate_coords_low(const std::array<int, D> &offset) {
270 std::array<double, D> coords_low;
271 for (size_t i = 0; i < D; i++)
272 coords_low[i] = m_origin[i] + offset[i] * m_discretization[i];
273 return coords_low;
274 }
275
280 std::array<double, D> calculate_coords_high(const std::array<int, D> &offset,
281 const std::array<int, D> &size) {
282 std::array<double, D> coords_high;
283 for (size_t i = 0; i < D; i++)
284 coords_high[i] = m_origin[i] + (offset[i] + size[i]) * m_discretization[i];
285 return coords_high;
286 }
287
288public:
328 DiscreteField(const std::array<int, D> &dimensions,
329 const std::array<int, D> &offsets,
330 const std::array<double, D> &origin,
331 const std::array<double, D> &discretization)
332 : m_array(dimensions, offsets), m_origin(origin),
333 m_discretization(discretization),
334 m_coords_low(calculate_coords_low(offsets)),
335 m_coords_high(calculate_coords_high(offsets, dimensions)) {}
336
342 /* Note: Free function alternative for construction from Decomposition
343 *
344 * This constructor was considered but deferred in favor of explicit construction
345 * to maintain clarity about what data is being used. Users can construct
346 * DiscreteField directly with the required parameters.
347 *
348 * If needed in the future, a free function could be added:
349 *
350 * namespace pfc {
351 * template<int D>
352 * DiscreteField<D> make_discrete_field(const Decomposition& decomp) {
353 * return DiscreteField<D>(
354 * get_inbox_size(decomp),
355 * get_inbox_offset(decomp),
356 * get_origin(decomp.get_world()),
357 * get_spacing(decomp.get_world())
358 * );
359 * }
360 * }
361 */
362
363 const std::array<double, D> &get_origin() const { return m_origin; }
364 const std::array<double, D> &get_discretization() const {
365 return m_discretization;
366 }
367 const std::array<double, D> &get_coords_low() const { return m_coords_low; }
368 const std::array<double, D> &get_coords_high() const { return m_coords_high; }
369
370 Array<T, D> &get_array() { return m_array; }
371 const Array<T, D> &get_array() const { return m_array; }
372 const MultiIndex<D> &get_index() { return get_array().get_index(); }
373 const MultiIndex<D> &get_index() const { return get_array().get_index(); }
374 std::vector<T> &get_data() { return get_array().get_data(); }
375
382 T &operator[](const std::array<int, D> &indices) { return get_array()[indices]; }
383
390 T &operator[](size_t idx) { return get_array()[idx]; }
391
398 std::array<double, D>
399 map_indices_to_coordinates(const std::array<int, D> &indices) const {
400 std::array<double, D> coordinates;
401 for (size_t i = 0; i < D; ++i) {
402 coordinates[i] = m_origin[i] + indices[i] * m_discretization[i];
403 }
404 return coordinates;
405 }
406
413 std::array<int, D>
414 map_coordinates_to_indices(const std::array<double, D> &coordinates) const {
415 std::array<int, D> indices;
416 for (size_t i = 0; i < D; ++i) {
417 indices[i] = static_cast<int>(
418 std::round((coordinates[i] - m_origin[i]) / m_discretization[i]));
419 }
420 return indices;
421 }
422
429 bool inbounds(const std::array<double, D> &coords) {
430 for (size_t i = 0; i < D; i++) {
431 if (m_coords_low[i] > coords[i] || coords[i] >= m_coords_high[i]) return false;
432 }
433 return true;
434 }
435
458 [[deprecated("Use pfc::interpolate(field, coords) free function instead")]] T &
459 interpolate(const std::array<double, D> &coordinates) {
460 // Keep original implementation (can't call free function yet - not declared)
461 return get_array()[(map_coordinates_to_indices(coordinates))];
462 }
463
464 using FunctionND = std::function<T(std::array<double, D>)>;
465 using Function1D = std::function<T(double)>;
466 using Function2D = std::function<T(double, double)>;
467 using Function3D = std::function<T(double, double, double)>;
468
478 void apply(FunctionND &&func) {
479 static_assert(
480 std::is_convertible_v<
481 std::invoke_result_t<FunctionND, std::array<double, D>>, T>,
482 "Func must be invocable with std::array<double, D> and return a type "
483 "convertible to T");
484 auto index_iterator = get_index().begin();
485 for (T &element : get_data()) {
486 element = std::invoke(std::forward<FunctionND>(func),
489 }
490 }
491
501 void apply(Function1D &&func) {
502 auto index_iterator = get_index().begin();
503 for (T &element : get_data()) {
505 element = std::invoke(std::forward<Function1D>(func), coords[0]);
507 }
508 }
509
519 void apply(Function2D &&func) {
520 auto index_iterator = get_index().begin();
521 for (T &element : get_data()) {
523 element = std::invoke(std::forward<Function2D>(func), x, y);
525 }
526 }
527
575 void apply(Function3D &&func) {
576 auto index_iterator = get_index().begin();
577 for (T &element : get_data()) {
578 const auto [x, y, z] = map_indices_to_coordinates(*index_iterator);
579 element = std::invoke(std::forward<Function3D>(func), x, y, z);
581 }
582 }
583
589 operator std::vector<T> &() { return get_data(); }
590 operator std::vector<T> &() const { return get_data(); }
591
597 const std::array<int, D> &get_size() const { return get_index().get_size(); }
598
599 const std::array<int, D> &get_offset() const { return get_index().get_begin(); }
600
601 void set_data(const std::vector<T> &data) { get_array().set_data(data); }
602
610 friend std::ostream &operator<<(std::ostream &os,
611 const DiscreteField<T, D> &field) {
612 const Array<T, D> &array = field.get_array();
613 const MultiIndex<D> &index = array.get_index();
614 os << "DiscreteField<" << TypeName<T>::get() << "," << D
615 << ">(begin = " << utils::array_to_string(index.get_begin())
616 << ", end = " << utils::array_to_string(index.get_end())
617 << ", size = " << utils::array_to_string(index.get_size())
618 << ", linear_size = " << index.get_linear_size()
619 << ", origin = " << utils::array_to_string(field.get_origin())
620 << ", discretization = " << utils::array_to_string(field.get_discretization())
621 << ", coords_low = " << utils::array_to_string(field.get_coords_low())
622 << ", coords_high = " << utils::array_to_string(field.get_coords_high());
623 return os;
624 }
625};
626
627// ============================================================================
628// Free Functions for DiscreteField
629// ============================================================================
630
690template <typename T, size_t D>
691inline T &interpolate(DiscreteField<T, D> &field,
692 const std::array<double, D> &coordinates) {
693 return field.get_array()[field.map_coordinates_to_indices(coordinates)];
694}
695
723template <typename T, size_t D>
724inline const T &interpolate(const DiscreteField<T, D> &field,
725 const std::array<double, D> &coordinates) {
726 // Note: const_cast needed because Array doesn't have const operator[]
727 // This is safe because we return const T&
728 return const_cast<DiscreteField<T, D> &>(field)
729 .get_array()[field.map_coordinates_to_indices(coordinates)];
730}
731
739template <typename T, size_t D, typename Function>
740void apply(DiscreteField<T, D> &field, Function &&func) {
741 field.apply(std::forward<Function>(func));
742}
743
744template <typename T, size_t D> void show(DiscreteField<T, D> &field) {
745 utils::show(field.get_array().get_data(), field.get_index().get_size(),
746 field.get_index().get_begin());
747}
748
749} // namespace pfc
750
751#endif
Multi-dimensional array container for field data.
Definition discrete_field.hpp:256
const std::array< double, D > & get_origin() const
Constructs a DiscreteField from an Decomposition object.
Definition discrete_field.hpp:363
std::array< int, D > map_coordinates_to_indices(const std::array< double, D > &coordinates) const
Maps coordinates to indices in the field.
Definition discrete_field.hpp:414
friend std::ostream & operator<<(std::ostream &os, const DiscreteField< T, D > &field)
Outputs the discrete field to the specified output stream.
Definition discrete_field.hpp:610
T & operator[](const std::array< int, D > &indices)
Returns the element at the specified index.
Definition discrete_field.hpp:382
T & interpolate(const std::array< double, D > &coordinates)
Interpolate field value at physical coordinates (nearest-neighbor)
Definition discrete_field.hpp:459
bool inbounds(const std::array< double, D > &coords)
Checks if the given coordinates are within the bounds of the field.
Definition discrete_field.hpp:429
const std::array< int, D > & get_size() const
Get the size of the field.
Definition discrete_field.hpp:597
void apply(Function1D &&func)
Applies the given 1D function to each element of the field.
Definition discrete_field.hpp:501
std::array< double, D > map_indices_to_coordinates(const std::array< int, D > &indices) const
Maps indices to coordinates in the field.
Definition discrete_field.hpp:399
void apply(FunctionND &&func)
Applies the given function to each element of the field.
Definition discrete_field.hpp:478
void apply(Function2D &&func)
Applies the given 2D function to each element of the field.
Definition discrete_field.hpp:519
T & operator[](size_t idx)
Returns the element at the specified index.
Definition discrete_field.hpp:390
Mathematical and physical constants.
Pretty-print 3D arrays to console.
Definition typename.hpp:48
Represents the global simulation domain (the "world").
Definition world.hpp:91
World class definition and unified interface.