/****************************************************************************/1// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo2// Copyright (C) 2002-2025 German Aerospace Center (DLR) and others.3// This program and the accompanying materials are made available under the4// terms of the Eclipse Public License 2.0 which is available at5// https://www.eclipse.org/legal/epl-2.0/6// This Source Code may also be made available under the following Secondary7// Licenses when the conditions for such availability set forth in the Eclipse8// Public License 2.0 are satisfied: GNU General Public License, version 29// or later which is available at10// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html11// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later12/****************************************************************************/13/// @file CharacteristicMap.h14/// @author Kevin Badalian ([email protected])15/// @date 2021-0216///17// Characteristic map for vehicle type parameters as needed by the MMPEVEM model18// Teaching and Research Area Mechatronics in Mobile Propulsion (MMP), RWTH Aachen19/****************************************************************************/202122/******************************************************************************23* ============================= Example Usage ============================== *24******************************************************************************25* *26* Assume a function f which maps from R^2 to R^1 according to... *27* *28* | 0 | 1 | 2 | 3 | 4 -> x_1 *29* ----|------------------------ *30* -1 | 1 | 3 | 1 | -2 | *31* 1 | 1 | -2 | -3 | 7 | 5 *32* 3 | -2 | -1 | 0 | 1 | 3 *33* 5 | | 0 | 8 | 4 | 4 *34* *35* | *36* v *37* x_2 *38* *39* ... so that, for example, f(3, 1) = 7. Note that f is not defined at *40* (4, -1) and (0, 5). There are two ways to create a CharacteristicMap *41* object for this function. *42* 1) Using the standard constructor: *43* // Axes *44* std::vector<std::vector<double>> axes; *45* axes.push_back(std::vector<double>{0, 1, 2, 3, 4}); // Axis 1 *46* axes.push_back(std::vector<double>{-1, 1, 3, 5}); // Axis 2 *47* // Flattened row-major map entries *48* std::vector<double> flattenedMap{1, 3, 1, -2, std::nan(""), *49* 1, -2, -3, 7, 5, -2, -1, 0, 1, 3, std::nan(""), 0, 8, 4, 4}; *50* *51* CharacteristicMap map1(2, // Mapping from R^2... *52* 1, // ... to R^1 *53* axes, *54* flattenedMap); *55* *56* 2) Using a string-encoding of the map: *57* CharacteristicMap map2("2,1|0,1,2,3,4;-1,1,3,5|1,3,1,-2,nan," *58* "1,-2,-3,7,5,-2,-1,0,1,3,nan,0,8,4,4"); *59* *60* See below for an in-depth explanation of the format. *61* *62* *63* To evaluate the map at, for instance, p = (2.2, 2), one must call: *64* std::vector<double> res = map1.eval(std::vector<double>{2.2, 2}, // p *65* 1e-3); // eps *66* if(std::isnan(res[0])) *67* std::cout << "[WARNING] Couldn't evaluate the map." << std::endl; *68* else *69* std::cout << "res = " << res[0] << std::endl; *70* *71* The epsilon value is used for numerical reasons and decides how much a *72* point must deviate from its nearest neighbor before linear interpolation *73* is applied or when a point is considered outside of the map. The default *74* is 1e-6. *75* *76* *77* The string-encoding of a CharacteristicMap that maps from R^m to R^n is *78* formally defined as *79* "m,n|A_1[1],A_1[2],...,A_1[l1];A_2[1],A_2[2],...,A_2[l2];...;\ *80* A_m[1],A_m[2],...,A_m[lm]|\ *81* M_flat[1],M_flat[2],...,M_flat[l1*l2*...*lm]" *82* where A_i[j] denotes the j-th value of the i-th axis (which has li values *83* total) and M_flat[i] stands for the i-th entry in the row-major flattened *84* map. To be more specific, given a map M, its flattened version is *85* computed as follows (using pseudo code): *86* M_flat = "" *87* for i_m in {1,2,...,lm}: // Last axis *88* ... *89* for i_2 in {1,2,...,l2}: // Second axis *90* for i_1 in {1,2,...,l1}: // First axis (i.e. row axis) *91* for d in {1,2,...,n}: // Image dimensions *92* M_flat += M[i_1,i_2,...,i_m][d] + "," *93* removeTrailingComma(M_flat) *94* *95******************************************************************************/96#pragma once9798#include <vector>99#include <string>100101102/**103* \class CharacteristicMap104* \brief The purpose of this class is to store a characteristic map (German:105* Kennfeld) of arbitrary dimensions and to provide functions in order to106* evaluate/interpolate points in it.107*/108class CharacteristicMap {109private:110/// Dimension of the map's domain111int domainDim;112113/// Image dimension of the map114int imageDim;115116/// Vector containing the values along each domain axis in ascending order117std::vector<std::vector<double>> axes;118119/// Flattened map entries120std::vector<double> flattenedMap;121122/// Stride for each domain dimension in the flattened map123std::vector<int> strides;124125/**126* \brief Determine the stride for each map dimension in the flattened map.127*/128void determineStrides();129130/**131* \brief Compute the index of a map entry in the flattened map.132*133* \param[in] ref_idxs Non-flattened map indices134* \returns Flattened map index135* \throws std::runtime_error136*/137int calcFlatIdx(const std::vector<int>& ref_idxs) const;138139/**140* \brief Determine the indices of the nearest neighbor of a point in the map.141*142* A point has no such neighbor if it lies outside of the range of an axis143* with respect to some epsilon.144* \param[in] ref_p A point145* \param[out] ref_idxs A vector into which the indices shall be written146* \param[in] eps An epsilon value147* \returns 0 if a nearest neighbor could be found, else -1148* \throws std::runtime_error149*/150int findNearestNeighborIdxs(const std::vector<double>& ref_p,151std::vector<int>& ref_idxs, double eps = 1e-6) const;152153/**154* \brief Access a map entry using its indices.155*156* \param[in] ref_idxs A vector containing indices157* \returns A vector containing the image values of the map at the specified158* location159* \throws std::runtime_error160*/161std::vector<double> at(const std::vector<int>& ref_idxs) const;162163164165public:166/**167* \brief Constructor168*169* \param[in] domainDim The map's domain dimension170* \param[in] imageDim The map's image dimension171* \param[in] ref_axes A vector of vectors containing the entries of their172* respective axes in ascending order173* \param[in] ref_flattenedMap The row-major flattened entries of the map174* (i.e. the map is flattened along its first axis)175* \throws std::runtime_error176*/177CharacteristicMap(int domainDim, int imageDim,178const std::vector<std::vector<double>>& ref_axes,179const std::vector<double>& ref_flattenedMap);180181/**182* \brief Constructor183*184* \param[in] ref_mapString A string representation of a characteristic map185* (cf. example at the top of the file)186* throws std::runtime_error187*/188CharacteristicMap(const std::string& ref_mapString);189190/**191* \brief Encode the map as a string.192*193* \returns A string representation of the characteristic map (cf. example at194* the top of the file)195*/196std::string toString() const;197198/**199* \brief Get the dimension of the map's domain.200*201* \returns The domain's dimension202*/203int getDomainDim() const;204205/**206* \brief Get the image dimension of the map.207*208* \returns The image dimension of the characteristic map209*/210int getImageDim() const;211212/**213* \brief Evaluate a point in the map using linear interpolation.214*215* Please note that the result may contain NaNs. That happens when...216* a) ... the point in question has no nearest neighbor (that is, it lies217* outside of the domain). In that case, all entries of the vector are set218* to NaN.219* b) ... an image value of the nearest neighbor is NaN. Then, the result is220* also NaN in that image dimension.221* c) ... evaluation would require interpolating with a support point which is222* NaN in an image dimension. The corresponding result entry is set to NaN223* in that case.224* \param[in] ref_p A point225* \param[in] eps An epsilon value226* \returns The (interpolated) image values of the map at the specified point227* \throws std::runtime_error228*/229std::vector<double> eval(const std::vector<double>& ref_p,230double eps = 1e-6) const;231};232233234