From f817d46673cc9634744689c347fa2865fcc7dc55 Mon Sep 17 00:00:00 2001 From: damien-masse Date: Tue, 29 Apr 2025 08:32:51 +0200 Subject: [PATCH 01/26] Polytope, first version --- CMakeLists.txt | 14 +- src/extensions/polytope/CMakeLists.txt | 63 ++ src/extensions/polytope/FindClp.cmake | 98 ++ src/extensions/polytope/clp/codac2_clp.cpp | 1064 ++++++++++++++++++++ src/extensions/polytope/clp/codac2_clp.h | 276 +++++ tests/CMakeLists.txt | 11 +- 6 files changed, 1524 insertions(+), 2 deletions(-) create mode 100644 src/extensions/polytope/CMakeLists.txt create mode 100644 src/extensions/polytope/FindClp.cmake create mode 100644 src/extensions/polytope/clp/codac2_clp.cpp create mode 100644 src/extensions/polytope/clp/codac2_clp.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e4dced308..9159ac110 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,6 +148,18 @@ find_package(CAPD REQUIRED) endif() +################################################################################ +# Looking for CLP (if needed) +################################################################################ + + option(WITH_POLYTOPE "Build with polytopes using CLP" OFF) + + if(WITH_POLYTOPE) + include(./src/extensions/polytope/FindClp.cmake) + message(STATUS "Found CLP version ${CLP_VERSION}") + message(STATUS "Found CLP inclusion ${CLP_INCLUDE_DIRS}") + endif() + ################################################################################ # Compile sources @@ -204,4 +216,4 @@ set(CPACK_DEBIAN_PACKAGE_HOMEPAGE ${CODAC_URL}) # todo: finish deb package - include(CPack) \ No newline at end of file + include(CPack) diff --git a/src/extensions/polytope/CMakeLists.txt b/src/extensions/polytope/CMakeLists.txt new file mode 100644 index 000000000..21ecf11bd --- /dev/null +++ b/src/extensions/polytope/CMakeLists.txt @@ -0,0 +1,63 @@ +# ================================================================== +# Codac - cmake configuration file +# ================================================================== + +list(APPEND CODAC_POLYTOPE_SRC + + ${CMAKE_CURRENT_SOURCE_DIR}/clp/codac2_clp.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/clp/codac2_clp.h +) + +################################################################################ +# Create the target for libcodac-polytope +################################################################################ + + #if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + #endif() + + add_library(${PROJECT_NAME}-polytope ${CODAC_POLYTOPE_SRC}) + target_include_directories(${PROJECT_NAME}-polytope PUBLIC + ${CLP_INCLUDE_DIRS} + ) + target_link_libraries(${PROJECT_NAME}-polytope PUBLIC ${PROJECT_NAME}-core Ibex::ibex Eigen3::Eigen ${CLP_LIBRARIES}) + + + ################################################################################ + # For the generation of the PKG file + ################################################################################ + + set(CODAC_PKG_CONFIG_CFLAGS "${CODAC_PKG_CONFIG_CFLAGS} -I\${includedir}/${PROJECT_NAME}-polytope" PARENT_SCOPE) + set(CODAC_PKG_CONFIG_LIBS "${CODAC_PKG_CONFIG_LIBS} -l${PROJECT_NAME}-polytope" PARENT_SCOPE) + + + ################################################################################ + # Installation of libcodac-polytope files + ################################################################################ + + # Getting header files from sources + + foreach(srcfile ${CODAC_POLYTOPE_SRC}) + if(srcfile MATCHES "\\.h$" OR srcfile MATCHES "\\.hpp$") + list(APPEND CODAC_POLYTOPE_HDR ${srcfile}) + file(COPY ${srcfile} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../../include) + endif() + endforeach() + + # Generating the file codac-polytope.h + + set(CODAC_POLYTOPE_MAIN_HEADER ${CMAKE_CURRENT_BINARY_DIR}/codac-polytope.h) + file(WRITE ${CODAC_POLYTOPE_MAIN_HEADER} "/* This file is generated by CMake */\n\n") + file(APPEND ${CODAC_POLYTOPE_MAIN_HEADER} "#pragma once\n\n") + foreach(header_path ${CODAC_POLYTOPE_HDR}) + get_filename_component(header_name ${header_path} NAME) + file(APPEND ${CODAC_POLYTOPE_MAIN_HEADER} "#include <${header_name}>\n") + endforeach() + file(COPY ${CODAC_POLYTOPE_MAIN_HEADER} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../../include) + + # Install files in system directories + + install(TARGETS ${PROJECT_NAME}-polytope DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(FILES ${CODAC_POLYTOPE_HDR} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-polytope) + install(FILES ${CODAC_POLYTOPE_MAIN_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-polytope) diff --git a/src/extensions/polytope/FindClp.cmake b/src/extensions/polytope/FindClp.cmake new file mode 100644 index 000000000..2dfaf90da --- /dev/null +++ b/src/extensions/polytope/FindClp.cmake @@ -0,0 +1,98 @@ +# FindClp +# ----------- +# +# The module defines the following variables: +# +# COINUTILS_FOUND +# COINUTILS_INCLUDE_DIRS +# COINUTILS_LIBRARIES +# COINUTILS_VERSION +# CLP_FOUND +# CLP_INCLUDE_DIRS +# CLP_LIBRARIES +# CLP_VERSION +# +# and the following imported target (if it does not already exist): +# +# Clp::Clp - The Clp library +# +# +# Requires CMake >= 3.0 + +include(CheckCXXCompilerFlag) +find_package (PkgConfig) + +set (COINUTILS_DIR "${COIN_DIR}" CACHE PATH "Directory to search for CoinUtils") +set (CLP_DIR "${CLP_DIR}" CACHE PATH "Directory to search for Clp") + +if (COINUTILS_DIR) + set (ENV{PKG_CONFIG_PATH} "${COINUTILS_DIR}/lib/pkgconfig:${PKG_CONFIG_PATH}") +endif () +if (CLP_DIR) + set (ENV{PKG_CONFIG_PATH} "${CLP_DIR}/lib/pkgconfig:${PKG_CONFIG_PATH}") +endif () + +pkg_check_modules (CLP_PKG QUIET clp) + +# Look for the library +foreach (_lib ${CLP_PKG_LIBRARIES}) + string (TOUPPER "${_lib}" _upperlib) + find_library (${_upperlib}_LIBRARY NAMES ${_lib} + HINTS ${CLP_PKG_LIBRARY_DIRS} + PATH_SUFFIXES lib) + list (APPEND _req_vars ${_upperlib}_LIBRARY) + list (APPEND CLP_LIBRARIES "${${_upperlib}_LIBRARY}") + mark_as_advanced (${_upperlib}_LIBRARY) + unset (_upperlib) +endforeach () +unset (_lib) + +# Look for the include directory +find_path (COINUTILS_INC_DIR NAMES CoinUtilsConfig.h + HINTS ${CLP_PKG_INCLUDE_DIRS} + PATH_SUFFIXES include include/coin) +find_path (CLP_INC_DIR NAMES ClpConfig.h + HINTS ${CLP_PKG_INCLUDE_DIRS} + PATH_SUFFIXES include include/coin) + +# Look for the version +set (_version_match "VERSION \"[0-9]+[.][0-9]+[.][0-9]+") +if (COINUTILS_INC_DIR) + file (READ "${COINUTILS_INC_DIR}/CoinUtilsConfig.h" _content) + string (REGEX MATCH "COINUTILS_${_version_match}" _match "${_content}") + string (SUBSTRING "${_match}" 19 -1 COINUTILS_VERSION) +endif () + +if (CLP_INC_DIR) + file (READ "${CLP_INC_DIR}/ClpConfig.h" _content) + string (REGEX MATCH "CLP_${_version_match}" _match "${_content}") + string (SUBSTRING "${_match}" 13 -1 CLP_VERSION) +endif () +unset (_content) +unset (_match) +unset (_version_match) + +include (FindPackageHandleStandardArgs) +list (APPEND _req_vars CLP_INC_DIR) +list (APPEND _req_vars COINUTILS_INC_DIR) +find_package_handle_standard_args (Clp REQUIRED_VARS ${_req_vars} + VERSION_VAR CLP_VERSION) +unset (_req_vars) + +if (CLP_FOUND) + set (CLP_INCLUDE_DIRS ${CLP_PKG_INCLUDE_DIRS}) + mark_as_advanced (COINUTILS_DIR) + mark_as_advanced (CLP_DIR) + if (NOT TARGET Clp::Clp) + # For now we make the target global, because this file is included from a + # CMakeLists.txt file in a subdirectory. With CMake >= 3.11, we could make + # it global afterwards with + # set_target_properties(Clp::Clp PROPERTIES IMPORTED_GLOBAL TRUE) + add_library (Clp::Clp INTERFACE IMPORTED GLOBAL) + set_target_properties (Clp::Clp PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${CLP_INCLUDE_DIRS}" + INTERFACE_LINK_LIBRARIES "${CLP_LIBRARIES}") + endif() +endif() + +mark_as_advanced (CLP_INC_DIR COINUTILS_INC_DIR) diff --git a/src/extensions/polytope/clp/codac2_clp.cpp b/src/extensions/polytope/clp/codac2_clp.cpp new file mode 100644 index 000000000..d28190dd3 --- /dev/null +++ b/src/extensions/polytope/clp/codac2_clp.cpp @@ -0,0 +1,1064 @@ +/** + * Linear programming problem, use coin-or clp + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2025 + * \license This program is distributed under the terms of + * the GNU Lesser General Public License (LGPL). + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wregister" +#include +#include +#pragma GCC diagnostic pop + + +#include "codac2_BoolInterval.h" +#include "codac2_clp.h" + +#undef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG + +namespace codac2 { + +/********** Problem building funtions ***********/ + +LPclp::LPclp(const Matrix &mat, const Row &objvect, + const Vector &rhsvect) : + nbRows(mat.rows()), nbCols(mat.cols()), + Amat(mat), + objvect(objvect), + rhsvect(rhsvect), cststat(nbRows,0), + rowBasis(nbRows,false), built(false), model(nullptr), + primalSol(nbCols), dualSol(nbRows), primalRay(nbCols), dualRay(nbRows), + bbox(nbCols) +{ +} + +LPclp::LPclp(const Matrix &mat, + const Vector &rhsvect) : + nbRows(mat.rows()), nbCols(mat.cols()), + Amat(mat), + objvect(Row::Zero(nbCols)), + rhsvect(rhsvect), cststat(nbRows,0), + rowBasis(nbRows,false), built(false), model(nullptr), + primalSol(nbCols), dualSol(nbRows), primalRay(nbCols), dualRay(nbRows), + bbox(nbCols) +{ + +} + +LPclp::LPclp(Index dim, Index nbcsts) : + nbRows(nbcsts), nbCols(dim), + Amat(Matrix::Zero(nbRows,nbCols)), + objvect(Row::Zero(nbCols)), + rhsvect(Vector::Zero(nbRows)), + cststat(nbRows,1<objvect=objvect; + if (built && !built_emptytest) { /* change the values of the objective */ + for (Index i =0; isetObjectiveCoefficient(i, objvect[i]); + this->status=1<setRowUpper(cst,rhsvect[cst]); + this->status=1<setRowUpper(cst,DBL_MAX); + this->status=1<setLogLevel(0); + model->setOptimizationDirection(-1); /* Maximize */ + + model->setMaximumIterations(this->maxIteration); + model->setMaximumSeconds(this->timeout); + model->setPrimalTolerance(this->tolerance); + model->setDualTolerance(this->tolerance); + + /* set dimension */ + Index nbCols2=nbCols+(emptytest ? 1 : 0); + model->resize(0,nbCols2); + for (Index i=0;isetColumnBounds(i,-COIN_DBL_MAX, COIN_DBL_MAX); + } + + /* adding the rows using BuildObject ? */ + CoinBuild buildObject; + int* row2Index = new int[nbCols2]; + for(Index i=0;iaddRows(buildObject); + delete row2Vals; + delete row2Index; + + /* set the objective */ + for (Index i =0; isetObjectiveCoefficient(i, (emptytest ? 0.0 : objvect[i])); + } + if (emptytest) + model->setObjectiveCoefficient(nbCols, 1.0); + built=true; + built_emptytest=emptytest; +} + +/* adding/removing emptiness testing on a built model */ +void LPclp::setModel(bool emptytest) { + if (!built) { + buildModel(emptytest); + return; + } + if (emptytest==built_emptytest) return; + if (emptytest) { + int* col2Index=new int[nbRows]; + for(Index i=0;iaddColumn + (nbRows,col2Index,col2Vals,-COIN_DBL_MAX,COIN_DBL_MAX,1.0); + for (Index i =0; isetObjectiveCoefficient(i, 0.0); + } else { + int nb=(int) nbCols; + model->deleteColumns(1,&nb); + for (Index i =0; isetObjectiveCoefficient(i, objvect[i]); + } + built_emptytest=emptytest; +} + + +/* reinitialise the solution */ +void LPclp::reset_solution() { + status=0; +// rowBasis.reset(); + Valobj=Interval(); + primalSol.setZero(); + dualSol.setZero(); + primalRay.setZero(); + dualRay.setZero(); +} + + +/********** Solving functions *******************/ + +/***** launch one solving, returns the status *******/ +LPclp::lp_result_stat LPclp::solve(bool checkempty, int option) { + this->setModel(checkempty); + reset_solution(); + model->primal(0,option | 1); + int stat = model->status(); + if (stat==-1 || stat>2) { + status=1<status(),checkempty); +} + + +int LPclp::minimize_polytope(const Interval &tolerance, bool checkempty) { + if (checkempty) { + lp_result_stat stat=this->solve(true); + if (stat[EMPTY]) return -1; + } + bool solvedOnce=checkempty; + int nb=0; + for (Index i=nbRows-1;i>=0;i--) { + if (cststat[i][REMOVED] || + cststat[i][INACTIVE] || + cststat[i][REDUNDANT]) + continue; + this->setActive(i,false); + this->setObjective(this->getCst(i)); + lp_result_stat stat=this->solve(false,(solvedOnce ? 4 : 0)); + if (stat[EMPTY]) return -1; + if (stat[BOUNDED]) { + if (this->Valobj.ub()<=rhsvect[i]+tolerance.ub() + && this->Valobj.lb()<=rhsvect[i]+tolerance.lb()) { + cststat[i][REDUNDANT]=true; + continue; + } + } + nb++; + this->setActive(i,true); + solvedOnce=true; + } + return nb; +} + + +/*************************************************************/ +/**** Validation of the results of coin-or ***************/ +/*************************************************************/ + + +/************ correction of inverse matrix *******************/ +/**** basis is a nb*N matrix and basisInverse is ******/ +/**** an approximation of a nb*nb submatrix of basis ******/ +/**** the columns i part of the submatrix are defined ******/ +/**** by (*revbasics)[i]!=1 ( if nb!=N ) ******/ +/**** modify basisInverse to contain the "true" inverse ******/ +/**** of the submatrix of basis ******/ +/**** if A is the submatrix and B is the approximate ******/ +/**** inverse, we compute Id-BA = Err ******/ +/**** then basisInverse = (Id+Eps)*B with ******/ +/**** Eps = Err*(Id+Err+Err^2+...) ******/ + +/**** code for approximation of infinite sum enclosure ****/ +/**** should be in codac2 ? ****/ +IntervalMatrix LPclp::infinite_sum_enclosure (const IntervalMatrix& M) { + size_t n = M.cols(); + Matrix TmpM = M.mig(); +#if 0 + // computing the mag of the eigen matrix + auto sit = M.reshaped().cbegin(); + auto dit = TmpM.reshaped().begin(); + for (;sit!=M.reshaped().cend(); ++sit,++dit) { + (*dit) = (*sit).mag(); + } +#endif + + // Floyd algorithm below + for (size_t k=0;k0.0) { + for (size_t r=0;r0.0 && TmpM(r,k)0.0 && TmpM(k,c) wholeBasis; + wholeBasis.reserve(nbCols2); + Index nbRowsInBasis=0; + /* row basics */ + for (Index i=0;igetRowStatus(i)!=ClpSimplex::Status::basic) { + wholeBasis.push_back(i); + nbRowsInBasis++; + rowBasis[i]=true; + } else { + rowBasis[i]=false; + } + dualSol[i]=model->dualRowSolution()[i]; + } + /* col basics */ + double slack=0.0; /* only for emptiness checking */ + for (Index i=0;igetColumnStatus(i)!=ClpSimplex::Status::basic) + wholeBasis.push_back(i+nbRows); +// dualVals[i+nbRows]=model->dualColumnSolution()[i]; /* useless for us*/ + if (iprimalColumnSolution()[i]; + else slack = model->primalColumnSolution()[i]; + } + assert((Index) wholeBasis.size()==nbCols2); + + std::vector revbasics(nbRows,-1); + int *basics=new int[nbRows]; + model->getBasics(basics); + for (Index i=0;iBInvRow qui "inverse" la c-ème colonne de Amat */ + } + delete basics; + + IntervalMatrix basis= + IntervalMatrix::Zero(nbCols2,nbCols2); + IntervalMatrix basisInverse= + IntervalMatrix::Zero(nbCols2,nbCols2); + + /* fill the basis with only the rows first */ + for (Index c=0;cgetBInvRow(revbasics[c],invCol); + for (Index k=0;k modify it. + */ + if (checkempty) { + assert_release(stat!=1); + if (stat==2) { + double *ray = model->unboundedRay(); + assert_release(ray!=nullptr); + assert_release(ray[nbCols]>0.0); + for (Index i=0;i=0 : normally, it's ok; slack<0.0 => empty , + dual value should prove the empty case */ + if (slack<0.0) { + dualRay=dualSol; + stat=1; + } + } + } + + /* basis rows : nb constraints, ordonnées par wholeBasis + des variables, = wholeBasis[i] - nbRows + cols : variables */ + /* basisinverse rows : variables + cols : nb constraints, ordonnées par wholeBasis + variables, wholeBasis[i]-nbRows. + obj*basisInverse : combinaisons de contraintes pour + obtenir obj + basisInverse*rhs : point qui satisfait rhs */ + + /* stat = 0 : CLP said optimal + stat = 1 : CLP said empty + stat = 2 : CLP said unbounded */ + if (stat==1) { /* test only dual infeasibility */ + /* if checkempty=true, we already have the ray, + otherwise we build it */ + double *ray=nullptr; + if (!checkempty) { + double *ray = model->infeasibilityRay(false); + assert_release(ray!=nullptr); /* false => bug of CLP */ + /* true for infeasibilityRay means that we have also + the negation of the "error" term after the rows, + but we will compute it ourselves. + unless the variables are bounded, this is not needed */ + for (Index i=0;i val * Amat : variables (~0) + val*Amat*basisInverse => basisInverse.cols */ + } +#ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG + std::cout << "dualRay: " << dualRay << "\n"; +#endif + IntervalRow errorNeum=IntervalRow::Zero(nbCols2); + errorNeum.head(nbCols)=dualRay*Amat; + if (checkempty) errorNeum[nbCols]=dualRay.sum()-1.0; + IntervalRow error = errorNeum*basisInverse; + BoolInterval ok=BoolInterval::TRUE; + for (Index i=0;i0.0) { + ok=BoolInterval::FALSE; + break; + } + } + if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) + for (Index i=0;i0.0)) { ok=BoolInterval::FALSE; break; } + } + } + if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) { + Interval product = dualRay.dot(rhsvect); + if (checkempty) Valobj=Interval(-oo,product.ub()); + if (product.lb()>=0.0) ok=BoolInterval::FALSE; + else if (product.ub()>=0) ok=BoolInterval::UNKNOWN; + } +#ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG + std::cout << "ok after check1: " << ok << "\n"; +#endif + if (ok==BoolInterval::TRUE) { + status[EMPTY]=true; + if (!checkempty) Valobj.set_empty(); + delete ray; + return status; + } + /* attempt with Neumaier */ + if (!bbox.is_unbounded()) { + /* restart with the ray */ + for (Index i=0;i okrow(nbRows,Interval(0.0,oo)); + int nbok=nbRows; + for (Index j=0;j0.0) { + if (dualRay[r].lb()<=0.0) { + okrow[j].set_empty(); nbok--; continue; + } else { + Interval u(dualRay[r].lb()); + u /= comp(j,i).ub(); + okrow[j] &= Interval(0.0, + u.lb()); + if (okrow[j].is_empty()) { nbok--; continue; } + } + } else if (comp(j,i).ub()==0.0) { + if (dualRay[r].lb()<0.0) { + okrow[j].set_empty(); nbok--; + } + continue; + } else { + if (dualRay[r].lb()>=0.0) continue; + Interval u(dualRay[r].lb()); + u /= comp(j,i).ub(); + okrow[j] &= Interval(u.ub(),oo); + if (okrow[j].is_empty()) { nbok--; continue; } + } + } + if (!checkempty) dualRay[r]=ray[r]; + else dualRay[r]=dualSol[r]; + } + /* recompute errorNeum */ + if (nbok>0) { + for (Index i=0;iokrow[j].ub()) dualRay[j]=okrow[j].mid(); + break; + } + errorNeum.head(nbCols)=dualRay*Amat; + if (checkempty) errorNeum[nbCols]=dualRay.sum()-1.0; + error = errorNeum*basisInverse; + ok=BoolInterval::TRUE; + for (Index i=0;i0.0) { + ok=BoolInterval::FALSE; + break; + } + } + if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) + for (Index i=0;i0.0)) + { ok=BoolInterval::FALSE; break; } + } + } + if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) { + Interval product = dualRay.dot(rhsvect); + if (checkempty) Valobj=Interval(-oo,product.ub()); + if (product.lb()>=0.0) ok=BoolInterval::FALSE; + else if (product.ub()>=0) ok=BoolInterval::UNKNOWN; + } +#ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG + std::cout << "ok after check2: " << ok << "\n"; + std::cout << "dualRay: " << dualRay << "\n"; +#endif + if (ok==BoolInterval::TRUE) { + status[EMPTY]=true; + if (!checkempty) Valobj.set_empty(); + delete ray; + return status; + } + } + } + delete ray; + if (ok==BoolInterval::UNKNOWN) { + status[EMPTY_APPROX]=true; + } else { + status[ERROR_PRIMAL_CHECK]=true; + } + /* we should try to bound the result, even if empty check failed */ + /* for that we use basisInverse directly */ + if (!checkempty) + { + IntervalRow dSol= objvect*basisInverse; + Interval product = dSol.dot(rhsvect); + dualSol.setZero(); + bool ok=true; + for (Index i=0;i0.0) { + if (bbox.is_unbounded()) { ok=false; break; } + status[BOUNDED_BBOX]=true; + product -= dSol[i].lb() * + bbox[wholeBasis[i]-nbRows]; + } + } + } + if (ok) { + status[BOUNDED]=true; + Valobj = min(Valobj,product); + } + } + return status; + } + /* following : stat=0 or stat=2, hence we look for nonemptiness + primalSol is supposed to give a good place */ + { + /* 1) we modify primalSol to get a box around the "optimal" place */ + /* note: should be done only for checkempty=false + (we do not _really_ want a box around the "center" of the polytope */ + IntervalVector errorPart = Amat*primalSol - rhsvect; + if (!checkempty) { + IntervalVector errorInBasis= IntervalVector::Zero(nbCols2); + /* error must be always negative, the real error is the positive part */ + for (Index i=0;i0.0) break; + } + if (mxVal.ub()<=0.0) { + status[NOTEMPTY]=true; + Valobj = max(Valobj,objvect.dot(primalSol)); + } else if (mxVal.lb()<=0.0) { + if (!checkempty) { + /* change of approach, we slightly move + primalSol and hope to prove the non-emptiness + we use another vector to save primalSol and get + it back if needed */ + IntervalVector primalSol2=primalSol.mid(); + for (Index i=0;i=0.0) + primalSol2 -= (1.1*err.ub()/Amat.row(i).squaredNorm())*Amat.row(i); + } + primalSol2=primalSol2.mid(); + errorPart=Amat*primalSol2 - rhsvect; +#ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG + std::cout << "new primalSol : " << primalSol2 << "\n"; + std::cout << "errorPart 2 : " << errorPart << "\n"; +#endif + bool ok=true; + for (Index i=0;i0.0) { + ok=false; /* failed */ + break; + } + } + if (ok) { + status[NOTEMPTY]=true; + primalSol = primalSol2; + Valobj = max(Valobj,objvect*primalSol); + + } else { + status[NOTEMPTY_APPROX]=true; + } + } else { + status[NOTEMPTY_APPROX]=true; + } + } else { + status[ERROR_PRIMAL_CHECK]=true; + } + } + if (stat==2) { + /* check unboundedness. This is the last step + (alternative is stat==0) */ + if (!checkempty) { /* needs to build the ray */ + double *ray = model->unboundedRay(); + assert_release(ray!=nullptr); + for (Index i=0;i0.0) break; + } + if (mxVal.lb()<=0.0) { + if (mxVal.ub()>0.0 && !checkempty) { + /* change of approach, we slightly move + the value and hope to prove the validity + we use another vector to save primalSol and get + it back if needed */ + IntervalVector primalRay2=primalRay.mid(); + for (Index i=0;i=0.0) { + primalRay2 -= (1.1*err.ub()/Amat.row(i).squaredNorm())*Amat.row(i); + } + } + primalRay2=primalRay2.mid(); + errorPart=Amat*primalRay2; +#ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG + std::cout << "new primalRay : " << primalRay2 << "\n"; + std::cout << "errorPart 2 : " << errorPart << "\n"; +#endif + bool ok=true; + for (Index i=0;i0.0) { + ok=false; /* failed */ + break; + } + } + if (ok) { + Interval evolobj = objvect.dot(primalRay2); + if (evolobj.lb()>0.0) { + status[UNBOUNDED]=true; + primalRay = primalRay2; + return status; + } + } + } + Interval evolobj = objvect.dot(primalRay); + if (mxVal.ub()<=0.0 && evolobj.lb()>0.0) + status[UNBOUNDED]=true; + else if (evolobj.ub()>0.0) + status[UNBOUNDED_APPROX]=true; + else status[ERROR_DUAL_CHECK]=true; + } else { + status[ERROR_DUAL_CHECK]=true; + } + return status; + } + { + /* finally, check boundedness */ + /* compute the error term , and reverse it with basis. */ + /* val : csts => val * Amat : variables => + val*Amat*basisInverse => basisInverse.cols */ + /* if checkempty, the goal may be to prove the global + boundedness of the polytope */ + IntervalRow errorNeum=IntervalRow::Zero(nbCols2); + if (checkempty) { + errorNeum.head(nbCols)=dualSol*Amat; + errorNeum[nbCols]=dualSol.sum()-1.0; + } else { + errorNeum = dualSol*Amat-objvect; + } + IntervalRow error = errorNeum*basisInverse; + BoolInterval ok=BoolInterval::TRUE; + for (Index i=0;i0.0) { + ok=BoolInterval::FALSE; + break; + } + } + if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) + for (Index i=0;i0.0)) { ok=BoolInterval::FALSE; break; } + } + } + if (ok==BoolInterval::TRUE) { + status[BOUNDED]=true; + Valobj = min(Valobj,dualSol.dot(rhsvect)); + return status; + } + /* Neumaier's approach , only with !checkempty */ + if (!bbox.is_unbounded()) { + /* rebuild the solution */ + for (Index i=0;idualRowSolution()[i]; + } + Interval productNeum = dualSol.dot(rhsvect); + productNeum -= errorNeum.head(nbCols).dot(bbox); + status[BOUNDED_BBOX]=true; + status[BOUNDED]=true; + Valobj = min(Valobj,productNeum); + return status; + } else if (!checkempty) { + /* other approach : we try to compensate with other values */ + /* for each epsilon value in dualSol, try to find a complement + in comp */ + IntervalMatrix comp = ((IntervalMatrix)(Amat))*basisInverse; + std::vector okrow(nbRows,Interval(0.0,oo)); + int nbok=nbRows; + for (Index j=0;j0.0) { + if (dualSol[r].lb()<=0.0) { + okrow[j].set_empty(); nbok--; continue; + } else { + Interval u(dualSol[r].lb()); + u /= comp(j,i).ub(); + okrow[j] &= Interval(0.0, + u.lb()); + if (okrow[j].is_empty()) { nbok--; continue; } + } + } else if (comp(j,i).ub()==0.0) { + if (dualSol[r].lb()<0.0) { + okrow[j].set_empty(); nbok--; + } + continue; + } else { + if (dualSol[r].lb()>=0.0) continue; + Interval u(dualSol[r].lb()); + u /= comp(j,i).ub(); + okrow[j] &= Interval(u.ub(),oo); + if (okrow[j].is_empty()) { nbok--; continue; } + } + } + dualSol[r]=model->dualRowSolution()[r]; + } + /* recompute errorNeum */ + if (nbok>0) { + for (Index i=0;idualRowSolution()[i]; + } + for (Index j=0;jokrow[j].ub()) dualSol[j]=okrow[j].mid(); + break; + } + errorNeum = dualSol*Amat-objvect; + error = errorNeum*basisInverse; + ok=BoolInterval::TRUE; + for (Index i=0;i0.0) { + ok=BoolInterval::FALSE; + break; + } + } + if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) + for (Index i=0;i0.0)) + { ok=BoolInterval::FALSE; break; } + } + } + if (ok==BoolInterval::TRUE) { + status[BOUNDED]=true; + Valobj = min(Valobj,dualSol.dot(rhsvect)); + return status; + } + } + } + if (ok==BoolInterval::UNKNOWN) { + status[BOUNDED_APPROX]=true; + } else { + status[ERROR_DUAL_CHECK]=true; + } + } + return status; +} + +/** simplified writing of lp_result_stat for debugging **/ +std::ostream& print_lp_stat(std::ostream& os, const LPclp::lp_result_stat x) { + if (x[LPclp::CHANGED]) { + os << "Not_computed"; return os; + } + if (x[LPclp::ERROR_LPCOIN]) { + os << "CLP_failed"; return os; + } + /* primal */ + if (x[LPclp::EMPTY]) { + os << "Empty"; + if (x[LPclp::EMPTY_BBOX]) os << "(bbox)"; + return os; + } else if (x[LPclp::EMPTY_APPROX]) { + os << "Empty(app)-"; + } else if (x[LPclp::NOTEMPTY]) { + os << "NonEmpty-"; + } else if (x[LPclp::NOTEMPTY_APPROX]) { + os << "NonEmpty(app)-"; + } else if (x[LPclp::ERROR_PRIMAL_CHECK]) { + os << "Error(prim)"; + } else os << "?(prim)"; + if (x[LPclp::UNBOUNDED]) { + os << "Unbounded"; + } else if (x[LPclp::UNBOUNDED_APPROX]) { + os << "Unbounded(app)"; + } else if (x[LPclp::BOUNDED]) { + os << "Bounded"; + if (x[LPclp::BOUNDED_BBOX]) os << "(bbox)"; + } else if (x[LPclp::BOUNDED_APPROX]) { + os << "Bounded(app)"; + } else if (x[LPclp::ERROR_DUAL_CHECK]) { + os << "Error(dual)"; + } else os << "?(dual)"; + return os; +} + +} + diff --git a/src/extensions/polytope/clp/codac2_clp.h b/src/extensions/polytope/clp/codac2_clp.h new file mode 100644 index 000000000..659898061 --- /dev/null +++ b/src/extensions/polytope/clp/codac2_clp.h @@ -0,0 +1,276 @@ +/** + * \file codac2_polytope_clp.h + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2025 + * \license This program is distributed under the terms of + * the GNU Lesser General Public License (LGPL). + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "codac2_Index.h" +#include "codac2_Matrix.h" +#include "codac2_Vector.h" +#include "codac2_Row.h" +#include "codac2_IntervalRow.h" +#include "codac2_IntervalVector.h" +#include "codac2_IntervalMatrix.h" + +class ClpSimplex; /* or #include */ + +namespace codac2 { + +/** \class LPclp + * \brief LPclp class, to modelise different LP problems + * + */ +class LPclp { + public: + /** \brief Constructor of the whole problem + * + * LP problem : max objvect X s.t. mat X <= rhsvect + * + * \param mat Matrix of the constraints (constraints in rows) + * \param objvect Vector of objectives (length = mat.cols()) + * \param rhsvect RHS of the contraints (length = mat.rows()) + */ + LPclp (const Matrix &mat, const Row &objvect, + const Vector &rhsvect); + + /** \brief Constructor of the constraints, objective = 0 + * + * LP constraint : mat X <= rhsvect + * Can be used either to enter the objective afterwards + * or the check non-emptiness, or minimize the set of constraints + * + * \param mat Matrix of the constraints (constraints in rows) + * \param rhsvect RHS of the contraints (length = mat.rows()) + */ + LPclp (const Matrix &mat, const Vector &rhsvect); + + /** \brief Empty constructors, with only the size of the problem + * \param dim number of variables + * \param nbcsts number of constraints + */ + LPclp(Index dim, Index nbcsts); + + /** \brief destructor */ + ~LPclp(); + + /** \brief Add a constraint + * \param vst the (row) vector + * \param rhs its RHS + * \return the row number of the constraint */ + Index addConstraint(const Row &vst, double rhs); + /** \brief Set the objvective + * \param objvect the new objective + * \param rhs its RHS */ + void setObjective(const Row &objvect); + /** \brief Set the ''activity'' of a constraint + * inactive constraints <=> rhs put to oo + * \param cst row number of the constraint + * \param state new state (default true) + */ + void setActive(Index cst, bool state=true); + /** \brief Get the ''activity'' of a constraint + * inactive constraints <=> rhs put to oo + * \param cst row number of the constraint + * \return the ''activity'' of the constraint + */ + bool getActive(Index cst) const; + /** \brief Get the ''redundant'' status of a constraint + * can be used after a call to minimize() + * \param cst row number of the constraint + * \return the ''activity'' of the constraint + */ + bool isRedundant(Index cst) const; + + /** \brief Get the matrix of constraint + \return the matrix + */ + const Matrix &getMat() const; + /** \brief Get the objective row + * \return the objective + */ + const Row &getObjvect() const; + /** \brief Get the RHS vector + * \return the rhs vector + */ + const Vector &getRhsvect() const; + /** \brief Get a single constraint, as an IntervalRow + * \return the rhs vector + */ + Row getCst(Index i) const; + + /** \brief Status a constraint (internal) + * \arg \c REMOVED Constraint removed or never added + * \arg \c INACTIVE Inactive constraint + * \arg \c REDUNDANT Detected as redundant by \c minimize() + * \arg \c LOCKED Forced in (not used) + * \arg \c SETIN Equality (not used) + */ + enum CSTSTATUS { + REMOVED, /* constraint has been removed (or never added) */ + INACTIVE, /* inactive, can be made active */ + REDUNDANT, /* shown redundant wrt other constraints */ + LOCKED, /* must be used for this objective */ + SETIN, /* equality forced */ + SIZE_CSTSTATUS + }; + using lp_cststatus = std::bitset; + + /** \brief Results of a solving process + * Different status are defined, with respect to + * primal results (existence of points in the polytope) + * and dual results (existence of constaints matching the + * objective). Also, results about the basis are computed. + * \c LPRESULT is used in a bitset to describes the different + * results. + * \arg \c CHANGED No result because not computed or modified + * \arg \c EMPTY Polytope is empty + * \arg \c EMPTY_APPROX LP-coin says empty, but the verification + * was not conclusive + * \arg \c EMPTY_BBOX Emptiness was proved thanks to the bbox + * \arg \c NOTEMPTY The analysis gave a guaranteed point + * \arg \c NOTEMPTY_APPROX LP-coin says non-empty, but the + * verification of its result is not clear + * \arg \c UNBOUNDED Polytope is unbounded + * \arg \c UNBOUNDED_APPROX LP-coin says unbounded, but the + * verification was not conclusive + * \arg \c BOUNDED A bound is found for the objective + * \arg \c BOUNDED_APPROX LP-coin gave a bound, but the + * verification was not conclusive + * \arg \c BOUNDED_BBOX Bound was proved thanks to the bbox + * \arg \c ERROR_LPCOIN LP-coin returned an error + * \arg \c ERROR_PRIMAL_CHECK The checking of LP-coin primal (empty or not) result failed. + * \arg \c ERROR_DUAL_CHECK The checking of LP-coin dual (unbounded or not) result failed. + */ + enum LPRESULT { + CHANGED, + EMPTY, + EMPTY_APPROX, + EMPTY_BBOX, + NOTEMPTY, + NOTEMPTY_APPROX, + UNBOUNDED, + UNBOUNDED_APPROX, + BOUNDED, + BOUNDED_APPROX, + BOUNDED_BBOX, + ERROR_LPCOIN, + ERROR_PRIMAL_CHECK, + ERROR_DUAL_CHECK, + SIZE_LPRESULT + }; + /** \code{.cpp} + using lp_result_stat = std::bitset; + \endcode */ + using lp_result_stat = std::bitset; + + /** \brief Solve the problem, i.e. build it if needed, call lp-coin + * and check the result. + * \param checkempty check for emptiness: add a column in the model + * and maximize on this column + * \param option for now, the only values are 0 (normal) or 4 (restart + * from previous solutions if possible). */ + lp_result_stat solve(bool checkempty=false, int option=0); + + /** \brief detect redundant constraints, with a tolerance. + * return the number of remaining constraints, -1 if the polytope + * is empty. + * if checkempty=true, we check emptiness first will all constraints. + * tolerance works as follows : + * max is the interval computed by maximizing without the constraint + * rhs the rhs of the constraint + * max_l <= max_u <= rhs => redundant + * max_l <= rhs <= max_u => redundant if rhs + tol_up >= max_u + * rhs <= max_l <= max_u => redundant if + * rhs+tol_up >= max_u AND rhs+tol_down >= max_l + */ + int minimize_polytope(const Interval &tolerance, bool checkempty=true); + + /** \brief Returns the value of the objective after + * solving. The value is guaranteed as an overapproximation. + * E.g. empty means that the polytope is empty, but +oo means only + * that the polytope may be unbounded */ + const Interval &getValobj() const; + /** \brief Returns a solution point (as a ``small'' box) + * (with \c NOTEMPTY and \c NOTEMPTY_APPROX result). + * If a specific emptiness checking was done, then + * the point is in the ``middle'' of the polytope */ + const IntervalVector &getFeasiblePoint() const; + /** \brief Returns a dual solution (as a ``small'' box) + * (with \c BOUNDED and \c BOUNDED_APPROX result). + * For a emptiness checking, the vector may prove the + * global boundedness of the polytope */ + const IntervalRow &getBoundedRow() const; + /** \brief Returns a emptiness vector + * (with \c EMPTY and \c EMPTY_APPROX result). */ + const IntervalRow &getEmptyRow() const; + /** \brief Returns a unboundness vector + * (with \c UNBOUNDED and \c UNBOUNDED_APPROX result). */ + const IntervalVector &getUnboundedVect() const; + + private: + /* initial problem */ + Index nbRows, nbCols; /* dimension of the matrix */ + Matrix Amat; /* the constraint matrix.*/ + Row objvect; /* objective vector */ + Vector rhsvect; /* rhs values */ + std::vector cststat; + + lp_result_stat status; + std::vector rowBasis; /* limited to the rows */ + bool built; + bool built_emptytest; + ClpSimplex *model=nullptr; + Interval Valobj; + IntervalVector primalSol; + IntervalRow dualSol; + IntervalVector primalRay; + IntervalRow dualRay; + + IntervalVector bbox; + double timeout=4.0; + double maxIteration=200; + double tolerance=1e-9; + + void buildModel(bool emptytest); + void setModel(bool emptytest); + + void correctBasisInverse(IntervalMatrix &basisInverse, + const IntervalMatrix &basis) const; + void reset_solution(); + lp_result_stat testGeneralApproach(int stat, bool checkempty); + + static IntervalMatrix infinite_sum_enclosure (const IntervalMatrix& M); +}; + + +std::ostream& print_lp_stat(std::ostream& os, const LPclp::lp_result_stat x); +// std::ostream& operator<<(std::ostream& os, const LPclp &lp); + +inline const Matrix &LPclp::getMat() const { return Amat; } +inline const Row &LPclp::getObjvect() const { return objvect; } +inline const Vector &LPclp::getRhsvect() const { return rhsvect; } +inline Row LPclp::getCst(Index i) const { return Amat.row(i); } +inline const Interval &LPclp::getValobj() const { return Valobj; } +inline bool LPclp::isRedundant(Index cst) const { return cststat[cst][REDUNDANT]; } +inline const IntervalVector &LPclp::getFeasiblePoint() const { return primalSol; } +inline const IntervalRow &LPclp::getBoundedRow() const { return dualSol; } +inline const IntervalRow &LPclp::getEmptyRow() const { return dualRay; } +inline const IntervalVector &LPclp::getUnboundedVect() const { return primalRay; } + +} + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 456ad6016..b2bc6fd7e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -71,6 +71,7 @@ list(APPEND SRC_TESTS # listing files without extension core/matrices/codac2_tests_Vector core/matrices/codac2_tests_inversion core/matrices/codac2_tests_IntFullPivLU +# core/matrices/codac2_tests_IntLDLT core/operators/codac2_tests_operators @@ -100,6 +101,14 @@ if (WITH_CAPD) set(CODAC_LIBRARIES ${CODAC_LIBRARIES} ${PROJECT_NAME}-capd capd::capd) endif() +# POLYTOPE test +if (WITH_POLYTOPE) + list(APPEND SRC_TESTS + extensions/polytope/codac2_tests_clp + ) + set(CODAC_LIBRARIES ${CODAC_LIBRARIES} ${PROJECT_NAME}-polytope ${CLP_LIBRARIES}) +endif() + # IBEX test #if (WITH_IBEX) list(APPEND SRC_TESTS @@ -126,4 +135,4 @@ foreach(SRC_TEST ${SRC_TESTS}) add_test(NAME ${TEST_NAME}_py COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/${SRC_TEST}.py) endif() -endforeach() \ No newline at end of file +endforeach() From fc67e66e21bc7dfe42bf1cfbfea13c57f541544b Mon Sep 17 00:00:00 2001 From: damien-masse Date: Mon, 29 Sep 2025 14:16:58 +0200 Subject: [PATCH 02/26] partial construction --- CMakeLists.txt | 25 +- src/CMakeLists.txt | 24 +- src/extensions/polytope/CMakeLists.txt | 7 + src/extensions/polytope/clp/codac2_clp.cpp | 465 +++++++++++----- src/extensions/polytope/clp/codac2_clp.h | 132 ++++- src/extensions/polytope/codac2_Facet.h | 112 ++++ src/extensions/polytope/codac2_Polytope.cpp | 275 ++++++++++ src/extensions/polytope/codac2_Polytope.h | 295 ++++++++++ src/extensions/polytope/codac2_dd.cpp | 578 ++++++++++++++++++++ src/extensions/polytope/codac2_dd.h | 182 ++++++ 10 files changed, 1932 insertions(+), 163 deletions(-) create mode 100644 src/extensions/polytope/codac2_Facet.h create mode 100644 src/extensions/polytope/codac2_Polytope.cpp create mode 100644 src/extensions/polytope/codac2_Polytope.h create mode 100644 src/extensions/polytope/codac2_dd.cpp create mode 100644 src/extensions/polytope/codac2_dd.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ae2846889..025e0c97b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -159,15 +159,22 @@ # Looking for CLP (if needed) ################################################################################ - option(WITH_POLYTOPE "Build with polytopes using CLP" OFF) - - if(WITH_POLYTOPE) - include(./src/extensions/polytope/FindClp.cmake) - message(STATUS "Found CLP version ${CLP_VERSION}") - message(STATUS "Found CLP inclusion ${CLP_INCLUDE_DIRS}") - endif() - - +option(WITH_POLYTOPE "Build with polytopes using CLP" OFF) + + if(WITH_POLYTOPE) + include(./src/extensions/polytope/FindClp.cmake) + message(STATUS "Found CLP version ${CLP_VERSION}") + message(STATUS "Found CLP inclusion ${CLP_INCLUDE_DIRS}") + endif() + +# option(WITH_POLYTOPE "Build with polytopes using CDD library" ON) +# +# if(WITH_POLYTOPE) +# include(./src/extensions/cdd-polytope/FindCdd.cmake) +# message(STATUS "Found CDD version ${CDD_VERSION}") +# message(STATUS "Found CDD inclusion ${CDD_INCLUDE_DIRS}") +# endif() +# ################################################################################ # Compile sources ################################################################################ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0429fb501..fd6eaf2b9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,6 +14,10 @@ add_subdirectory(extensions/capd) endif() + if(WITH_POLYTOPE) + add_subdirectory(extensions/polytope/) + endif() + #if(WITH_IBEX) # included by default for now add_subdirectory(extensions/ibex) #endif() @@ -119,6 +123,24 @@ endif() + if(WITH_POLYTOPE) + + file(APPEND ${CODAC_CMAKE_CONFIG_FILE} " + + # Optional 3rd party: + + find_path(CODAC_POLYTOPE_INCLUDE_DIR ${PROJECT_NAME}-polytope.h + PATH_SUFFIXES include/${PROJECT_NAME}-polytope) + set(CODAC_INCLUDE_DIRS \${CODAC_INCLUDE_DIRS} \${CODAC_POLYTOPE_INCLUDE_DIR} \${CLP_INCLUDE_DIRS}) + + find_library(CODAC_POLYTOPE_LIBRARY NAMES ${PROJECT_NAME}-polytope + PATH_SUFFIXES lib) + + set(CODAC_LIBRARIES \${CODAC_LIBRARIES} \${CODAC_POLYTOPE_LIBRARY} ${CLP_LIBRARIES}) + ") + + endif() + #if(WITH_IBEX) # included by default for now file(APPEND ${CODAC_CMAKE_CONFIG_FILE} " @@ -157,4 +179,4 @@ get_filename_component(header_name ${header_path} NAME) file(APPEND ${CODAC_MAIN_HEADER} "#include <${header_name}>\n") endforeach() - install(FILES ${CODAC_MAIN_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/) \ No newline at end of file + install(FILES ${CODAC_MAIN_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/) diff --git a/src/extensions/polytope/CMakeLists.txt b/src/extensions/polytope/CMakeLists.txt index 21ecf11bd..c39dc892c 100644 --- a/src/extensions/polytope/CMakeLists.txt +++ b/src/extensions/polytope/CMakeLists.txt @@ -6,6 +6,11 @@ list(APPEND CODAC_POLYTOPE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/clp/codac2_clp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/clp/codac2_clp.h + ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Facet.h + ${CMAKE_CURRENT_SOURCE_DIR}/codac2_dd.h + ${CMAKE_CURRENT_SOURCE_DIR}/codac2_dd.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Polytope.h + ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Polytope.cpp ) ################################################################################ @@ -20,6 +25,8 @@ list(APPEND CODAC_POLYTOPE_SRC add_library(${PROJECT_NAME}-polytope ${CODAC_POLYTOPE_SRC}) target_include_directories(${PROJECT_NAME}-polytope PUBLIC ${CLP_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/ + ${CMAKE_CURRENT_SOURCE_DIR}/clp ) target_link_libraries(${PROJECT_NAME}-polytope PUBLIC ${PROJECT_NAME}-core Ibex::ibex Eigen3::Eigen ${CLP_LIBRARIES}) diff --git a/src/extensions/polytope/clp/codac2_clp.cpp b/src/extensions/polytope/clp/codac2_clp.cpp index d28190dd3..c26be03f1 100644 --- a/src/extensions/polytope/clp/codac2_clp.cpp +++ b/src/extensions/polytope/clp/codac2_clp.cpp @@ -26,6 +26,7 @@ #include "codac2_BoolInterval.h" +#include "codac2_Facet.h" #include "codac2_clp.h" #undef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG @@ -34,47 +35,149 @@ namespace codac2 { /********** Problem building funtions ***********/ +LPclp::LPclp (Index dim, const std::vector &facets, + const Row &objvect) : + nbRows(facets.size()), nbCols(dim), + Afacets(facets), objvect(objvect), + cststat(nbRows,0), + rowBasis(nbRows,false), built(false), model(nullptr), + primalSol(nbCols), dualSol(nbRows), primalRay(nbCols), dualRay(nbRows), + bbox(nbCols) +{ +} + +LPclp::LPclp (Index dim, const std::vector &facets) : + nbRows(facets.size()), nbCols(dim), + Afacets(facets), objvect(Row::Zero(nbCols)), + cststat(nbRows,0), + rowBasis(nbRows,false), built(false), model(nullptr), + primalSol(nbCols), dualSol(nbRows), primalRay(nbCols), dualRay(nbRows), + bbox(nbCols) +{ +} + +LPclp::LPclp (Index dim, const std::vector &facets, + const IntervalVector &box) : + nbRows(facets.size()), nbCols(dim), + Afacets(facets), objvect(Row::Zero(nbCols)), + cststat(nbRows,0), + rowBasis(nbRows,false), built(false), model(nullptr), + primalSol(nbCols), dualSol(nbRows), primalRay(nbCols), dualRay(nbRows), + bbox(box) +{ +} + LPclp::LPclp(const Matrix &mat, const Row &objvect, - const Vector &rhsvect) : + const Vector &rhsvect, const std::vector &eqSet) : nbRows(mat.rows()), nbCols(mat.cols()), - Amat(mat), + Afacets(nbRows), objvect(objvect), - rhsvect(rhsvect), cststat(nbRows,0), + cststat(nbRows,0), rowBasis(nbRows,false), built(false), model(nullptr), primalSol(nbCols), dualSol(nbRows), primalRay(nbCols), dualRay(nbRows), bbox(nbCols) { + for (Index i=0;i=0 && i &eqSet) : nbRows(mat.rows()), nbCols(mat.cols()), - Amat(mat), + Afacets(nbRows), objvect(Row::Zero(nbCols)), - rhsvect(rhsvect), cststat(nbRows,0), + cststat(nbRows,0), rowBasis(nbRows,false), built(false), model(nullptr), primalSol(nbCols), dualSol(nbRows), primalRay(nbCols), dualRay(nbRows), bbox(nbCols) { - + for (Index i=0;i=0 && iAfacets.push_back(facet); + this->cststat.push_back(0); + this->dualSol.resize(nbRows+1); + this->dualRay.resize(nbRows+1); + if (built) { + CoinBuild buildObject; + Index nbCols2=nbCols+(built_emptytest ? 1 : 0); + int* row2Index = new int[nbCols2]; + for(Index i=0;iaddFacetToCoinBuild(buildObject,facet,0,row2Index,row2Vals); + delete row2Index; + if (built_emptytest) delete row2Vals; + model->addRows(buildObject); + this->status=1<addConstraint(newFacet); +} + void LPclp::setObjective(const Row &objvect) { this->objvect=objvect; if (built && !built_emptytest) { /* change the values of the objective */ @@ -89,12 +192,18 @@ void LPclp::setActive(Index cst, bool state) { if (state) { if (!cststat[cst][INACTIVE]) return; cststat[cst][INACTIVE]=false; - if (built) model->setRowUpper(cst,rhsvect[cst]); + if (built) { + model->setRowUpper(cst,Afacets[cst].rhs); + if (Afacets[cst].eqcst) model->setRowLower(cst,Afacets[cst].rhs); + } this->status=1<setRowUpper(cst,DBL_MAX); + if (built) { + model->setRowUpper(cst,DBL_MAX); + model->setRowLower(cst,-DBL_MAX); + } this->status=1<addFacetToCoinBuild(buildObject, + Afacets[k],cststat[k],row2Index,row2Vals); } model->addRows(buildObject); delete row2Vals; @@ -151,7 +250,6 @@ void LPclp::buildModel(bool emptytest) { if (emptytest) model->setObjectiveCoefficient(nbCols, 1.0); built=true; - built_emptytest=emptytest; } /* adding/removing emptiness testing on a built model */ @@ -165,7 +263,8 @@ void LPclp::setModel(bool emptytest) { int* col2Index=new int[nbRows]; for(Index i=0;iaddColumn (nbRows,col2Index,col2Vals,-COIN_DBL_MAX,COIN_DBL_MAX,1.0); for (Index i =0; istatus(),checkempty); } +int LPclp::minimize_eqpolytope() { + std::vector triMat; + std::vector triRhs; + Index nbCsts=0; + std::vector mainCol; + for (Index i = 0; iAfacets[i].eqcst) continue; + if (cststat[i][REMOVED] || cststat[i][INACTIVE] || cststat[i][REDUNDANT]) + continue; + triMat.emplace_back(this->Afacets[i].row); + triRhs.emplace_back(this->Afacets[i].rhs); + for (Index j=0;jbVal) { + bVal=a; + bCol=j; + } + } + if (bCol==-1) { + if (triRhs[nbCsts].contains(0)) { + cststat[i][REDUNDANT]=true; + continue; + } + for (Index j=0;jminimize_eqpolytope(); + if (nb==-1) return -1; if (checkempty) { lp_result_stat stat=this->solve(true); if (stat[EMPTY]) return -1; } bool solvedOnce=checkempty; - int nb=0; for (Index i=nbRows-1;i>=0;i--) { if (cststat[i][REMOVED] || cststat[i][INACTIVE] || cststat[i][REDUNDANT]) continue; + if (this->Afacets[i].eqcst) { nb++; continue; } this->setActive(i,false); - this->setObjective(this->getCst(i)); + this->setObjective(this->Afacets[i].row); lp_result_stat stat=this->solve(false,(solvedOnce ? 4 : 0)); if (stat[EMPTY]) return -1; if (stat[BOUNDED]) { - if (this->Valobj.ub()<=rhsvect[i]+tolerance.ub() - && this->Valobj.lb()<=rhsvect[i]+tolerance.lb()) { + if (this->Valobj.ub()<=this->Afacets[i].rhs+tolerance.ub() + && this->Valobj.lb()<=this->Afacets[i].rhs+tolerance.lb()) { cststat[i][REDUNDANT]=true; continue; } @@ -331,6 +476,54 @@ void LPclp::correctBasisInverse(IntervalMatrix &basisInverse, basisInverse = (Id+Eps)*basisInverse; } +Interval LPclp::dotprodrhs(const IntervalRow &d) const { + Interval r(0.0); + for (Index i=0;iAfacets[i].rhs; + } + return r; +} + + +/* + try to check a product dualVect * facets(extended) + initSum = 0, + correcting it if needed using basisInverse + */ +BoolInterval LPclp::checkDualForm(IntervalRow &dualVect, + IntervalRow &initSum, const IntervalMatrix &basisInverse, + const std::vector &wholeBasis, Index nbRowsInBasis, + bool checkempty) const { + for (Index i=0;i0.0) { + return BoolInterval::FALSE; + } + } + for (Index i=0;i0.0)) { ok=BoolInterval::FALSE; break; } + } + return ok; +} + + + /* this function tests all part sequentially. It starts from the result of CLP solve */ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { @@ -390,7 +583,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { for (Index c=0;cAfacets[r].row[c]; } } if (checkempty) { @@ -496,38 +689,17 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { std::cout << "dualRay: " << dualRay << "\n"; #endif IntervalRow errorNeum=IntervalRow::Zero(nbCols2); - errorNeum.head(nbCols)=dualRay*Amat; - if (checkempty) errorNeum[nbCols]=dualRay.sum()-1.0; - IntervalRow error = errorNeum*basisInverse; - BoolInterval ok=BoolInterval::TRUE; - for (Index i=0;icheckDualForm(dualRay, errorNeum, + basisInverse, wholeBasis, nbRowsInBasis, checkempty); #ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG std::cout << "dualRay with error: " << dualRay << "\n"; std::cout << "ok: " << ok << "\n"; #endif - if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) - { /* vérifie les variables colonnes, qui doivent - être libre, ainsi que les contraintes inactives */ - for (Index i=nbRowsInBasis;i0.0) { - ok=BoolInterval::FALSE; - break; - } - } - if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) - for (Index i=0;i0.0)) { ok=BoolInterval::FALSE; break; } - } - } if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) { - Interval product = dualRay.dot(rhsvect); + Interval product = this->dotprodrhs(dualRay); if (checkempty) Valobj=Interval(-oo,product.ub()); if (product.lb()>=0.0) ok=BoolInterval::FALSE; else if (product.ub()>=0) ok=BoolInterval::UNKNOWN; @@ -542,13 +714,13 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { return status; } /* attempt with Neumaier */ - if (!bbox.is_unbounded()) { + if (!checkempty && !bbox.is_unbounded()) { /* restart with the ray */ for (Index i=0;idotprodrhs(dualRay); productNeum -= errorNeum.head(nbCols).dot(bbox); if (productNeum.ub()<0.0) { status[EMPTY]=true; @@ -560,10 +732,15 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { } } else { /* other approach : we try to compensate with other values */ - IntervalMatrix comp = ((IntervalMatrix)(Amat))*basisInverse.topRows(nbCols); - if (checkempty) { - IntervalVector v = IntervalVector::Ones(nbRows); - comp += v*basisInverse.row(nbCols); + IntervalMatrix comp(nbRows,nbCols2); + { + IntervalVector v = IntervalVector::Ones(nbRows); + for (Index j=0;jAfacets[j].row))* + basisInverse.topRows(nbCols); + if (this->Afacets[j].eqcst) v[j]=0.0; + } + if (checkempty) comp += v*basisInverse.row(nbCols); } /* for each epsilon value in dualSol, try to find a complement in comp */ @@ -574,6 +751,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { okrow[j].set_empty(); nbok--; continue; } if (rowBasis[j]) { okrow[j].set_empty(); nbok--; continue; } + if (Afacets[j].eqcst) { okrow[j].init(); } } for (Index i=0;iokrow[j].ub()) dualRay[j]=okrow[j].mid(); break; } - errorNeum.head(nbCols)=dualRay*Amat; - if (checkempty) errorNeum[nbCols]=dualRay.sum()-1.0; - error = errorNeum*basisInverse; + errorNeum=IntervalRow::Zero(nbCols2); + for (Index i=0;idotprodrhs(dualRay); if (checkempty) Valobj=Interval(-oo,product.ub()); if (product.lb()>=0.0) ok=BoolInterval::FALSE; else if (product.ub()>=0) ok=BoolInterval::UNKNOWN; @@ -679,19 +866,21 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { if (!checkempty) { IntervalRow dSol= objvect*basisInverse; - Interval product = dSol.dot(rhsvect); + Interval product = this->dotprodrhs(dSol); dualSol.setZero(); bool ok=true; for (Index i=0;i0.0) { + if (dSol[i].mig()>0.0) { if (bbox.is_unbounded()) { ok=false; break; } status[BOUNDED_BBOX]=true; - product -= dSol[i].lb() * + product -= dSol[i] * bbox[wholeBasis[i]-nbRows]; } } @@ -709,20 +898,29 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { /* 1) we modify primalSol to get a box around the "optimal" place */ /* note: should be done only for checkempty=false (we do not _really_ want a box around the "center" of the polytope */ - IntervalVector errorPart = Amat*primalSol - rhsvect; + IntervalVector errorPart(nbRows); + for (Index i=0;iAfacets[i].row*primalSol - this->Afacets[i].rhs; + } if (!checkempty) { IntervalVector errorInBasis= IntervalVector::Zero(nbCols2); - /* error must be always negative, the real error is the positive part */ + /* except for equalities, + error must be always negative, the real error is the positive part */ for (Index i=0;iAfacets[r].eqcst) { + errorInBasis[i] = errorPart[r]; continue; + } errorInBasis[i]=max(errorPart[r],Interval::zero()); /* negative is ok */ } /* compute the correction */ IntervalVector correction=basisInverse*errorInBasis; primalSol -= correction; - errorPart = Amat*primalSol - rhsvect; + for (Index i=0;iAfacets[i].row*primalSol - this->Afacets[i].rhs; + } } /* check the different constraints @@ -742,6 +940,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { #ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG std::cout << "check " << i << "\n"; #endif + if (Afacets[i].eqcst) errorPart[i]=abs(errorPart[i]); mxVal=max(errorPart[i],mxVal); if (mxVal.lb()>0.0) break; } @@ -756,12 +955,16 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { it back if needed */ IntervalVector primalSol2=primalSol.mid(); for (Index i=0;iAfacets[i].row*primalSol2 - this->Afacets[i].rhs; if (err.ub()>=0.0) - primalSol2 -= (1.1*err.ub()/Amat.row(i).squaredNorm())*Amat.row(i); + primalSol2 -= (1.1*err.ub()/this->Afacets[i].row.squaredNorm())*this->Afacets[i].row; } primalSol2=primalSol2.mid(); - errorPart=Amat*primalSol2 - rhsvect; + for (Index i=0;iAfacets[i].row*primalSol2 - this->Afacets[i].rhs; + } #ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG std::cout << "new primalSol : " << primalSol2 << "\n"; std::cout << "errorPart 2 : " << errorPart << "\n"; @@ -773,6 +976,10 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { ok=false; /* failed */ break; } + if (Afacets[i].eqcst && errorPart[i].lb()<0.0) { + ok=false; /* failed */ + break; + } } if (ok) { status[NOTEMPTY]=true; @@ -798,7 +1005,10 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { for (Index i=0;iAfacets[i].row*primalRay; + } if (!checkempty) { /* if !checkempty, the ray "follows" some facets, so we need to guarantee that it will not cross it */ @@ -806,18 +1016,23 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { for (Index i=0;iAfacets[r].eqcst) + { errorInBasis[i] = errorPart[r]; continue; } errorInBasis[i]=max(errorPart[r],Interval::zero()); /* negative is ok */ } /* compute the correction */ IntervalVector correction=basisInverse*errorInBasis; primalRay -= correction; - errorPart = Amat*primalRay; + for (Index i=0;iAfacets[i].row*primalRay; + } } Interval mxVal(-1.0); for (Index i=0;i0.0) break; } @@ -829,13 +1044,17 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { it back if needed */ IntervalVector primalRay2=primalRay.mid(); for (Index i=0;iAfacets[i].row*primalRay2; if (err.ub()>=0.0) { - primalRay2 -= (1.1*err.ub()/Amat.row(i).squaredNorm())*Amat.row(i); + primalRay2 -= (1.1*err.ub()/this->Afacets[i].row.squaredNorm())*this->Afacets[i].row; } } primalRay2=primalRay2.mid(); - errorPart=Amat*primalRay2; + for (Index i=0;iAfacets[i].row*primalRay2; + } #ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG std::cout << "new primalRay : " << primalRay2 << "\n"; std::cout << "errorPart 2 : " << errorPart << "\n"; @@ -875,48 +1094,28 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { val*Amat*basisInverse => basisInverse.cols */ /* if checkempty, the goal may be to prove the global boundedness of the polytope */ - IntervalRow errorNeum=IntervalRow::Zero(nbCols2); + BoolInterval ok; + IntervalRow errorNeum; if (checkempty) { - errorNeum.head(nbCols)=dualSol*Amat; - errorNeum[nbCols]=dualSol.sum()-1.0; + errorNeum=IntervalRow::Zero(nbCols2); + errorNeum[nbCols]= -1.0; } else { - errorNeum = dualSol*Amat-objvect; - } - IntervalRow error = errorNeum*basisInverse; - BoolInterval ok=BoolInterval::TRUE; - for (Index i=0;i0.0) { - ok=BoolInterval::FALSE; - break; - } - } - if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) - for (Index i=0;i0.0)) { ok=BoolInterval::FALSE; break; } - } + errorNeum=-objvect; } + ok = this->checkDualForm(dualSol, errorNeum, + basisInverse, wholeBasis, nbRowsInBasis, checkempty); if (ok==BoolInterval::TRUE) { status[BOUNDED]=true; - Valobj = min(Valobj,dualSol.dot(rhsvect)); + Valobj = min(Valobj,this->dotprodrhs(dualSol)); return status; } /* Neumaier's approach , only with !checkempty */ - if (!bbox.is_unbounded()) { + if (!checkempty && !bbox.is_unbounded()) { /* rebuild the solution */ for (Index i=0;idualRowSolution()[i]; } - Interval productNeum = dualSol.dot(rhsvect); + Interval productNeum = this->dotprodrhs(dualSol); productNeum -= errorNeum.head(nbCols).dot(bbox); status[BOUNDED_BBOX]=true; status[BOUNDED]=true; @@ -926,7 +1125,13 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { /* other approach : we try to compensate with other values */ /* for each epsilon value in dualSol, try to find a complement in comp */ - IntervalMatrix comp = ((IntervalMatrix)(Amat))*basisInverse; + IntervalMatrix comp(nbRows,nbCols); + { + for (Index j=0;jAfacets[j].row))* + basisInverse; + } + } std::vector okrow(nbRows,Interval(0.0,oo)); int nbok=nbRows; for (Index j=0;jokrow[j].ub()) dualSol[j]=okrow[j].mid(); break; } - errorNeum = dualSol*Amat-objvect; - error = errorNeum*basisInverse; + errorNeum=-objvect; + for (Index i=0;iAfacets[i].row; + } + IntervalRow error = errorNeum*basisInverse; ok=BoolInterval::TRUE; for (Index i=0;idotprodrhs(dualSol)); return status; } } diff --git a/src/extensions/polytope/clp/codac2_clp.h b/src/extensions/polytope/clp/codac2_clp.h index 659898061..725b71bd7 100644 --- a/src/extensions/polytope/clp/codac2_clp.h +++ b/src/extensions/polytope/clp/codac2_clp.h @@ -1,5 +1,5 @@ /** - * \file codac2_polytope_clp.h + * \file codac2_clp.h * ---------------------------------------------------------------------------- * \date 2025 * \author Damien Massé @@ -26,27 +26,61 @@ #include "codac2_IntervalRow.h" #include "codac2_IntervalVector.h" #include "codac2_IntervalMatrix.h" +#include "codac2_BoolInterval.h" +#include "codac2_Facet.h" -class ClpSimplex; /* or #include */ +class ClpSimplex; /* instead of including ClpSimplex.hpp */ +class CoinBuild; namespace codac2 { + /** \class LPclp * \brief LPclp class, to modelise different LP problems * */ class LPclp { public: - /** \brief Constructor of the whole problem + /** \brief Constructor with the set of facets + * + * LP problem : max objvect X s.t. mat X <= rhsvect + * + * \param dim dimension of the space + * \param facets the facets + * \param objvect the objective */ + LPclp (Index dim, const std::vector &facets, + const Row &objvect); + + /** \brief Constructor with the set of facets, no objective + * + * LP problem : max objvect X s.t. mat X <= rhsvect + * + * \param dim dimension of the space + * \param facets the facets */ + LPclp (Index dim, const std::vector &facets); + + /** \brief Constructor with the set of facets, bbox, no objective + * + * LP problem : max objvect X s.t. mat X <= rhsvect + * + * \param dim dimension of the space + * \param facets the facets + * \param box the bounding box for Neumaier's approx */ + LPclp (Index dim, const std::vector &facets, + const IntervalVector &box); + + /** \brief Constructor from a matrix * * LP problem : max objvect X s.t. mat X <= rhsvect * * \param mat Matrix of the constraints (constraints in rows) * \param objvect Vector of objectives (length = mat.cols()) * \param rhsvect RHS of the contraints (length = mat.rows()) + * \param eqSet rows which are equalities */ LPclp (const Matrix &mat, const Row &objvect, - const Vector &rhsvect); + const Vector &rhsvect, + const std::vector &eqSet=std::vector()); /** \brief Constructor of the constraints, objective = 0 * @@ -56,23 +90,41 @@ class LPclp { * * \param mat Matrix of the constraints (constraints in rows) * \param rhsvect RHS of the contraints (length = mat.rows()) + * \param eqSet rows which are equalities + */ + LPclp (const Matrix &mat, const Vector &rhsvect, + const std::vector &eqSet=std::vector()); + + /** \brief Copy constructor + * Do not build the model, even if the original model is built + * also do not copy all results related to the last computation : + * status, row_basis, valObj, primalSol, primalRay, dualSol, dualRay */ - LPclp (const Matrix &mat, const Vector &rhsvect); + LPclp (const LPclp& P); - /** \brief Empty constructors, with only the size of the problem + /** \brief Empty constructor * \param dim number of variables - * \param nbcsts number of constraints */ - LPclp(Index dim, Index nbcsts); + LPclp(Index dim); /** \brief destructor */ ~LPclp(); + /** \brief set bbox + * \param bbox the new bbox + */ + void set_bbox(const IntervalVector &box); + + /** \brief Add a constraint + * \param facet the facet + * \return the row number of the constraint */ + Index addConstraint(const Facet &facet); /** \brief Add a constraint * \param vst the (row) vector * \param rhs its RHS + * \param isEq true if it is an equality * \return the row number of the constraint */ - Index addConstraint(const Row &vst, double rhs); + Index addConstraint(const Row &vst, double rhs, bool isEq=false); /** \brief Set the objvective * \param objvect the new objective * \param rhs its RHS */ @@ -96,22 +148,26 @@ class LPclp { */ bool isRedundant(Index cst) const; - /** \brief Get the matrix of constraint - \return the matrix + /** \brief Get the set of facets + \return the facets */ - const Matrix &getMat() const; + const std::vector &getFacets() const; /** \brief Get the objective row * \return the objective */ const Row &getObjvect() const; - /** \brief Get the RHS vector + /** \brief Build the matrix + * \return the matrix + */ + Matrix getMat() const; + /** \brief Build the RHS vector * \return the rhs vector */ - const Vector &getRhsvect() const; - /** \brief Get a single constraint, as an IntervalRow + Vector getRhsvect() const; + /** \brief Get a single constraint, as an facet * \return the rhs vector */ - Row getCst(Index i) const; + const Facet &getCst(Index i) const; /** \brief Status a constraint (internal) * \arg \c REMOVED Constraint removed or never added @@ -186,6 +242,19 @@ class LPclp { * from previous solutions if possible). */ lp_result_stat solve(bool checkempty=false, int option=0); + /** \brief detect redundant equality constraints + * returns the number of remaining equality constraints, -1 if empty + * tolerance works as follows : + * each new equality is added to a (pseudo)-triangular matrix + * of IntervalVector + * when a row with all coefficients containing 0 appears, + * a) if 0 is in the rhs, declare the row as redundant + * b) if not, and all coefficients are exactly 0, returns empty + * c) if bounding box, use it to try to prove emptiness, if not + * possible keep the row (and hope for emptiness later?) + */ + int minimize_eqpolytope(); + /** \brief detect redundant constraints, with a tolerance. * return the number of remaining constraints, -1 if the polytope * is empty. @@ -222,12 +291,12 @@ class LPclp { * (with \c UNBOUNDED and \c UNBOUNDED_APPROX result). */ const IntervalVector &getUnboundedVect() const; + private: /* initial problem */ Index nbRows, nbCols; /* dimension of the matrix */ - Matrix Amat; /* the constraint matrix.*/ + std::vector Afacets; /* the facets */ Row objvect; /* objective vector */ - Vector rhsvect; /* rhs values */ std::vector cststat; lp_result_stat status; @@ -245,6 +314,10 @@ class LPclp { double timeout=4.0; double maxIteration=200; double tolerance=1e-9; + + void addFacetToCoinBuild(CoinBuild &buildObject, + const Facet &facet, lp_cststatus status, + int *row2Index, double *row2Vals) const; void buildModel(bool emptytest); void setModel(bool emptytest); @@ -252,25 +325,34 @@ class LPclp { void correctBasisInverse(IntervalMatrix &basisInverse, const IntervalMatrix &basis) const; void reset_solution(); + Interval dotprodrhs(const IntervalRow &d) const; + + BoolInterval checkDualForm(IntervalRow &dualVect, + IntervalRow &initSum, const IntervalMatrix &basisInverse, + const std::vector &wholeBasis, Index nbRowsInBasis, + bool checkempty) const; lp_result_stat testGeneralApproach(int stat, bool checkempty); + static IntervalMatrix infinite_sum_enclosure (const IntervalMatrix& M); + }; std::ostream& print_lp_stat(std::ostream& os, const LPclp::lp_result_stat x); // std::ostream& operator<<(std::ostream& os, const LPclp &lp); -inline const Matrix &LPclp::getMat() const { return Amat; } +// inline const Matrix &LPclp::getMat() const { return Amat; } /* TODO */ inline const Row &LPclp::getObjvect() const { return objvect; } -inline const Vector &LPclp::getRhsvect() const { return rhsvect; } -inline Row LPclp::getCst(Index i) const { return Amat.row(i); } +// inline const Vector &LPclp::getRhsvect() const { return rhsvect; } /* TODO */ +inline const Facet &LPclp::getCst(Index i) const { return Afacets[i]; } inline const Interval &LPclp::getValobj() const { return Valobj; } inline bool LPclp::isRedundant(Index cst) const { return cststat[cst][REDUNDANT]; } -inline const IntervalVector &LPclp::getFeasiblePoint() const { return primalSol; } -inline const IntervalRow &LPclp::getBoundedRow() const { return dualSol; } -inline const IntervalRow &LPclp::getEmptyRow() const { return dualRay; } -inline const IntervalVector &LPclp::getUnboundedVect() const { return primalRay; } + inline const IntervalVector &LPclp::getFeasiblePoint() const { return this->primalSol; } +inline const IntervalRow &LPclp::getBoundedRow() const { return this->dualSol; } +inline const IntervalRow &LPclp::getEmptyRow() const { return this->dualRay; } +inline const IntervalVector &LPclp::getUnboundedVect() const { return this->primalRay; } +inline void LPclp::set_bbox(const IntervalVector &box) { this->bbox=box; } } diff --git a/src/extensions/polytope/codac2_Facet.h b/src/extensions/polytope/codac2_Facet.h new file mode 100644 index 000000000..b293f06dd --- /dev/null +++ b/src/extensions/polytope/codac2_Facet.h @@ -0,0 +1,112 @@ +/** + * \file codac2_Facet.h + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2025 + * \license This program is distributed under the terms of + * the GNU Lesser General Public License (LGPL). + */ + +#pragma once + +#include /* uses swap */ + +#include "codac2_Index.h" +#include "codac2_Vector.h" +#include "codac2_Row.h" +#include "codac2_Interval.h" +#include "codac2_IntervalVector.h" +#include "codac2_IntervalRow.h" +#include "codac2_BoolInterval.h" + +namespace codac2 { + +/** \brief Potential inclusion relation between sets + * INCLUDES : A is included in B + * MAYINCLUDE : A may be included in B + * NOTINCLUDE : A\B is not empty + * INTERSECTS : A inter B is non empty + * MAYINTERSECT : A inter B may be non empty + * DISJOINT : A inter B is empty + */ +enum INCLREL { + INCLUDES, + MAYINCLUDE, + NOTINCLUDE, + INTERSECTS, + MAYINTERSECT, + DISJOINT, + SIZEINCLREL +}; +using polytope_inclrel = std::bitset; +const polytope_inclrel inclrel_includes = 1<row.swap(f.row); + std::swap(this->rhs,f.rhs); + std::swap(this->eqcst,f.eqcst); + } + + /* test only includes and intersects */ + polytope_inclrel relation_Box(const IntervalVector &b) const { + if (b.is_empty()) return inclrel_includes | inclrel_disjoint; + IntervalVector a(b); + /* check the vertex that maximizes row */ + for (Index i=0;i0) a[i]=a[i].ub(); else a[i]=a[i].lb(); + } + Interval maxv = row.dot(a)-rhs; + polytope_inclrel r1=0; + if (maxv.ub()<=0.0) { + if (!eqcst) return inclrel_includes | inclrel_intersects; + else if (maxv.ub()<0.0) return inclrel_notinclude | inclrel_disjoint; + r1 = inclrel_includes; + } else if (maxv.lb()<=0.0) { + r1 = inclrel_mayinclude; + } else { + r1 = inclrel_notinclude; + } + /* check the vertex that minimizes row */ + for (Index i=0;i0) a[i]=b[i].lb(); else a[i]=b[i].ub(); + } + Interval minv = row.dot(a)-rhs; + if (minv.lb()>0.0) return inclrel_notinclude | inclrel_disjoint; + if (!eqcst) { + if (minv.ub()>0.0) return r1 | inclrel_mayintersect; + return r1 | inclrel_intersects; + } else { + if (r1[INCLUDES]) { /* maxv.ub == 0 */ + if (minv.lb()>=0.0) return + inclrel_includes | inclrel_intersects; + return (maxv.lb()==0.0 ? inclrel_intersects : inclrel_mayintersect) + | (minv.ub()<0.0 ? inclrel_notinclude : inclrel_mayinclude); + } + if (minv.ub()<0.0) r1 = inclrel_notinclude; + if (maxv.lb()>=0.0 && minv.ub()<=0.0) + return r1 | inclrel_intersects; + else return r1 | inclrel_mayintersect; + } + } + +}; + +} diff --git a/src/extensions/polytope/codac2_Polytope.cpp b/src/extensions/polytope/codac2_Polytope.cpp new file mode 100644 index 000000000..41b7d8c48 --- /dev/null +++ b/src/extensions/polytope/codac2_Polytope.cpp @@ -0,0 +1,275 @@ +/** + * \file codac2_Polytope.cpp + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2025 + * \license This program is distributed under the terms of + * the GNU Lesser General Public License (LGPL). + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "codac2_Index.h" +#include "codac2_Matrix.h" +#include "codac2_Vector.h" +#include "codac2_Row.h" +#include "codac2_IntervalRow.h" +#include "codac2_IntervalVector.h" +#include "codac2_IntervalMatrix.h" +#include "codac2_inversion.h" +#include "codac2_clp.h" +#include "codac2_Polytope.h" +#include "codac2_Facet.h" + +using namespace codac2; + + +namespace codac2 { + +Polytope::Polytope() : _dim(-1), _nbEqcsts(0), _nbineqCsts(0), _empty(true) +{ +} + +Polytope::Polytope(Index dim) : Polytope(dim,false) +{ +} + +Polytope::Polytope(Index dim, bool empty) : _dim(dim), _nbEqcsts(0), _nbineqCsts(0), _empty(empty) +{ +} + +void Polytope::build_clpForm_from_box() { + _nbEqcsts=0; + _nbineqCsts=0; + _dim=_box.size(); + std::vector built; + for (Index i=0;i<_box.size();i++) { + Row r = Row::zero(_box.size()); + r[i] = 1.0; + if (_box[i].is_degenerated()) { + _nbEqcsts++; + built.emplace_back(r,_box[i].mid(),true); + } + else { + if (_box[i].ub()-oo) { + _nbineqCsts++; + built.emplace_back(r,_box[i].ub(),false); + } + } + } + _clpForm=std::make_unique(_box.size(),built); +} + +Polytope::Polytope(const IntervalVector &box) : Polytope(box.size()) +{ + if (box.is_empty()) { _empty=true; return; } + _box=box; + this->build_clpForm_from_box(); +} + +Polytope::Polytope(const Polytope &P) : _dim(P._dim), _nbEqcsts(P._nbEqcsts), + _nbineqCsts(P._nbineqCsts), _empty(P._empty), + _box(P._box) { + if (P._clpForm) this->_clpForm = std::make_unique(*P._clpForm); + +} + +Polytope::Polytope(const std::vector &vertices) : + Polytope() +{ + /* note : for now we just take the bounding box of the vertices, + so that's definitely not good */ + if (vertices.empty()) return; + _empty= false; + _box = *(vertices.begin()); + for (auto v : vertices) { + _box |= v; + } + this->build_clpForm_from_box(); +} + +Polytope::Polytope(const std::vector &vertices, + const std::vector &facetforms) + : _dim(facetforms[0].size()), + _nbEqcsts(0), _nbineqCsts(facetforms.size()), + _empty(vertices.empty()) { + if (vertices.empty()) { _nbineqCsts=0; return; } + _box = *(vertices.begin()); + for (auto v : vertices) { + _box |= v; + } + std::vector facets; + for (const auto &row : facetforms) { + IntervalRow rI(row); /* use intervals to ensure upward bound */ + double bnd = -oo; + for (const auto &vtx : vertices) { + bnd = std::max(bnd,rI.dot(vtx).ub()); + } + facets.emplace_back(row,bnd,false); + } + _clpForm=std::make_unique(_dim,facets,_box); +} + + +Polytope::Polytope(const IntervalVector &box, + const std::vector &facets, bool minimize) : Polytope(box.size()) { + _box=box; + _clpForm = std::make_unique(_box.size(), facets); + if (minimize) this->minimize_constraints(); +} + +Polytope::Polytope(const IntervalMatrix &M, const IntervalMatrix &rM, + const IntervalVector &V) : + Polytope(V.size(),V.is_empty()) { + if (V.is_empty()) return; + /* rM x \in V */ + _box = M*V; + std::vector built; + for (Index i=0;i<_dim;i++) { + IntervalRow rowI = rM.row(i); + if (rowI.is_unbounded()) continue; + Row rowIm = rowI.mid(); + Interval rhs = V[i] - (rowI-rowIm)*_box; + if (rhs.is_degenerated()) { + _nbEqcsts++; + built.emplace_back(rowIm,rhs.mid(),true); + } else { + if (rhs.ub()-oo) { + _nbineqCsts++; + built.emplace_back(-rowIm,-rhs.lb(),false); + } + + } + } +} + +Polytope::Polytope(const IntervalMatrix &M, const IntervalVector &V) : + Polytope(M,inverse_enclosure(M),V) { +} + +Polytope::~Polytope() { +} + +void Polytope::minimize_constraints() { /* TODO */ } + +const IntervalVector& Polytope::update_box() { + if (_dim==-1 || _empty) return this->_box; + if (!_clpForm) return this->_box; + Row obj = Row::zero(_dim); + for (Index i=0;i<_dim;i++) { + obj[i]=1.0; + _clpForm->setObjective(obj); + LPclp::lp_result_stat ret = _clpForm->solve(false,4); + if (ret[LPclp::EMPTY]) { _empty=true; _box.set_empty(); return _box; } + if (ret[LPclp::BOUNDED]) _box[i] = min(_box[i],_clpForm->getValobj()); + obj[i]=-1.0; + _clpForm->setObjective(obj); + ret = _clpForm->solve(false,4); + if (ret[LPclp::EMPTY]) { _empty=true; _box.set_empty(); return _box; } + if (ret[LPclp::BOUNDED]) _box[i] = max(_box[i],-_clpForm->getValobj()); + } + _clpForm->set_bbox(_box); + return this->_box; +} + +polytope_inclrel Polytope::relation_Box(const IntervalVector& p) const { + assert_release(_dim>=0); + if (p.is_empty()) return (1<& facets = _clpForm->getFacets(); + for (const Facet &facet : facets) { + polytope_inclrel res2 = facet.relation_Box(p); + if (res2[DISJOINT]) return (1<relation_Box(p); + return (r[INCLUDES] ? BoolInterval::TRUE : + (r[NOTINCLUDE] ? BoolInterval::FALSE : BoolInterval::UNKNOWN)); +} + +BoolInterval Polytope::intersects(const IntervalVector& p) const { + polytope_inclrel r = this->relation_Box(p); + return (r[INTERSECTS] ? BoolInterval::TRUE : + (r[DISJOINT] ? BoolInterval::FALSE : BoolInterval::UNKNOWN)); +} + +const Interval& Polytope::operator[](Index i) const { + assert_release(i<=_dim); + return _box[i]; +} + +Vector Polytope::mid() const { + assert_release(_dim>=0); + if (_empty) return + Vector::Constant(_dim,std::numeric_limits::quiet_NaN()); + if (!_clpForm) return _box.mid(); + LPclp::lp_result_stat stat = _clpForm->solve(true,0); + if (stat[LPclp::NOTEMPTY] || stat[LPclp::NOTEMPTY_APPROX]) + return _clpForm->getFeasiblePoint().mid(); + return Vector::Constant(_dim,std::numeric_limits::quiet_NaN()); +} + +double Polytope::distance_cst(const Facet &fc) const { + assert_release(_dim==fc.row.size()); + if (_empty) return -oo; + if (!_clpForm) return (fc.row.dot(_box)-fc.rhs).ub(); + _clpForm->setObjective(fc.row); + LPclp::lp_result_stat stat = _clpForm->solve(false,0); + if (stat[LPclp::EMPTY]) return -oo; + return (_clpForm->getValobj()-fc.rhs).ub(); +} + +bool Polytope::box_is_included(const IntervalVector& x) const { + return _box.is_subset(x); +} + +void Polytope::set_empty() { + _empty=true; + _box.set_empty(); + _nbEqcsts=_nbineqCsts=0; + _clpForm.reset(nullptr); +} + +void Polytope::clear() { + assert_release(_dim>=0); + _empty=false; + _box = IntervalVector::Zero(_dim); + this->build_clpForm_from_box(); +} + +} + diff --git a/src/extensions/polytope/codac2_Polytope.h b/src/extensions/polytope/codac2_Polytope.h new file mode 100644 index 000000000..5f119f96c --- /dev/null +++ b/src/extensions/polytope/codac2_Polytope.h @@ -0,0 +1,295 @@ +/** + * \file codac2_Polytope.h + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2025 + * \license This program is distributed under the terms of + * the GNU Lesser General Public License (LGPL). + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "codac2_Index.h" +#include "codac2_Matrix.h" +#include "codac2_Vector.h" +#include "codac2_Row.h" +#include "codac2_IntervalRow.h" +#include "codac2_IntervalVector.h" +#include "codac2_IntervalMatrix.h" +#include "codac2_BoolInterval.h" +#include "codac2_clp.h" + + +namespace codac2 { + + /** + * \class Polytope + * \brief Represents a bounded convex polytope as a set of constraints + * and a bounding box (the bounding box may be, or not, inside + * the constraints) */ + class Polytope + { + public: + /** + * \brief basic constuctor + */ + Polytope(); + + /** + * \brief basic constuctor with a dimension, full + */ + Polytope(Index dim); + + /** + * \brief basic constuctor with a dimension, empty or full + */ + Polytope(Index dim, bool empty); + + /** + * \brief Constructs a bounded polytope representing a box + * from a bounded interval vector + */ + Polytope(const IntervalVector &box); + + /** + * \brief Copy (no minimization) + */ + Polytope(const Polytope &P); + + /** + * \brief Constructs a polytope from a set of vertices + * for now, just take the bounding box of the vertices + */ + Polytope(const std::vector &vertices); + + /** + * \brief Constructs a polytope from a set of vertices + * and a set of linear forms, i.e. the minimal polytope + * built on the linear forms that includes the vertices. + * \param vertices the vertices + * \param facetforms the facets description (must NOT be empty) + */ + Polytope(const std::vector &vertices, + const std::vector &facetforms); + + /** + * \brief from a slanted box (M.V with reverse known) + * \param M matrix + * \param rM inverse matrix + * \param V vector + */ + Polytope(const IntervalMatrix& M, const IntervalMatrix &rM, + const IntervalVector &V); + + /** + * \brief from a slanted box (M.V, reverse computed) + * \param M matrix + * \param V vector + */ + Polytope(const IntervalMatrix& M, const IntervalVector &V); + + /** + * \brief Constructs a polytope from a bounding box and + * a set of constraints + */ + Polytope(const IntervalVector &box, + const std::vector &facets, + bool minimize=false); + + /** + * \brief Destructor + */ + ~Polytope(); + + /***************** ACCESS **************************/ + /** + * \brief Get the dimension of the space + * + * \return the dimension of the space + */ + Index dim() const; + Index size() const; + + /** + * \brief Get the dimension of the polytope (ie the dimension + * of the space minus the equalities). -1 means that the polytope + * is empty. + * + * \return the dimension of the polytope. + */ +// Index dim_polytope() const; + + /** + * \brief Get the number of constraints + * + * \return the number of facets (including equalities) + */ + Index nbFacets() const; + + /** + * \brief Get the number of equality constraints + * + * \return the number of equalities + */ + Index nbEqFacets() const; + + /** + * \brief is empty + */ + bool is_empty() const; + + /** + * \brief has a flat dimension + */ + bool is_flat() const; + + /** \brief ``component'' : interval of the bounding box + * (only const !!!) */ + const Interval& operator[](Index i) const; + + /** + * ``middle'' (vector inside the polytope, if possible) + */ + Vector mid() const; + + /** \brief ``distance'' from the satisfaction of a constraint + * ie the inflation of the rhs needed to satisfy the constraint + * \param fc the constraint + * \return the distance + */ + double distance_cst (const Facet &fc) const; + + + /** + * \brief relationships with a box (fast check) + * \param p the box + * \return polytope_inclrel checking inclusion and intersection */ + polytope_inclrel relation_Box(const IntervalVector& p) const; + + /** + * \brief Checks whether the polytope contains a given point, or + * includes a box + * + * \param p The point or box to check, enclosed in an IntervalVector. + * \return BoolInterval indicating possible containment. + */ + BoolInterval contains(const IntervalVector& p) const; + + /** \brief intersects a box + * \param x the box (IntervalVector) + * \return if the polytope intersects the box + */ + BoolInterval intersects(const IntervalVector& x) const; + + /** \brief intersects a polytope + * \param x the polytope + * \return if the polytope intersects the box + */ + BoolInterval intersects(const Polytope &p) const; + + /************* Box access *************************/ + + /** + * \brief get the current bounding box of the polytope + * (note: it may not be tightest bounding box, it is the + * bounding box used for approximations results) + * + * \return The current bounding box + */ + const IntervalVector &box() const; + + /** + * \brief compute and update the bounding box + * (using LPs on the polytope) + * + * \return the updated hull box + */ + const IntervalVector &update_box(); + + /** + * \brief test if bounding box is included + * (mainly useful if current bounding box is tight) + */ + bool box_is_included(const IntervalVector& x) const; + + + /************* Modification **********************/ + /** modification creates a new polytope **/ + + /** \brief set to empty */ + void set_empty(); + + /** \brief set to (singleton) 0 */ + void clear(); + + /** \brief inflation by a cube + * this <- this + [-rad,rad]^d + * \param rad radius of the box + * \return the + */ + Polytope inflate(double rad); + + /** \brief inflation by a ball while keeping the set of facets + * this <- this + B_d(rad) + * \param rad radius of the ball + * \return *this + */ + Polytope inflate_ball(double rad); + + /** \brief expansion of a dimension (flat or not) + * \param dm index of the dimension + * \param rad radius + * \return *this */ + Polytope unflat(Index dm, double rad); + + /** \brief centered homothety + * x <- [c] + delta*([x]-[c]) ( (1-delta)[c] + delta*[x] ) + * \param c ``center'' + * \param delta expansion + * \return this + */ + Polytope homothety(IntervalVector c, double delta); + + + /** + * \brief Computes the sets of vertices of the polytope + * + * WARNING: the results is unsafe: no guarantee is given + * that the convex hull of the result fully encloses the polytope + * works only for bounded polyhedron, suppose _box exists and is bounded + * + * \return set of vertices, as Vectors + */ + std::vector vertices() const; + + private: + Index _dim; /* dimension */ + Index _nbEqcsts; /* nb of equality constraints */ + Index _nbineqCsts; /* nb of inequality constraints */ + bool _empty; /* is empty */ + IntervalVector _box; /* bounding box */ + mutable std::unique_ptr _clpForm; /* LPclp formulation */ + void build_clpForm_from_box(); + void minimize_constraints(); +}; + +inline const IntervalVector &Polytope::box() const { return _box; } +inline Index Polytope::dim() const { return _dim; } +inline Index Polytope::size() const { return _dim; } +inline bool Polytope::is_empty() const { return _empty; } +inline bool Polytope::is_flat() const { return _nbEqcsts!=0; } +inline Index Polytope::nbFacets() const { return _nbEqcsts!=0; } + + +} + diff --git a/src/extensions/polytope/codac2_dd.cpp b/src/extensions/polytope/codac2_dd.cpp new file mode 100644 index 000000000..38786dd0c --- /dev/null +++ b/src/extensions/polytope/codac2_dd.cpp @@ -0,0 +1,578 @@ +/** + * \file codac2_dd.h implementation of the dd algorithm + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2025 + * \license This program is distributed under the terms of + * the GNU Lesser General Public License (LGPL). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "codac2_Index.h" +#include "codac2_Vector.h" +#include "codac2_Row.h" +#include "codac2_Facet.h" +#include "codac2_dd.h" + +#undef DEBUG_CODAC2_DD + +namespace codac2 { + +DDbuildF2V::DDbuildF2V(const IntervalVector &box) : firstIn(-1) { + assert_release(!box.is_unbounded()); + /* we use "negative" number for the box constraints : + one for each negative bounds (-s to -1), one for all positive bounds + (-s-1) */ + std::set links; + std::vector fcts; + for (Index i=-box.size();i<0;i++) { + fcts.push_back(i); + } + for (Index i=1;i<=box.size();i++) { + links.insert(i); + } + IntervalVector l = box.lb(); + this->addDDvertex(l,links,fcts); + Interval sum = box.sum(); + for (Index i=0;iaddDDvertex(l,links,fcts); + } +} + +Index DDbuildF2V::addDDvertex(const IntervalVector &v, + std::set &links, std::vector &fcts) { + nbIn++; + vertices.emplace(nbIn, + DDvertex(v,nbIn,links,vtx)); + return nbIn; +} +Index DDbuildF2V::addDDvertex(IntervalVector &&v, + std::set &links, std::vector &fcts) { + nbIn++; + vertices.emplace(nbIn, + DDvertex(v,nbIn,links,vtx)); + return nbIn; +} + +/* p1 has lambda>0, p2 has lambda<0 ; + p1 is outside the new constraint, p2 is inside. + For inequalities, we need to get "as much" p1 as possible, + that is maximize |lambda2| and minimize |lambda1| */ +Index DDbuildF2V::addVertexSon(const DDvertex &p1, DDvertex &p2, + Index idFacet, bool isEq) { + Interval ratio2=1.0/(1.0-p1.lambda/p2.lambda); /* maximize ratio */ + IntervalVector newV = ratio2.ub()*p1.vertex + + (Interval(1.0)-ratio2.ub())*p2.vertex; + /* computation of facets */ + std::vector nfcts; + Index a1=0; Index a2=0; + bool idFacetAdded=false; + while (a1<(Index)p1.fcts.size() && a2<(Index)p2.fcts.size()) { + if (p1.fcts[a1]idFacet && !idFacetAdded) { + nfcts.push_back(idFacet); + idFacetAdded=true; + } + nfcts.push_back(p1.fcts[a1]); + a1++; a2++; + } + } + if (!idFacetAdded) nfcts.push_back(idFacet); + std::vector lnks; + if (!isEq) lnks.push_back(p2.Id); + Index newId=this->addDDvertex(newV,nfcts,lnks); + if (!isEq) p2.replaceLnks(p1.Id,newId); + return newId; +} + + +struct PairMaxSets { + Index a,b; + std::vector elems; + + PairMaxSets() {} + + PairMaxSets(Index a, Index b, const std::vector e1, const std::vector e2) : a(a), b(b) { + Index i=0,j=0; + while(i<(Index)e1.size() && j<(Index)e2.size()) { + if (e1[i]==e2[j]) { + elems.push_back(e1[i]); + i++; j++; + } else if (e1[i]a,p2.a); + std::swap(this->b,p2.b); + this->elems.swap(p2.elems); + } + + int comp(const PairMaxSets &pm) const { + /* -1 if elems included in pm.elems; 0 if ==, + +1 elems includes pm.elems, +2 if not comparable */ + const std::vector &e1=elems; + const std::vector &e2=pm.elems; + std::vector::size_type i=0,j=0; + bool leq=(e1.size()<=e2.size()), geq=(e1.size()>=e2.size()); + /* leq=F,geq=T => +1 + leq=F,geq=F => 2 + leq=T,geq=F => -1 + leq=T,geq=T => 0 */ + while(ie2[j]) { + if (!leq) return 2; + geq=false; j++; + } else { i++; j++; } + } + if (iaddVertexSon(d,d2,idFacet,fct.eqcst); + cnt++; + if (fNew==-1) fNew=newId; + } + this->removeVertex(d.Id,false); + } + /* elimination of old vertices */ + i = firstIn; + while (i!=fNew) { + DDvertex &d = vertices[i]; + i = d.nextId; + if (fct.eqcst && d.status==DDvertex::VERTEXLT) { + this->removeVertex(d.Id,true); + } + } + /*construction of new links for the eq vertices */ + i = firstIn; + std::vector maxset; + while (i!=-1) { + DDvertex &d = vertices[i]; + i = d.nextId; + if (d.status==DDvertex::VERTEXEQ || d.status==DDvertex::VERTEXNEW) { + Index j=i; + while (j!=-1) { + DDvertex &d2 = vertices[j]; + j=d2.nextId; + if (d2.status==DDvertex::VERTEXEQ || d2.status==DDvertex::VERTEXNEW) { + PairMaxSets p(d.Id,d2.Id,d.fcts,d2.fcts); + PairMaxSets &actuel = p; + bool placed=false; + unsigned long int k=0, l=0; + while (k nvtx; + Index a1=0; Index a2=0; + bool idVertexAdded=false; + while (a1<(Index)p1.vtx.size() && a2<(Index)p2.vtx.size()) { + if (p1.vtx[a1]idVertex && !idVertexAdded) { + nvtx.push_back(idVertex); + idVertexAdded=true; + } + nvtx.push_back(p1.vtx[a1]); + a1++; a2++; + } + } + if (!idVertexAdded) nvtx.push_back(idVertex); + std::set lnks; + lnks.insert(p2.Id); + Index newId=this->addDDfacet(Facet(newV,newRhs,false),lnks,nvtx); + p2.replaceLnks(p1.Id,newId); + return newId; +} + +int DDbuildV2F::add_vertex(Index idVertex, const Vector& vertex) { + return this->add_vertex(idVertex,IntervalVector(vertex)); +} + +int DDbuildV2F::add_vertex(Index idVertex, const IntervalVector& vertex) { + int cnt=0; + /* first check equalities */ + if (!eqfacets.empty()) { + Index eqViolated=-1; + Interval delta; + for (Index i=eqfacets.size()-1;i>=0;i--) { + Interval calc = eqfacets[i].row.dot(vertex)-eqfacets[i].rhs; + if (calc.mig()tolerance) continue; + if (eqViolated==-1) { eqViolated=i; delta=calc; continue; } + else { + double correction = -(calc/delta).mid(); + eqfacets[i].row += correction*eqfacets[eqViolated].row; + eqfacets[i].rhs += correction*eqfacets[eqViolated].rhs; + } + } + if (eqViolated!=-1) { + if (facets.empty()) { + this->nbIn=0; + std::set lnk = { 2 }; + std::vector vtx = { idVertex }; + if (delta.lb()>0.0) { + this->addDDfacet(Facet(eqfacets[eqViolated].row, + eqfacets[eqViolated].row.dot(vertex).ub(),false), + lnk,vtx); + vtx = idVertices; + lnk = { 1 }; + this->addDDfacet(Facet(-eqfacets[eqViolated].row, + -eqfacets[eqViolated].rhs,false), + lnk,vtx); + } else { + this->addDDfacet(Facet(-eqfacets[eqViolated].row, + (-eqfacets[eqViolated].row.dot(vertex)).ub(),false), + lnk,vtx); + vtx = idVertices; + lnk = { 1 }; + this->addDDfacet(Facet(eqfacets[eqViolated].row, + eqfacets[eqViolated].rhs,false), + lnk,vtx); + } + cnt=2; + } else { + /* add the remaining inequality, linked with all existing facets */ + std::set lnk; + for (std::pair &f : facets) { + f.second.status=DDfacet::FACETOUT; /* to remove FACETNEW */ + lnk.insert(f.first); + } + Index u; + std::vector vtx = idVertices; + if (delta.lb()>0.0) { + u = this->addDDfacet(Facet(-eqfacets[eqViolated].row, + -eqfacets[eqViolated].rhs,false),lnk,vtx); + } else { + u = this->addDDfacet(Facet(eqfacets[eqViolated].row, + eqfacets[eqViolated].rhs,false),lnk,vtx); + } + /* change all existing facets to take the new vertex into account */ + + for (std::pair &f : facets) { + if (f.second.status!=DDfacet::FACETOUT) continue; + Interval q = f.second.facet.rhs- + f.second.facet.row.dot(vertex); + double correction = (q/delta).mid(); + f.second.facet.row += correction*eqfacets[eqViolated].row; + f.second.facet.rhs += correction*eqfacets[eqViolated].rhs; + f.second.addLnk(u); + f.second.addVtx(idVertex); + } + cnt=1; + } + if (eqViolated!=(Index)eqfacets.size()-1) { + eqfacets[eqViolated]=eqfacets[eqfacets.size()-1]; + } + eqfacets.pop_back(); + if (!eqfacets.empty()) idVertices.push_back(idVertex); + return cnt; + } else idVertices.push_back(idVertex); /* and follows */ + } + /* no equalities removed */ + for (std::pair &f : facets) { + DDfacet &d = f.second; + Interval calc = d.facet.row.dot(vertex)-d.facet.rhs; + d.lambda = calc.lb(); + if (calc.mig()tolerance) { d.status=DDfacet::FACETON; } + else if (calc.lb()>0.0) { d.status=DDfacet::FACETOUT; } + else { d.status=DDfacet::FACETIN; } + } + Index endF = nbIn; + for (std::map::iterator it = facets.begin(); + it!=facets.end() && it->first<=endF;) { + DDfacet &d = it->second; + ++it; + if (d.status==DDfacet::FACETON) { + d.addVtx(idVertex); + continue; + } + if (d.status==DDfacet::FACETIN || d.status==DDfacet::FACETNEW) continue; + if (d.links.empty()) { /* not supposed to happen */ + /* very specific case of a 1-dimension polyhedra */ + d.vtx.clear(); + d.vtx.push_back(idVertex); + d.facet.rhs = d.facet.row.dot(vertex).ub(); + continue; + } + for (Index idL : d.links) { + auto it2 = facets.find(idL); + if (it2==facets.end()) continue; + DDfacet &d2 = it2->second; + if (d2.status==DDfacet::FACETON) { + d2.removeLnk(d.Id); + continue; + } + if (d2.status!=DDfacet::FACETIN) { + continue; /* do nothing for out */ + } +#ifdef CODAC2_DD + std::cout << " addFacet " << d.Id << " " << d2.Id << " " << d.status << " " << d2.status << std::endl; + std::cout << " (" << d.lambda << " " << d2.lambda << ") " << std::endl; +#endif +#ifdef CODAC2_DD + Index newid = this->addFacetSon(d,d2,idVertex); + std::cout << " ... done " << newid <addFacetSon(d,d2,idVertex); +#endif + cnt++; + } + this->removeFacet(d.Id,false); + + } + /*construction of new links for the eq facets */ + std::vector maxset; + for (std::map::iterator it = facets.begin(); + it!=facets.end();++it) { + DDfacet &d = it->second; + if (d.status==DDfacet::FACETON || d.status==DDfacet::FACETNEW) { + for (std::map::iterator it2 = next(it); + it2!=facets.end(); ++it2) { + DDfacet &d2 = it2->second; + if (d2.status==DDfacet::FACETON || d2.status==DDfacet::FACETNEW) { + PairMaxSets actuel(d.Id,d2.Id,d.vtx,d2.vtx); + bool placed=false; + unsigned long int k=0, l=0; + while (k &links, + std::vector &vtx) { + + nbIn++; + facets.emplace(nbIn, + DDfacet(f,nbIn,links,vtx)); + return nbIn; +} + +Index DDbuildV2F::addDDfacet(Facet &&f, std::set &links, + std::vector &vtx) { + + nbIn++; + facets.emplace(nbIn, + DDfacet(f,nbIn,links,vtx)); + return nbIn; +} + +void DDbuildV2F::removeFacet(Index Id, bool removeLnks) { + DDfacet &d = facets.at(Id); + if (removeLnks) { + for (Index l : d.links) { + auto it = facets.find(l); + if (it==facets.end()) continue; + it->second.removeLnk(Id); + } + } + this->facets.erase(Id); +} + +std::ostream& operator<<(std::ostream& os, const DDbuildV2F& build) { + os << "DDbuildV2F (" << build.eqfacets.size() << " eq, " << + build.facets.size() << " ineq) : " << std::endl; + for (const Facet &eqf : build.eqfacets) { + os << eqf.row << " = " << eqf.rhs << std::endl; + } + for (const auto &ifacet : build.facets) { + auto &ieqf = ifacet.second; + os << ieqf.Id << " : " << ieqf.facet.row << " <= " << ieqf.facet.rhs << std::endl; + os << " vtx: ("; + for (Index a : ieqf.vtx) { + os << a << ","; + } + os << ")" << std::endl; + os << " lnks: ("; + for (Index a : ieqf.links) { + os << a << ","; + } + os << ")" << std::endl; + } + os << "endDDbuildV2F" << std::endl; + return os; +} + +} + diff --git a/src/extensions/polytope/codac2_dd.h b/src/extensions/polytope/codac2_dd.h new file mode 100644 index 000000000..769ee2ff1 --- /dev/null +++ b/src/extensions/polytope/codac2_dd.h @@ -0,0 +1,182 @@ +/** + * \file codac2_dd.h implementation of the dd algorithm + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2025 + * \license This program is distributed under the terms of + * the GNU Lesser General Public License (LGPL). + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "codac2_Index.h" +#include "codac2_Vector.h" +#include "codac2_Row.h" +#include "codac2_Facet.h" + + +namespace codac2 { + +struct DDvertex { + Index Id; + IntervalVector vertex; + Interval lambda; + + enum ddvertexstatus { + VERTEXNEW, + VERTEXLT, + VERTEXGT, + VERTEXEQ, + }; + ddvertexstatus status; + + std::set links; + std::vector fcts; + + DDvertex(const IntervalVector &v, Index Id, + std::set &nlinks, + std::vector &nfcts) : Id(Id), + vertex(v), status(VERTEXNEW) { + this->links.swap(nlinks); + this->fcts.swap(nfcts); + } + DDvertex(IntervalVector &&v, Index Id, + std::set &nlinks, + std::vector &nfcts) : Id(Id), + vertex(v), status(VERTEXNEW) { + this->links.swap(nlinks); + this->fcts.swap(nfcts); + } + + void replaceLnks(Index old, Index nw) { + this->links.erase(old); + this->links.insert(nw); + } + + void addLnk(Index a) { + this->links.insert(a); + } + + void addFct(Index a) { + auto it = this->fcts.begin(); + while (it != this->fcts.end() && (*it)fcts.end() && (*it)==a) return; + this->fcts.insert(it,a); + } + + void removeLnk(Index a) { + this->links.erase(a); + } +}; + +struct DDfacet { + Index Id; + Facet facet; + double lambda; + + enum ddfacetstatus { + FACETNEW, + FACETIN, + FACETON, + FACETOUT + }; + ddfacetstatus status; + + std::set links; + std::vector vtx; + + DDfacet(const Facet &v, Index Id, + std::set &nlinks, + std::vector &nvtx) : Id(Id), facet(v), status(FACETNEW) { + this->links.swap(nlinks); + this->vtx.swap(nvtx); + } + DDfacet(Facet &&v, Index Id, + std::set &nlinks, + std::vector &nvtx) : Id(Id), facet(v), status(FACETNEW) { + this->links.swap(nlinks); + this->vtx.swap(nvtx); + } + + void replaceLnks(Index old, Index nw) { + this->links.erase(old); + this->links.insert(nw); + } + + void addLnk(Index a) { + this->links.insert(a); + } + + void addVtx(Index a) { + auto it = this->vtx.begin(); + while (it != this->vtx.end() && (*it)vtx.end() && (*it)==a) return; + this->vtx.insert(it,a); + } + + void removeLnk(Index a) { + this->links.erase(a); + } +}; + +class DDbuildF2V { + public: + DDbuildF2V(const IntervalVector &box); + + int add_facet(Index idFacet, const Facet& fct); + + friend std::ostream& operator<<(std::ostream& os, const DDbuildF2V& build); + + private: + Index addDDvertex(const IntervalVector &v, std::set &links, + std::vector &fcts); + Index addDDvertex(IntervalVector &&v, std::set &links, + std::vector &fcts); + Index addVertexSon(const DDvertex &p1, DDvertex &p2, + Index idFacet, bool isEq); + void removeVertex(Index Id, bool removeLnks); + + std::map vertices; + Index nbIn; + double tolerance = 1e-9; +}; + +class DDbuildV2F { + public: + DDbuildV2F(Index idVertex, const Vector &vertex); + + int add_vertex(Index idVertex, const Vector& vertex); + int add_vertex(Index idVertex, const IntervalVector& vertex); + + friend std::ostream& operator<<(std::ostream& os, const DDbuildV2F& build); + + private: + Index addDDfacet(const Facet &f, std::set &links, + std::vector &vtx); + Index addDDfacet(Facet &&f, std::set &links, + std::vector &vtx); + Index addFacetSon(const DDfacet &p1, DDfacet &p2, Index idVertex); + void removeFacet(Index Id, bool removeLnks); + + std::map facets; + std::vector eqfacets; /* no need to 'link' eqfacets */ + std::vector idVertices; /* list of vertices entered, useful as + long as there are eqfacets */ + Index nbIn=0; + double tolerance =1e-9; +}; + +} + From 9fb8b3a7076db7935fdab4d8ae6a105bf2ac2ce5 Mon Sep 17 00:00:00 2001 From: damien-masse Date: Wed, 1 Oct 2025 08:33:13 +0200 Subject: [PATCH 03/26] followup --- src/CMakeLists.txt | 7 - src/extensions/polytope/CMakeLists.txt | 1 + src/extensions/polytope/codac2_Facet.cpp | 85 +++++ src/extensions/polytope/codac2_Facet.h | 48 +-- src/extensions/polytope/codac2_dd.cpp | 409 +++++++++++++++-------- src/extensions/polytope/codac2_dd.h | 60 +++- 6 files changed, 416 insertions(+), 194 deletions(-) create mode 100644 src/extensions/polytope/codac2_Facet.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7cd6ca8e7..628fad0ca 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,10 +18,6 @@ add_subdirectory(extensions/polytope/) endif() - #if(WITH_IBEX) # included by default for now - add_subdirectory(extensions/ibex) - #endif() - #if(WITH_PYTHON) # add_subdirectory(sympy) #endif() @@ -123,7 +119,6 @@ endif() -<<<<<<< HEAD if(WITH_POLYTOPE) file(APPEND ${CODAC_CMAKE_CONFIG_FILE} " @@ -162,8 +157,6 @@ #endif() -======= ->>>>>>> 93a76fa4ad5b3f67ca14383e60f6f8a9ea3d0efc file(APPEND ${CODAC_CMAKE_CONFIG_FILE} " set(CODAC_LIBRARIES \${CODAC_LIBRARIES} \${CODAC_GRAPHICS_LIBRARY} \${CODAC_CORE_LIBRARY}) ") diff --git a/src/extensions/polytope/CMakeLists.txt b/src/extensions/polytope/CMakeLists.txt index c39dc892c..8b1ff8b7b 100644 --- a/src/extensions/polytope/CMakeLists.txt +++ b/src/extensions/polytope/CMakeLists.txt @@ -7,6 +7,7 @@ list(APPEND CODAC_POLYTOPE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/clp/codac2_clp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/clp/codac2_clp.h ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Facet.h + ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Facet.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codac2_dd.h ${CMAKE_CURRENT_SOURCE_DIR}/codac2_dd.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Polytope.h diff --git a/src/extensions/polytope/codac2_Facet.cpp b/src/extensions/polytope/codac2_Facet.cpp new file mode 100644 index 000000000..aff74eb97 --- /dev/null +++ b/src/extensions/polytope/codac2_Facet.cpp @@ -0,0 +1,85 @@ +/** + * \file codac2_Facet.h + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2025 + * \license This program is distributed under the terms of + * the GNU Lesser General Public License (LGPL). + */ + + +#include /* uses swap */ + +#include "codac2_Index.h" +#include "codac2_Vector.h" +#include "codac2_Row.h" +#include "codac2_Interval.h" +#include "codac2_IntervalVector.h" +#include "codac2_IntervalRow.h" +#include "codac2_BoolInterval.h" +#include "codac2_Facet.h" + +namespace codac2 { + +polytope_inclrel Facet::relation_Box(const IntervalVector &b) const { + if (b.is_empty()) return inclrel_includes | inclrel_disjoint; + IntervalVector a(b); + /* check the vertex that maximizes row */ + for (Index i=0;i0) a[i]=a[i].ub(); else a[i]=a[i].lb(); + } + Interval maxv = row.dot(a)-rhs; + polytope_inclrel r1=0; + if (maxv.ub()<=0.0) { + if (!eqcst) return inclrel_includes | inclrel_intersects; + else if (maxv.ub()<0.0) return inclrel_notinclude | inclrel_disjoint; + r1 = inclrel_includes; + } else if (maxv.lb()<=0.0) { + r1 = inclrel_mayinclude; + } else { + r1 = inclrel_notinclude; + } + /* check the vertex that minimizes row */ + for (Index i=0;i0) a[i]=b[i].lb(); else a[i]=b[i].ub(); + } + Interval minv = row.dot(a)-rhs; + if (minv.lb()>0.0) return inclrel_notinclude | inclrel_disjoint; + if (!eqcst) { + if (minv.ub()>0.0) return r1 | inclrel_mayintersect; + return r1 | inclrel_intersects; + } else { + if (r1[INCLUDES]) { /* maxv.ub == 0 */ + if (minv.lb()>=0.0) return + inclrel_includes | inclrel_intersects; + return (maxv.lb()==0.0 ? inclrel_intersects : inclrel_mayintersect) + | (minv.ub()<0.0 ? inclrel_notinclude : inclrel_mayinclude); + } + if (minv.ub()<0.0) r1 = inclrel_notinclude; + if (maxv.lb()>=0.0 && minv.ub()<=0.0) + return r1 | inclrel_intersects; + else return r1 | inclrel_mayintersect; + } +} + +void Facet::contract_Box(IntervalVector &b) const { + /* use MulOp:bwd */ + IntervalRow x1(this->row); + MulOp::bwd(Interval(-oo,this->rhs),x1,b); + if (b[0].is_empty()) b.set_empty(); +} + +void Facet::contract_out_Box(IntervalVector &b) const { + /* use MulOp:bwd */ + IntervalRow x1(this->row); + MulOp::bwd(Interval(this->rhs,oo),x1,b); + if (b[0].is_empty()) b.set_empty(); +} + +std::ostream& operator<<(std::ostream& os, const Facet& f) { + os << f.row << (f.eqcst ? "=" : "<=" ) << f.rhs; + return os; +} + +} diff --git a/src/extensions/polytope/codac2_Facet.h b/src/extensions/polytope/codac2_Facet.h index b293f06dd..e2be1a455 100644 --- a/src/extensions/polytope/codac2_Facet.h +++ b/src/extensions/polytope/codac2_Facet.h @@ -59,53 +59,19 @@ struct Facet { Facet() {} - void swap(Facet &f) { + inline void swap(Facet &f) { this->row.swap(f.row); std::swap(this->rhs,f.rhs); std::swap(this->eqcst,f.eqcst); } /* test only includes and intersects */ - polytope_inclrel relation_Box(const IntervalVector &b) const { - if (b.is_empty()) return inclrel_includes | inclrel_disjoint; - IntervalVector a(b); - /* check the vertex that maximizes row */ - for (Index i=0;i0) a[i]=a[i].ub(); else a[i]=a[i].lb(); - } - Interval maxv = row.dot(a)-rhs; - polytope_inclrel r1=0; - if (maxv.ub()<=0.0) { - if (!eqcst) return inclrel_includes | inclrel_intersects; - else if (maxv.ub()<0.0) return inclrel_notinclude | inclrel_disjoint; - r1 = inclrel_includes; - } else if (maxv.lb()<=0.0) { - r1 = inclrel_mayinclude; - } else { - r1 = inclrel_notinclude; - } - /* check the vertex that minimizes row */ - for (Index i=0;i0) a[i]=b[i].lb(); else a[i]=b[i].ub(); - } - Interval minv = row.dot(a)-rhs; - if (minv.lb()>0.0) return inclrel_notinclude | inclrel_disjoint; - if (!eqcst) { - if (minv.ub()>0.0) return r1 | inclrel_mayintersect; - return r1 | inclrel_intersects; - } else { - if (r1[INCLUDES]) { /* maxv.ub == 0 */ - if (minv.lb()>=0.0) return - inclrel_includes | inclrel_intersects; - return (maxv.lb()==0.0 ? inclrel_intersects : inclrel_mayintersect) - | (minv.ub()<0.0 ? inclrel_notinclude : inclrel_mayinclude); - } - if (minv.ub()<0.0) r1 = inclrel_notinclude; - if (maxv.lb()>=0.0 && minv.ub()<=0.0) - return r1 | inclrel_intersects; - else return r1 | inclrel_mayintersect; - } - } + polytope_inclrel relation_Box(const IntervalVector &b) const; + + void contract_Box(IntervalVector &b) const; + void contract_out_Box(IntervalVector &b) const; + friend std::ostream& operator<<(std::ostream& os, const Facet& f); + }; diff --git a/src/extensions/polytope/codac2_dd.cpp b/src/extensions/polytope/codac2_dd.cpp index 38786dd0c..66aea2931 100644 --- a/src/extensions/polytope/codac2_dd.cpp +++ b/src/extensions/polytope/codac2_dd.cpp @@ -24,92 +24,13 @@ #include "codac2_Facet.h" #include "codac2_dd.h" -#undef DEBUG_CODAC2_DD +#define DEBUG_CODAC2_DD namespace codac2 { -DDbuildF2V::DDbuildF2V(const IntervalVector &box) : firstIn(-1) { - assert_release(!box.is_unbounded()); - /* we use "negative" number for the box constraints : - one for each negative bounds (-s to -1), one for all positive bounds - (-s-1) */ - std::set links; - std::vector fcts; - for (Index i=-box.size();i<0;i++) { - fcts.push_back(i); - } - for (Index i=1;i<=box.size();i++) { - links.insert(i); - } - IntervalVector l = box.lb(); - this->addDDvertex(l,links,fcts); - Interval sum = box.sum(); - for (Index i=0;iaddDDvertex(l,links,fcts); - } -} - -Index DDbuildF2V::addDDvertex(const IntervalVector &v, - std::set &links, std::vector &fcts) { - nbIn++; - vertices.emplace(nbIn, - DDvertex(v,nbIn,links,vtx)); - return nbIn; -} -Index DDbuildF2V::addDDvertex(IntervalVector &&v, - std::set &links, std::vector &fcts) { - nbIn++; - vertices.emplace(nbIn, - DDvertex(v,nbIn,links,vtx)); - return nbIn; -} - -/* p1 has lambda>0, p2 has lambda<0 ; - p1 is outside the new constraint, p2 is inside. - For inequalities, we need to get "as much" p1 as possible, - that is maximize |lambda2| and minimize |lambda1| */ -Index DDbuildF2V::addVertexSon(const DDvertex &p1, DDvertex &p2, - Index idFacet, bool isEq) { - Interval ratio2=1.0/(1.0-p1.lambda/p2.lambda); /* maximize ratio */ - IntervalVector newV = ratio2.ub()*p1.vertex + - (Interval(1.0)-ratio2.ub())*p2.vertex; - /* computation of facets */ - std::vector nfcts; - Index a1=0; Index a2=0; - bool idFacetAdded=false; - while (a1<(Index)p1.fcts.size() && a2<(Index)p2.fcts.size()) { - if (p1.fcts[a1]idFacet && !idFacetAdded) { - nfcts.push_back(idFacet); - idFacetAdded=true; - } - nfcts.push_back(p1.fcts[a1]); - a1++; a2++; - } - } - if (!idFacetAdded) nfcts.push_back(idFacet); - std::vector lnks; - if (!isEq) lnks.push_back(p2.Id); - Index newId=this->addDDvertex(newV,nfcts,lnks); - if (!isEq) p2.replaceLnks(p1.Id,newId); - return newId; -} - - +/*************************/ +/****** PairMaxSet *******/ +/*************************/ struct PairMaxSets { Index a,b; std::vector elems; @@ -166,66 +87,206 @@ struct PairMaxSets { }; +/********************/ +/*** DDbuildF2V *****/ +/********************/ + +DDbuildF2V::DDbuildF2V(const IntervalVector &box, + const std::vector &eqconstraints) : bbox(box), empty(false), + nbIn(0) { + assert_release(!bbox.is_unbounded()); + + for (const Facet &f : eqconstraints) { + assert(f.eqcst); + Row cst = f.row; + Interval rhs(f.rhs); + for (const EqFacet &e : eqfacets) { + e.adapt_eqconstraint(bbox,cst,rhs); + } + try { + eqfacets.push_back(EqFacet::build_EqFacet(bbox,cst,rhs)); + } catch (const std::domain_error &error) { + if (rhs.contains(0.0)) continue; + empty=true; + eqfacets.clear(); + return; + } + } + for (const EqFacet &e : eqfacets) { + e.adapt_box(bbox); + if (bbox.is_empty()) { + empty=true; + eqfacets.clear(); + return; + } + } + /* we order dim_facets in decreasing order, to build fcts in the + correct order later */ + for (Index i=bbox.size()-1; i>=0; i--) { + bool found=false; + for (const EqFacet &e : eqfacets) { + if (e.dim ==i) { found=true; break; } + } + if (!found) dim_facets.push_back(i); + } + /* we use "negative" number for the box constraints : + one for each negative bounds (-s to -1), one for all positive bounds + (-s-1). + idem for links, which means we use specific id for the vertices */ + std::set links; + std::vector fcts; + for (Index i : dim_facets) { + fcts.push_back(-i-1); + links.insert(-i-1); + } + IntervalVector l = bbox.lb(); + this->initDDvertex(-bbox.size()-1,l,links,fcts); + Interval sum = bbox.sum(); + for (Index i : dim_facets) { + l=box.lb(); + l[i]=l[i]+sum.ub()-sum.lb(); + fcts.push_back(-bbox.size()-1); + links.insert(1); + for (Index j : dim_facets) { + if (j==i) continue; + fcts.push_back(-j-1); + links.insert(-j-1); + } + this->initDDvertex(-i-1,l,links,fcts); + } +} + +Index DDbuildF2V::initDDvertex(Index id, const IntervalVector &v, + std::set &links, std::vector &fcts) { + vertices.emplace(id, + DDvertex(v,id,links,fcts)); + return id; +} +Index DDbuildF2V::addDDvertex(const IntervalVector &v, + std::set &links, std::vector &fcts) { + nbIn++; + vertices.emplace(nbIn, + DDvertex(v,nbIn,links,fcts)); + return nbIn; +} +Index DDbuildF2V::addDDvertex(IntervalVector &&v, + std::set &links, std::vector &fcts) { + nbIn++; + vertices.emplace(nbIn, + DDvertex(v,nbIn,links,fcts)); + return nbIn; +} + +/* p1 has lambda>rhs, p2 has lambda nfcts; + Index a1=0; Index a2=0; + bool idFacetAdded=false; + while (a1<(Index)p1.fcts.size() && a2<(Index)p2.fcts.size()) { + if (p1.fcts[a1]idFacet && !idFacetAdded) { + nfcts.push_back(idFacet); + idFacetAdded=true; + } + nfcts.push_back(p1.fcts[a1]); + a1++; a2++; + } + } + if (!idFacetAdded) nfcts.push_back(idFacet); + std::set lnks; + lnks.insert(p2.Id); + Index newId=this->addDDvertex(newV,lnks,nfcts); + p2.replaceLnks(p1.Id,newId); + return newId; +} + + + int DDbuildF2V::add_facet(Index idFacet, const Facet& fct) { IntervalRow rowI = fct.row; + double rhs = fct.rhs; + /* treat eqfacets to remove some dimensions */ + for (const EqFacet &e : eqfacets) { + e.adapt_ineqconstraint(rowI,rhs); + } int cnt=0; - /* the first step is the modification of the status */ - for (DDvertex &d : vertices) { - if (d.status==DDvertex::VERTEXREMOVED) continue; - Interval a = rowI*d.vertex - fct.rhs; - d.lambda=a.lb(); - if (a.ub()<0.0) { d.status=DDvertex::VERTEXLT; } - else if (a.lb()<=0.0) { d.status=DDvertex::VERTEXEQ; } + /* compute the status of each vertices */ + for (std::pair &v : vertices) { + DDvertex &d = v.second; + d.lambda = rowI*d.vertex; + if (d.lambda.ub()::iterator it = vertices.begin(); + it!=vertices.end() && it->first<=endF;) { + DDvertex &d = it->second; + std::cout << " vertex: " << d.Id << " " << d.status << "\n"; + ++it; if (d.status==DDvertex::VERTEXEQ) { d.addFct(idFacet); + continue; } - if (d.status==DDvertex::VERTEXEQ || d.status==DDvertex::VERTEXLT) continue; - /* in case fct.eqcst is true, we remove VERTEXLT later */ + if (d.status==DDvertex::VERTEXLT || d.status==DDvertex::VERTEXNEW) + continue; for (Index idL : d.links) { - DDvertex &d2 = vertices[idL]; + auto it2 = vertices.find(idL); + if (it2==vertices.end()) continue; + DDvertex &d2 = it2->second; if (d2.status==DDvertex::VERTEXEQ) { d2.removeLnk(d.Id); continue; } if (d2.status!=DDvertex::VERTEXLT) { - continue; /* may be GT or REMOVED, for each do nothing */ + continue; /* do nothing */ } - Index newId = this->addVertexSon(d,d2,idFacet,fct.eqcst); +#ifdef DEBUG_CODAC2_DD + std::cout << " addVertex " << d.Id << " " << d2.Id << " " << d.status << " " << d2.status << std::endl; + std::cout << " (" << d.lambda << " " << d2.lambda << ") " << std::endl; +#endif +#ifdef DEBUG_CODAC2_DD + Index newId = this->addVertexSon(d,d2,rhs,idFacet); + std::cout << " ... done " << newId <addVertexSon(d,d2,idFacet); +#endif cnt++; - if (fNew==-1) fNew=newId; } this->removeVertex(d.Id,false); } - /* elimination of old vertices */ - i = firstIn; - while (i!=fNew) { - DDvertex &d = vertices[i]; - i = d.nextId; - if (fct.eqcst && d.status==DDvertex::VERTEXLT) { - this->removeVertex(d.Id,true); - } - } /*construction of new links for the eq vertices */ - i = firstIn; std::vector maxset; - while (i!=-1) { - DDvertex &d = vertices[i]; - i = d.nextId; + for (std::map::iterator it = vertices.begin(); + it!=vertices.end();++it) { + DDvertex &d = it->second; if (d.status==DDvertex::VERTEXEQ || d.status==DDvertex::VERTEXNEW) { - Index j=i; - while (j!=-1) { - DDvertex &d2 = vertices[j]; - j=d2.nextId; - if (d2.status==DDvertex::VERTEXEQ || d2.status==DDvertex::VERTEXNEW) { - PairMaxSets p(d.Id,d2.Id,d.fcts,d2.fcts); - PairMaxSets &actuel = p; + for (std::map::iterator it2 = next(it); + it2!=vertices.end(); ++it2) { + DDvertex &d2 = it2->second; + if (d2.status==DDvertex::VERTEXEQ + || d2.status==DDvertex::VERTEXNEW) { + PairMaxSets actuel(d.Id,d2.Id,d.fcts,d2.fcts); bool placed=false; unsigned long int k=0, l=0; while (ksecond.removeLnk(Id); } } + this->vertices.erase(Id); +} + +IntervalVector DDbuildF2V::recompute_vertex(const IntervalVector &vect) const { + IntervalVector v=vect; + for (Index i = eqfacets.size()-1; i>=0; i--) { + const EqFacet &ef = eqfacets[i]; + v[ef.dim]=ef.eqcst.dot(v)+ef.valcst; + } + return v; } std::ostream& operator<<(std::ostream& os, const DDbuildF2V& build) { os << "DDbuildF2V (" << build.vertices.size() << " vtx) : " << std::endl; - for (const DDvertex &vt : build.vertices) { - if (vt.status==DDvertex::VERTEXREMOVED) continue; - os << vt.vertex << std::endl; + for (const auto &ivt : build.vertices) { + auto &vt = ivt.second; + os << vt.Id << " : " << vt.vertex << std::endl; os << " fct: ("; for (Index a : vt.fcts) { os << a << ","; } os << ")" << std::endl; + os << " lnks: ("; + for (Index a : vt.links) { + os << a << ","; + } + os << ")" << std::endl; } os << "endDDbuildF2V" << std::endl; return os; } +/*****************************/ +/*** DDbuildF2V::EqFacet *****/ +/*****************************/ +/* management of the equality constraints for DDbuildF2V */ +void DDbuildF2V::EqFacet::adapt_eqconstraint(const IntervalVector &bbox, + Row &cst, Interval &rhs) const { + Interval alpha(cst[dim]); cst[dim]=0.0; + IntervalRow newcst = cst + alpha*this->eqcst; + cst = newcst.mid(); + rhs += (newcst-cst).dot(bbox)-alpha*valcst; +} +void DDbuildF2V::EqFacet::adapt_ineqconstraint(IntervalRow &cst, double &rhs) + const { + Interval alpha(cst[dim]); cst[dim]=0.0; + cst = cst + alpha*this->eqcst; + rhs = (rhs-alpha*valcst).ub(); +} +DDbuildF2V::EqFacet + DDbuildF2V::EqFacet::build_EqFacet(const IntervalVector &bbox, + const Row &cst, Interval &rhs) { + /* identification of the dimension */ + Index dim = -1; + double val=0; + for (Index i=0;ival) { + dim=i; + val = v2; + } + } + if (dim==-1) throw + std::domain_error("zero vector in build_EqFacet"); + IntervalRow newcst(cst); + val=cst[dim]; + newcst[dim]=0.0; + newcst = newcst/val; + rhs = rhs/val - (newcst-newcst.mid()).dot(bbox); + return EqFacet(dim,-newcst.mid(),rhs); +} + +void DDbuildF2V::EqFacet::adapt_box(IntervalVector &bbox) const { + Interval eval = this->eqcst.dot(bbox) + valcst; + if ((bbox[this->dim] & eval).is_empty()) { + bbox.set_empty(); + } + bbox[this->dim]=0; +} + + +/*****************************/ +/*** DDbuildV2F::EqFacet *****/ +/*****************************/ + DDbuildV2F::DDbuildV2F(Index idVertex, const Vector &vertex) { Row r = Row::Zero(vertex.size()); for (Index i=0;iaddFacetSon(d,d2,idVertex); std::cout << " ... done " << newid < #include #include +#include #include "codac2_Index.h" #include "codac2_Vector.h" @@ -36,9 +37,10 @@ struct DDvertex { enum ddvertexstatus { VERTEXNEW, - VERTEXLT, - VERTEXGT, - VERTEXEQ, + VERTEXLT, /* (GT,LT) => new vertex */ + VERTEXLE, /* (GT,LE) => extend LE vertex */ + VERTEXGT, + VERTEXEQ, /* }; ddvertexstatus status; @@ -133,22 +135,68 @@ struct DDfacet { class DDbuildF2V { public: - DDbuildF2V(const IntervalVector &box); + DDbuildF2V(const IntervalVector &box, + const std::vector &eqconstraints); int add_facet(Index idFacet, const Facet& fct); friend std::ostream& operator<<(std::ostream& os, const DDbuildF2V& build); private: + Index initDDvertex(Index id, const IntervalVector &v, + std::set &links, std::vector &fcts); Index addDDvertex(const IntervalVector &v, std::set &links, std::vector &fcts); Index addDDvertex(IntervalVector &&v, std::set &links, std::vector &fcts); - Index addVertexSon(const DDvertex &p1, DDvertex &p2, - Index idFacet, bool isEq); + Index addVertexSon(const DDvertex &p1, DDvertex &p2, double rhs, + Index idFacet); void removeVertex(Index Id, bool removeLnks); std::map vertices; + + /* x_dim = eqcst x + rhs */ + struct EqFacet { + Index dim; + Row eqcst; + Interval valcst; + + EqFacet (Index dim, const Row &eqcst, const Interval &valcst) : + dim(dim), eqcst(eqcst), valcst(valcst) { }; + + /* computation of the value */ + void computeVal(IntervalVector &x) const { + x[dim] = this->eqcst.dot(x) + valcst; + } + + /* adapt box */ + void adapt_box(IntervalVector &bbox) const; + + /* adaptation of an equality constraint cst x = rhs */ + /* with alpha = cst[dim], + we have (cst' + alpha*eqcst).mid x' = rhs - alpha*valcst + + [-1,1] * (cst' + alpha*eqcst).rad * box */ + void adapt_eqconstraint(const IntervalVector &bbox, + Row &cst, Interval &rhs) const; + + /* adaptation of an inequality constraint cst x <= rhs */ + /* with alpha = cst[dim], + we have (cst' + alpha*eqcst) x' <== (rhs - alpha*valcst).ub */ + void adapt_ineqconstraint(IntervalRow &cst, double &rhs) const; + + /* construction of a new eqfacet from an equality constraint + cst x = rhs */ + static EqFacet build_EqFacet(const IntervalVector &bbox, + const Row &cst, Interval &rhs); + }; + + IntervalVector recompute_vertex(const IntervalVector &vect) const; + + std::vector eqfacets; + std::vector dim_facets; + IntervalVector bbox; + + bool empty; Index nbIn; double tolerance = 1e-9; }; From 8db0568ca580adae2eea5598e486a106a1ae64d5 Mon Sep 17 00:00:00 2001 From: damien-masse Date: Wed, 15 Oct 2025 10:29:34 +0200 Subject: [PATCH 04/26] temporaire --- src/extensions/polytope/codac2_Facet.h | 2 + src/extensions/polytope/codac2_dd.cpp | 151 +++++++++++++------ src/extensions/polytope/codac2_dd.h | 200 +++++++++++++++---------- 3 files changed, 228 insertions(+), 125 deletions(-) diff --git a/src/extensions/polytope/codac2_Facet.h b/src/extensions/polytope/codac2_Facet.h index e2be1a455..d9e9cc46f 100644 --- a/src/extensions/polytope/codac2_Facet.h +++ b/src/extensions/polytope/codac2_Facet.h @@ -11,6 +11,7 @@ #pragma once #include /* uses swap */ +#include #include "codac2_Index.h" #include "codac2_Vector.h" @@ -19,6 +20,7 @@ #include "codac2_IntervalVector.h" #include "codac2_IntervalRow.h" #include "codac2_BoolInterval.h" +#include "codac2_operators.h" namespace codac2 { diff --git a/src/extensions/polytope/codac2_dd.cpp b/src/extensions/polytope/codac2_dd.cpp index 66aea2931..ff403bb29 100644 --- a/src/extensions/polytope/codac2_dd.cpp +++ b/src/extensions/polytope/codac2_dd.cpp @@ -23,6 +23,7 @@ #include "codac2_Row.h" #include "codac2_Facet.h" #include "codac2_dd.h" +#include "codac2_IntvFulPivLU.h" #define DEBUG_CODAC2_DD @@ -31,13 +32,14 @@ namespace codac2 { /*************************/ /****** PairMaxSet *******/ /*************************/ -struct PairMaxSets { - Index a,b; +template +struct PairMaxSets { + T a,b; std::vector elems; PairMaxSets() {} - PairMaxSets(Index a, Index b, const std::vector e1, const std::vector e2) : a(a), b(b) { + PairMaxSets(T a, T b, const std::vector e1, const std::vector e2) : a(a), b(b) { Index i=0,j=0; while(i<(Index)e1.size() && j<(Index)e2.size()) { if (e1[i]==e2[j]) { @@ -91,11 +93,29 @@ struct PairMaxSets { /*** DDbuildF2V *****/ /********************/ -DDbuildF2V::DDbuildF2V(const IntervalVector &box, - const std::vector &eqconstraints) : bbox(box), empty(false), - nbIn(0) { - assert_release(!bbox.is_unbounded()); +DDbuildF2V::DDbuildF2V(Index dim, const IntervalVector &box, + const std::vector &eqconstraints) : dim(dim), + fdim(dim), + bbox(box), empty(false), nbInVertex(0), nbInLinks(0) { + if (eqconstraints.size()!=0) { + /* managing the equalities */ + /* we first check the existence of a solution, and get a finite + number of idependant equalities */ + Matrix MatEQ = Matrix(eqconstraints.size(),dim); + IntervalVector RhsEQ = IntervalVector(eqconstraints.size()); + for (Index i=0;i links; std::vector fcts; for (Index i : dim_facets) { fcts.push_back(-i-1); - links.insert(-i-1); } IntervalVector l = bbox.lb(); - this->initDDvertex(-bbox.size()-1,l,links,fcts); + this->initDDvertex(-bbox.size()-1,l,fcts); Interval sum = bbox.sum(); for (Index i : dim_facets) { l=box.lb(); l[i]=l[i]+sum.ub()-sum.lb(); fcts.push_back(-bbox.size()-1); - links.insert(1); for (Index j : dim_facets) { if (j==i) continue; fcts.push_back(-j-1); - links.insert(-j-1); } - this->initDDvertex(-i-1,l,links,fcts); + this->initDDvertex(-i-1,l,fcts); } + for (Index i : dim_facets) { + for (Index j : dim_facets) { + if (j==i) continue; + fcts.push(back(-j-1); + } + this->initDDlinks(vertices.find(-bbox.size()-1), + vertices.find(-i-1),fcts)); + for (Index j : dim_facets) { + if (j<=i) continue; + fcts.push_back(-bbox.size()-1); + for (Index k : dim_facets) { + if (k!=i) continue; + if (k!=j) continue; + fcts.push_back(-k-1); + } + this->initDDlinks(vertices.find(-i-1), + vertices.find(-j-1),fcts); + } + } } Index DDbuildF2V::initDDvertex(Index id, const IntervalVector &v, - std::set &links, std::vector &fcts) { + std::vector &fcts) { vertices.emplace(id, - DDvertex(v,id,links,fcts)); + DDvertex(v,id,fcts)); return id; } -Index DDbuildF2V::addDDvertex(const IntervalVector &v, - std::set &links, std::vector &fcts) { +Index DDbuildF2V::addDDvertex(const IntervalVector &v, std::vector &fcts) { nbIn++; - vertices.emplace(nbIn, - DDvertex(v,nbIn,links,fcts)); + vertices.emplace(nbIn, DDvertex(v,nbIn,fcts)); return nbIn; } -Index DDbuildF2V::addDDvertex(IntervalVector &&v, - std::set &links, std::vector &fcts) { +Index DDbuildF2V::addDDvertex(IntervalVector &&v, std::vector &fcts) { nbIn++; - vertices.emplace(nbIn, - DDvertex(v,nbIn,links,fcts)); + vertices.emplace(nbIn, DDvertex(v,nbIn,fcts)); return nbIn; } @@ -222,43 +253,61 @@ Index DDbuildF2V::addVertexSon(const DDvertex &p1, DDvertex &p2, double rhs, int DDbuildF2V::add_facet(Index idFacet, const Facet& fct) { + if (empty) return 0; IntervalRow rowI = fct.row; double rhs = fct.rhs; /* treat eqfacets to remove some dimensions */ for (const EqFacet &e : eqfacets) { e.adapt_ineqconstraint(rowI,rhs); } + ineqfacets.emplace(idFacet,std::pair(rowI,rhs)); int cnt=0; /* compute the status of each vertices */ for (std::pair &v : vertices) { DDvertex &d = v.second; d.lambda = rowI*d.vertex; if (d.lambda.ub()::iterator it = vertices.begin(); - it!=vertices.end() && it->first<=endF;) { + for (std::map::iterator it = vertices.rbegin(); + it!=vertices.rend();++it) { DDvertex &d = it->second; std::cout << " vertex: " << d.Id << " " << d.status << "\n"; - ++it; - if (d.status==DDvertex::VERTEXEQ) { + if (d.status==DDvertex::VERTEXLT || + d.status==DDvertex::VERTEXON || + d.status==DDvertex::VERTEXNEW) + continue; + /* check the links towards LT and ON, if the facet is a valid + intersection */ + if (d.status==DDvertex::VERTEXGT) { + bool oktoremove=true; + for (auto &l : d.links) { + if (l==total_links.end()) continue; + auto &u = l->second.otherVertex(it); + if (u==vertices.end()) { + total_links.remove(l); + l=total_links.end(); + continue; + } + if (u->second.status!=VERTEXON + && u->second.status!=VERTEXLT) continue; + } + } + if (d.status==DDvertex::VERTEXGE) { d.addFct(idFacet); continue; } - if (d.status==DDvertex::VERTEXLT || d.status==DDvertex::VERTEXNEW) - continue; - for (Index idL : d.links) { - auto it2 = vertices.find(idL); - if (it2==vertices.end()) continue; - DDvertex &d2 = it2->second; - if (d2.status==DDvertex::VERTEXEQ) { - d2.removeLnk(d.Id); + for (auto &l : d.links) { + if (l==total_links.end()) continue; + auto &u = l->second.otherVertex(it); + auto &d2 = u->second; + if (d2.status==DDvertex::VERTEXGE || d2.status==DDvertex::VERTEXGT) { + l->second.remove(it); continue; } - if (d2.status!=DDvertex::VERTEXLT) { + if (d2.status!=DDvertex::VERTEXLT && d2.status!=DDvertex::VERTEXON) { continue; /* do nothing */ } #ifdef DEBUG_CODAC2_DD @@ -276,17 +325,21 @@ int DDbuildF2V::add_facet(Index idFacet, const Facet& fct) { this->removeVertex(d.Id,false); } /*construction of new links for the eq vertices */ +#ifdef DEBUG_CODAC2_DD + std::cout << " (" << idFacet << ")" << " construction of PairMaxSet " << cnt << std::endl; +#endif std::vector maxset; for (std::map::iterator it = vertices.begin(); it!=vertices.end();++it) { DDvertex &d = it->second; - if (d.status==DDvertex::VERTEXEQ || d.status==DDvertex::VERTEXNEW) { + if (d.status==DDvertex::VERTEXGE || d.status==DDvertex::VERTEXNEW) { for (std::map::iterator it2 = next(it); it2!=vertices.end(); ++it2) { DDvertex &d2 = it2->second; - if (d2.status==DDvertex::VERTEXEQ + if (d2.status==DDvertex::VERTEXGE || d2.status==DDvertex::VERTEXNEW) { PairMaxSets actuel(d.Id,d2.Id,d.fcts,d2.fcts); +// if (actuel.elems.size() &fcts, + const IntervalVector &v) { + IntervalMatrix M = IntervalMatrix::zero(fcts.size(), v.size()); + for (unsigned long int i=0;i #include #include +#include #include #include "codac2_Index.h" @@ -30,6 +31,8 @@ namespace codac2 { +struct DDlink; /* link between two vertices */ + struct DDvertex { Index Id; IntervalVector vertex; @@ -37,105 +40,77 @@ struct DDvertex { enum ddvertexstatus { VERTEXNEW, - VERTEXLT, /* (GT,LT) => new vertex */ - VERTEXLE, /* (GT,LE) => extend LE vertex */ - VERTEXGT, - VERTEXEQ, /* + VERTEXLT, /* < (GT,LT) => new vertex */ + VERTEXON, /* <,=,> (GT,LE) => for now, equivalent to LT */ + VERTEXGE, /* = and possible small > + keep the vertex. no change with (GT,GE) */ + VERTEXGT, /* remove vertex, add new vertices for links */ }; ddvertexstatus status; - std::set links; - std::vector fcts; + std::vector::iterator> links; - DDvertex(const IntervalVector &v, Index Id, - std::set &nlinks, - std::vector &nfcts) : Id(Id), + DDvertex(const IntervalVector &v, Index Id) : Id(Id), vertex(v), status(VERTEXNEW) { - this->links.swap(nlinks); - this->fcts.swap(nfcts); } + DDvertex(IntervalVector &&v, Index Id, - std::set &nlinks, - std::vector &nfcts) : Id(Id), + std::vector::iterator> &nlinks) : + Id(Id), vertex(v), status(VERTEXNEW) { this->links.swap(nlinks); - this->fcts.swap(nfcts); } - void replaceLnks(Index old, Index nw) { - this->links.erase(old); - this->links.insert(nw); + void addLnk(const std::map::iterator &a) { + this->links.push_back(a); } - void addLnk(Index a) { - this->links.insert(a); - } + void addLnkVertex(const std::map::iterator &V1, + const std::vector &fcts); - void addFct(Index a) { - auto it = this->fcts.begin(); - while (it != this->fcts.end() && (*it)fcts.end() && (*it)==a) return; - this->fcts.insert(it,a); - } + void removeLnk(const std::map::iterator &a); - void removeLnk(Index a) { - this->links.erase(a); +#if 0 + void addFct(Index a) { + auto rev_it = this->fcts.rbegin(); + while (rev_it != this->fcts.rend() && (*rev_it)>a) it++; + if (rev_it!=this->fcts.rend() && (*rev_it)==a) return; + this->fcts.insert(rev_it.base(),a); } +#endif }; -struct DDfacet { - Index Id; - Facet facet; - double lambda; - - enum ddfacetstatus { - FACETNEW, - FACETIN, - FACETON, - FACETOUT - }; - ddfacetstatus status; - - std::set links; - std::vector vtx; - - DDfacet(const Facet &v, Index Id, - std::set &nlinks, - std::vector &nvtx) : Id(Id), facet(v), status(FACETNEW) { - this->links.swap(nlinks); - this->vtx.swap(nvtx); - } - DDfacet(Facet &&v, Index Id, - std::set &nlinks, - std::vector &nvtx) : Id(Id), facet(v), status(FACETNEW) { - this->links.swap(nlinks); - this->vtx.swap(nvtx); +struct DDlink { + std::map::iterator V1; + std::map::iterator V2; + std::unordered_set> fcts; + + DDlink(Index dim, const std::map::iterator &V1, + const std::map::iterator &V2, + const std::vector &fcts) : V1(V1), V2(V2) { + this->fcts.insert(fcts); } - void replaceLnks(Index old, Index nw) { - this->links.erase(old); - this->links.insert(nw); + void changeVertex(const std::map::iterator &Vold, + const std::map::iterator &Vnew) { + if (this->V1==Vold) { this->V1=Vnew; return; } + this->V2=Vnew; } - - void addLnk(Index a) { - this->links.insert(a); + + const std::map::iterator &otherVertex + (const std::map::iterator &V) const { + if (V==V1) return V2; + return V1; } - - void addVtx(Index a) { - auto it = this->vtx.begin(); - while (it != this->vtx.end() && (*it)vtx.end() && (*it)==a) return; - this->vtx.insert(it,a); - } - - void removeLnk(Index a) { - this->links.erase(a); + + void addFct(const std::vector &fcts) { + this->fcts.insert(fcts); } }; class DDbuildF2V { public: - DDbuildF2V(const IntervalVector &box, + DDbuildF2V(Index dim, const IntervalVector &box, const std::vector &eqconstraints); int add_facet(Index idFacet, const Facet& fct); @@ -143,17 +118,19 @@ class DDbuildF2V { friend std::ostream& operator<<(std::ostream& os, const DDbuildF2V& build); private: - Index initDDvertex(Index id, const IntervalVector &v, - std::set &links, std::vector &fcts); - Index addDDvertex(const IntervalVector &v, std::set &links, - std::vector &fcts); - Index addDDvertex(IntervalVector &&v, std::set &links, + std::map::iterator initDDvertex(Index id, + const IntervalVector &v, std::vector &fcts); + Index initDDlinks(const std::map::iterator& V1, + const std::map::iterator& V2, + std::vector &fct); + + std::map::iterator addDDvertex(const IntervalVector &v, std::vector &fcts); - Index addVertexSon(const DDvertex &p1, DDvertex &p2, double rhs, - Index idFacet); + std::map::iterator addDDvertex(IntervalVector &&v, + std::vector &fcts); + std::map::iterator addVertexSon(const DDvertex &p1, + DDvertex &p2, double rhs, Index idFacet); void removeVertex(Index Id, bool removeLnks); - - std::map vertices; /* x_dim = eqcst x + rhs */ struct EqFacet { @@ -192,15 +169,72 @@ class DDbuildF2V { IntervalVector recompute_vertex(const IntervalVector &vect) const; + Index dim; + Index fdim; + IntervalMatrix M_EQ1, M_EQ2; std::vector eqfacets; std::vector dim_facets; - IntervalVector bbox; + + std::map vertices; + std::map total_links; + std::map> ineqfacets; bool empty; - Index nbIn; + Index nbIdVertex; + Index nbIdLinks; double tolerance = 1e-9; }; +struct DDfacet { + Index Id; + Facet facet; + double lambda; + + enum ddfacetstatus { + FACETNEW, + FACETIN, + FACETON, + FACETOUT + }; + ddfacetstatus status; + + std::set links; + std::vector vtx; + + DDfacet(const Facet &v, Index Id, + std::set &nlinks, + std::vector &nvtx) : Id(Id), facet(v), status(FACETNEW) { + this->links.swap(nlinks); + this->vtx.swap(nvtx); + } + DDfacet(Facet &&v, Index Id, + std::set &nlinks, + std::vector &nvtx) : Id(Id), facet(v), status(FACETNEW) { + this->links.swap(nlinks); + this->vtx.swap(nvtx); + } + + void replaceLnks(Index old, Index nw) { + this->links.erase(old); + this->links.insert(nw); + } + + void addLnk(Index a) { + this->links.insert(a); + } + + void addVtx(Index a) { + auto it = this->vtx.begin(); + while (it != this->vtx.end() && (*it)vtx.end() && (*it)==a) return; + this->vtx.insert(it,a); + } + + void removeLnk(Index a) { + this->links.erase(a); + } +}; + class DDbuildV2F { public: DDbuildV2F(Index idVertex, const Vector &vertex); From 71f1509d0d7de3a78b7089d6d87e34e2924082e9 Mon Sep 17 00:00:00 2001 From: damien-masse Date: Fri, 17 Oct 2025 08:25:14 +0200 Subject: [PATCH 05/26] tmp --- src/extensions/polytope/codac2_dd.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/extensions/polytope/codac2_dd.cpp b/src/extensions/polytope/codac2_dd.cpp index ff403bb29..345e24a71 100644 --- a/src/extensions/polytope/codac2_dd.cpp +++ b/src/extensions/polytope/codac2_dd.cpp @@ -110,7 +110,11 @@ DDbuildF2V::DDbuildF2V(Index dim, const IntervalVector &box, IntvFullPivLU LUdec(MatEQ); if (LUdec.is_injective()!=BoolInterval::TRUE) { /* TODO : check emptiness */ - IntervalMatrix eqsolve = LUdec.solve(RhsEQ); + /* IntervalMatrix eqsolve = LUdec.solve(RhsEQ); + if (eqsolve.is_empty()) { empty=true; return; } */ + /* FIXME : NOT CORRECT, as solve DOES NOT + guarantee the emptiness of solutions. We need + to use e.g. a bounding box */ } From eeb19122c35af72ce88584adee89ddd3b5a4f6af Mon Sep 17 00:00:00 2001 From: damien-masse Date: Tue, 21 Oct 2025 13:45:22 +0200 Subject: [PATCH 06/26] tmp --- src/extensions/polytope/codac2_dd.cpp | 256 +++++++++++++++----------- src/extensions/polytope/codac2_dd.h | 71 ++++--- 2 files changed, 193 insertions(+), 134 deletions(-) diff --git a/src/extensions/polytope/codac2_dd.cpp b/src/extensions/polytope/codac2_dd.cpp index 345e24a71..ef6149675 100644 --- a/src/extensions/polytope/codac2_dd.cpp +++ b/src/extensions/polytope/codac2_dd.cpp @@ -94,122 +94,81 @@ struct PairMaxSets { /********************/ DDbuildF2V::DDbuildF2V(Index dim, const IntervalVector &box, - const std::vector &eqconstraints) : dim(dim), + const std::vector &eqconstraints, bool include_box) : dim(dim), fdim(dim), - bbox(box), empty(false), nbInVertex(0), nbInLinks(0) { - if (eqconstraints.size()!=0) { + bbox(box), empty(false), flat(false), + nbInVertex(0), nbInLinks(0) { + int nb_eq_dim= eqconstraints.size(); + if (include_box) { + for (int i=0;i0) { /* managing the equalities */ /* we first check the existence of a solution, and get a finite number of idependant equalities */ - Matrix MatEQ = Matrix(eqconstraints.size(),dim); - IntervalVector RhsEQ = IntervalVector(eqconstraints.size()); + Matrix MatEQ = Matrix::Zero(nb_eq_dim,dim); + IntervalVector RhsEQ = + IntervalVector::Zero(nb_eq_dim); + int base=0; + if (include_box) { + for (int i=0;ifdim = this->dim - nb_eq_dim; + this->M_EQ = IntervalMatrix::Zero(this->dim,this->fdim+1); + this->M_EQ.col(0) = LUdec.solve(RhsEQ); + this->M_EQ.rightCols(this->fdim) = LUdec.kernel(); + this->flat=true; } - - - - } - for (const Facet &f : eqconstraints) { - assert(f.eqcst); - Row cst = f.row; - Interval rhs(f.rhs); - for (const EqFacet &e : eqfacets) { - e.adapt_eqconstraint(bbox,cst,rhs); - } - try { - eqfacets.push_back(EqFacet::build_EqFacet(bbox,cst,rhs)); - } catch (const std::domain_error &error) { - if (rhs.contains(0.0)) continue; - empty=true; - eqfacets.clear(); - return; - } +#ifdef DEBUG_CODAC2_DD + std::cout << "M_EQ : " << M_EQ << std::endl; +#endif } - for (const EqFacet &e : eqfacets) { - e.adapt_box(bbox); - if (bbox.is_empty()) { - empty=true; - eqfacets.clear(); - return; - } - } - /* we order dim_facets in decreasing order, to build fcts in the - correct order later */ - for (Index i=bbox.size()-1; i>=0; i--) { - bool found=false; - for (const EqFacet &e : eqfacets) { - if (e.dim ==i) { found=true; break; } - } - if (!found) dim_facets.push_back(i); - } - /* we use "negative" number for the box constraints : - one for each negative bounds (-s to -1), one for all positive bounds - (-s-1). - idem for links, which means we use specific id for the vertices */ - std::vector fcts; - for (Index i : dim_facets) { - fcts.push_back(-i-1); + /* initial vertice : + (1 0 0 ... ), + rays */ + IntervalVector v = IntervalVector::Zero(this->fdim+1); + v[0]=1.0; + this->addDDvertex(v); + for (int i=0;ifdim+1); + line[i+1]=1.0; + lines.push_back(line); } - IntervalVector l = bbox.lb(); - this->initDDvertex(-bbox.size()-1,l,fcts); - Interval sum = bbox.sum(); - for (Index i : dim_facets) { - l=box.lb(); - l[i]=l[i]+sum.ub()-sum.lb(); - fcts.push_back(-bbox.size()-1); - for (Index j : dim_facets) { - if (j==i) continue; - fcts.push_back(-j-1); - } - this->initDDvertex(-i-1,l,fcts); - } - for (Index i : dim_facets) { - for (Index j : dim_facets) { - if (j==i) continue; - fcts.push(back(-j-1); - } - this->initDDlinks(vertices.find(-bbox.size()-1), - vertices.find(-i-1),fcts)); - for (Index j : dim_facets) { - if (j<=i) continue; - fcts.push_back(-bbox.size()-1); - for (Index k : dim_facets) { - if (k!=i) continue; - if (k!=j) continue; - fcts.push_back(-k-1); - } - this->initDDlinks(vertices.find(-i-1), - vertices.find(-j-1),fcts); - } - } +// if (include_box) this->add_constraint_box(bbox); } -Index DDbuildF2V::initDDvertex(Index id, const IntervalVector &v, - std::vector &fcts) { - vertices.emplace(id, - DDvertex(v,id,fcts)); - return id; -} -Index DDbuildF2V::addDDvertex(const IntervalVector &v, std::vector &fcts) { +std::list::iterator DDbuildF2V::addDDvertex(const IntervalVector &v) { nbIn++; - vertices.emplace(nbIn, DDvertex(v,nbIn,fcts)); - return nbIn; + vertices.emplace_front(DDvertex(v,nbIn)); + return vertices.begin(); } -Index DDbuildF2V::addDDvertex(IntervalVector &&v, std::vector &fcts) { +std::list::iterator DDbuildF2V::addDDvertex(IntervalVector &&v) { nbIn++; - vertices.emplace(nbIn, DDvertex(v,nbIn,fcts)); - return nbIn; + vertices.emplace_front(DDvertex(v,nbIn)); + return vertices.begin(); } /* p1 has lambda>rhs, p2 has lambdafdim+1); + facet[0] = -fct.rhs; + if (flat) { + facet += fct.row * this->M_EQ; + } else { + facet.tail(this->fdim) = fct.row; } - ineqfacets.emplace(idFacet,std::pair(rowI,rhs)); - int cnt=0; - /* compute the status of each vertices */ + /* then consider lines */ + Index bestline=-1; + double maxmig=0.0; + for (Index i=0;imaxmig) { + bestline=i; + maxmig=mg; + } + } + if (bestline!=-1) { + Index codeFacet = reffacets.size(); + reffacets.push_back(idFacet); + if (bestline!=lines.size()-1) { + lines[bestline].swap(lines.back()); + } + IntervalVector& bstline = lines.back(); + Interval ubest = facet.dot(bstline); + /* traitement des lines */ + for (Index i=0;i0.0) { + if (lower>0.0) + v.vertex = ubest.ub()*v.vertex - lower*bstline; + else + v.vertex = ubest.lb()*v.vertex - lower*bstline; + } else { + if (lower>0.0) + v.vertex = -ubest.lb()*v.vertex + lower*bstline; + else + v.vertex = -ubest.ub()*v.vertex + lower*bstline; + } + v.addFct(codeFacet); + } + /* ajout du nouveau sommet et des liens */ + std::forward_list::iterator itVertex = vertices.begin(); + std::forward_list::iterator newVertex = + this->addDDvertex(ubest.lb()<0.0 ? bstline : (-bstline)); + for (;itVertex!=vertices.end();++itVertex) { + this->addDDlink(itVertex,newVertex); + } + for (Index a=0;aaddFct(a); + } + return 1; + } + /* end of line treatments , the following + is when no line is available */ + + std::forward_list::iterator itVertex = vertices.begin(); + std::vector::iterator> stack; + Index refFacet=-1; + while (itVertex!=vertices.end()) { + std::forward_list::iterator actVertex; + if (stack.size()>0) { + actVertex = stack.back(); + stack.pop_back(); + if (actVertex->status!=VERTEXGT) continue; + } else { + actVertex=itVertex; + ++itVertex; + if (actVertex->status!=VERTEXTODO) continue; + actVertex->lambda = facet.dot(actVertex->vertex); + if (actVertex->lambda.ub()<0.0) { + actVertex->status = VERTEXLT; + continue; + } + if (actVertex->lambda.lb()<0.0) { + actVertex->status = VERTEXON; + continue; + } + if (actVertex->lambda.lb()tolerance) { + actVertex->status = VERTEXGE; + continue; + } + retFacet = reffacets.size(); /* new facet identification */ + reffacets.push_back(idFacet); + } + actVertex->status = VERTEXREM; + + } + + for (std::pair &v : vertices) { DDvertex &d = v.second; d.lambda = rowI*d.vertex; diff --git a/src/extensions/polytope/codac2_dd.h b/src/extensions/polytope/codac2_dd.h index 872972e2d..e0995579e 100644 --- a/src/extensions/polytope/codac2_dd.h +++ b/src/extensions/polytope/codac2_dd.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -37,14 +38,19 @@ struct DDvertex { Index Id; IntervalVector vertex; Interval lambda; + std::vector fcts; + /* note : double redirection here, a ``real'' facet may have several + Ids */ enum ddvertexstatus { - VERTEXNEW, + VERTEXNEW, /* created */ + VERTEXTODO, /* unknown status */ VERTEXLT, /* < (GT,LT) => new vertex */ VERTEXON, /* <,=,> (GT,LE) => for now, equivalent to LT */ VERTEXGE, /* = and possible small > keep the vertex. no change with (GT,GE) */ - VERTEXGT, /* remove vertex, add new vertices for links */ + VERTEXGT, /* vertex in stack for later treatment */ + VERTEXREM /* vertex GT treated, to be removed */ }; ddvertexstatus status; @@ -70,68 +76,67 @@ struct DDvertex { void removeLnk(const std::map::iterator &a); -#if 0 void addFct(Index a) { auto rev_it = this->fcts.rbegin(); while (rev_it != this->fcts.rend() && (*rev_it)>a) it++; if (rev_it!=this->fcts.rend() && (*rev_it)==a) return; this->fcts.insert(rev_it.base(),a); } -#endif }; struct DDlink { - std::map::iterator V1; - std::map::iterator V2; - std::unordered_set> fcts; + std::forward_list::iterator V1; + std::forward_list::iterator V2; +// std::unordered_set> fcts; - DDlink(Index dim, const std::map::iterator &V1, - const std::map::iterator &V2, - const std::vector &fcts) : V1(V1), V2(V2) { - this->fcts.insert(fcts); + DDlink(const std::forward_list::iterator &V1, + const std::forward_list::iterator &V2 + /*, const std::vector &fcts*/) : V1(V1), V2(V2) { +/* this->fcts.insert(fcts); */ } - void changeVertex(const std::map::iterator &Vold, - const std::map::iterator &Vnew) { + void changeVertex(const std::forward_list::iterator &Vold, + const std::forward_list::iterator &Vnew) { if (this->V1==Vold) { this->V1=Vnew; return; } this->V2=Vnew; } - const std::map::iterator &otherVertex - (const std::map::iterator &V) const { + const std::forward_list::iterator &otherVertex + (const std::forward_list::iterator &V) const { if (V==V1) return V2; return V1; } +#if 0 void addFct(const std::vector &fcts) { this->fcts.insert(fcts); } +#endif }; class DDbuildF2V { public: DDbuildF2V(Index dim, const IntervalVector &box, - const std::vector &eqconstraints); + const std::vector &eqconstraints, bool include_box=true); + +// void add_constraint_box(const IntervalVector &box); int add_facet(Index idFacet, const Facet& fct); friend std::ostream& operator<<(std::ostream& os, const DDbuildF2V& build); private: - std::map::iterator initDDvertex(Index id, - const IntervalVector &v, std::vector &fcts); Index initDDlinks(const std::map::iterator& V1, const std::map::iterator& V2, std::vector &fct); - std::map::iterator addDDvertex(const IntervalVector &v, - std::vector &fcts); - std::map::iterator addDDvertex(IntervalVector &&v, - std::vector &fcts); + std::forward_list::iterator addDDvertex(const IntervalVector &v); + std::map::iterator addDDvertex(IntervalVector &&v); std::map::iterator addVertexSon(const DDvertex &p1, DDvertex &p2, double rhs, Index idFacet); void removeVertex(Index Id, bool removeLnks); +#if 0 /* x_dim = eqcst x + rhs */ struct EqFacet { Index dim; @@ -166,20 +171,26 @@ class DDbuildF2V { static EqFacet build_EqFacet(const IntervalVector &bbox, const Row &cst, Interval &rhs); }; +#endif IntervalVector recompute_vertex(const IntervalVector &vect) const; Index dim; - Index fdim; - IntervalMatrix M_EQ1, M_EQ2; - std::vector eqfacets; - std::vector dim_facets; + Index fdim; /* dimension - nb independant equalities constraints */ + + /* if fdim lines; /* lines (for unbounded polyhedron) */ + /* rays can be handled as classical vertices */ - std::map vertices; - std::map total_links; - std::map> ineqfacets; + std::forward_list vertices; + std::forward_list total_links; + std::vector reffacets; /* indirection for facets */ - bool empty; + bool empty, flat; Index nbIdVertex; Index nbIdLinks; double tolerance = 1e-9; From c2a77e0893be43dae79b107980f1794682f455e6 Mon Sep 17 00:00:00 2001 From: damien-masse Date: Mon, 27 Oct 2025 18:11:13 +0100 Subject: [PATCH 07/26] better, but not complete --- src/extensions/polytope/CMakeLists.txt | 2 + src/extensions/polytope/codac2_Facet.h | 2 +- .../polytope/codac2_Polytope_util.cpp | 146 ++++++ .../polytope/codac2_Polytope_util.h | 49 ++ src/extensions/polytope/codac2_dd.cpp | 430 +++++++++++------- src/extensions/polytope/codac2_dd.h | 118 ++++- 6 files changed, 571 insertions(+), 176 deletions(-) create mode 100644 src/extensions/polytope/codac2_Polytope_util.cpp create mode 100644 src/extensions/polytope/codac2_Polytope_util.h diff --git a/src/extensions/polytope/CMakeLists.txt b/src/extensions/polytope/CMakeLists.txt index 8b1ff8b7b..6e15e989c 100644 --- a/src/extensions/polytope/CMakeLists.txt +++ b/src/extensions/polytope/CMakeLists.txt @@ -12,6 +12,8 @@ list(APPEND CODAC_POLYTOPE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/codac2_dd.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Polytope.h ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Polytope.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Polytope_util.h + ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Polytope_util.cpp ) ################################################################################ diff --git a/src/extensions/polytope/codac2_Facet.h b/src/extensions/polytope/codac2_Facet.h index d9e9cc46f..6e2ca503f 100644 --- a/src/extensions/polytope/codac2_Facet.h +++ b/src/extensions/polytope/codac2_Facet.h @@ -27,7 +27,7 @@ namespace codac2 { /** \brief Potential inclusion relation between sets * INCLUDES : A is included in B * MAYINCLUDE : A may be included in B - * NOTINCLUDE : A\B is not empty + * NOTINCLUDE : A\\B is not empty * INTERSECTS : A inter B is non empty * MAYINTERSECT : A inter B may be non empty * DISJOINT : A inter B is empty diff --git a/src/extensions/polytope/codac2_Polytope_util.cpp b/src/extensions/polytope/codac2_Polytope_util.cpp new file mode 100644 index 000000000..0ad83034b --- /dev/null +++ b/src/extensions/polytope/codac2_Polytope_util.cpp @@ -0,0 +1,146 @@ +/** + * \file codac2_Polytope_util.cpp utilities for polytope + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2025 + * \license This program is distributed under the terms of + * the GNU Lesser General Public License (LGPL). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "codac2_Index.h" +#include "codac2_Vector.h" +#include "codac2_Row.h" +#include "codac2_Facet.h" +#include "codac2_dd.h" +#include "codac2_Polytope_util.h" + + +namespace codac2 { + +/** read a .ine file and return a list of facets + * this is a very crude function, which assumes the format of the + * file is correct, and no real check is done */ +std::vector read_ineFile(const char *filename) { + std::ifstream fichier(filename); + assert(!fichier.fail()); + /* skip everything until begin */ + std::string line; + while (getline(fichier,line)) { + std::cout << line << std::endl; + if (line.compare("begin")==0) break; + } + assert(fichier.good()); + Index nbfacets, dim; + char dummy[50]; + fichier >> nbfacets >> dim >> dummy; + std::cout << nbfacets << " facettes " << dim << " dimensions" << std::endl; + dim = dim-1; /* non-normalised dimension */ + std::vector result; + result.reserve(nbfacets); + for (Index i=0;i> rhs; + for (Index j=0;j> row[j]; + } + row = -row; + result.push_back(Facet(row,rhs,false)); + } + fichier.close(); + return result; +} + +/* create a 2D facet in a 3D polyhedron, return the number of vertices built. + fct = -1 : any facet */ +Index fill_3Dfacet(const DDbuildF2V &build, + std::vector &tofill, Index fct, double bound) { + const std::forward_list &vertices=build.get_vertices(); + std::forward_list::const_iterator it=vertices.begin(); + while (it != vertices.end()) { + if (it->vertex[0]!=0.0 && + (fct==-1 || std::find(it->fcts.begin(), + it->fcts.end(),fct)!=it->fcts.end())) + break; + ++it; + } + if (it==vertices.end()) return 0; + + std::vector::const_iterator> used; + used.push_back(it); + std::cout << "add vert : " << it->Id << "\n"; + Vector vt = build.compute_vertex(it->vertex).mid(); + tofill.push_back(vt); + while (it != vertices.end()) { + std::forward_list::const_iterator it2; + Index i=0; + while (i<(Index)it->links.size()) { + it2 = it->links[i]; + if ((fct==-1 || + std::find(it2->fcts.begin(), + it2->fcts.end(),fct)!=it2->fcts.end()) + && (std::find(used.begin(),used.end(),it2)==used.end())) break; + i++; + } + if (i==(Index)it->links.size()) break; + it=it2; + if (it->vertex[0]==0.0) /* ray */ { + vt += bound*build.compute_vertex(it->vertex).mid(); + } else { + vt = build.compute_vertex(it->vertex).mid(); + } + tofill.push_back(vt); + used.push_back(it); + std::cout << "add vert : " << it->Id << "\n"; + } + return (Index)tofill.size(); +} + +std::vector> build_3Dfacets(const DDbuildF2V &build, double bound) { + assert(build.get_dim()==3); + if (build.is_empty()) return std::vector>(); + if (build.get_fdim()==0) { /* the only point has no coordinate */ + std::vector> result(1); + Vector v = Vector::constant(1,1.0); + result[0].push_back((build.get_M_EQ()).mid()*v); + return result; + } else + if (build.get_fdim()<3) { /* line or face are managed as a single face */ + std::vector> result(1); + std::vector &face = result[0]; + fill_3Dfacet(build,face,-1,bound); + /* look for a initial vertex */ + return result; + } + std::vector> result; + for (Index i=0;i<(Index)build.get_reffacets().size();i++) { + if (build.get_reffacets()[i]==0) continue; + std::vector nfac; + Index nbV = fill_3Dfacet(build, nfac, i, bound); + std::cout << "facet build, " << nbV << " vertices.\n"; + if (nbV<3) continue; + result.push_back(nfac); + } + return result; +} + +} + + diff --git a/src/extensions/polytope/codac2_Polytope_util.h b/src/extensions/polytope/codac2_Polytope_util.h new file mode 100644 index 000000000..fc2f8d93c --- /dev/null +++ b/src/extensions/polytope/codac2_Polytope_util.h @@ -0,0 +1,49 @@ +/** + * \file codac2_Polytope_util.h utilities for polytope + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2025 + * \license This program is distributed under the terms of + * the GNU Lesser General Public License (LGPL). + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "codac2_Index.h" +#include "codac2_Vector.h" +#include "codac2_Row.h" +#include "codac2_Facet.h" +#include "codac2_dd.h" + + +namespace codac2 { + +/** read a .ine file and return a list of facets + * this is a very crude function, which assumes the format of the + * file is correct + * @param filename name of the file */ +std::vector read_ineFile(const char *filename); + +/** construct the list of facets of a 3D-polyhedron from the buildF2V + * structure + * use the mid of each vertex. For rays/line use an arbitrary bound + * @param build the buildF2V structure */ +std::vector> build_3Dfacets(const DDbuildF2V &build, double bound=50.0); + +} + diff --git a/src/extensions/polytope/codac2_dd.cpp b/src/extensions/polytope/codac2_dd.cpp index ef6149675..3f94f4a35 100644 --- a/src/extensions/polytope/codac2_dd.cpp +++ b/src/extensions/polytope/codac2_dd.cpp @@ -17,13 +17,15 @@ #include #include #include +#include +#include #include "codac2_Index.h" #include "codac2_Vector.h" #include "codac2_Row.h" #include "codac2_Facet.h" #include "codac2_dd.h" -#include "codac2_IntvFulPivLU.h" +#include "codac2_IntvFullPivLU.h" #define DEBUG_CODAC2_DD @@ -33,7 +35,7 @@ namespace codac2 { /****** PairMaxSet *******/ /*************************/ template -struct PairMaxSets { +struct PairMaxSets { T a,b; std::vector elems; @@ -50,6 +52,18 @@ struct PairMaxSets { } } + PairMaxSets(T a, T b, const std::vector e1, const std::vector e2, const Index rejectF) : a(a), b(b) { + Index i=0,j=0; + while(i<(Index)e1.size() && j<(Index)e2.size()) { + if (e1[i]==e2[j]) { + if (e1[i]!=rejectF) + elems.push_back(e1[i]); + i++; j++; + } else if (e1[i]a,p2.a); std::swap(this->b,p2.b); @@ -97,7 +111,7 @@ DDbuildF2V::DDbuildF2V(Index dim, const IntervalVector &box, const std::vector &eqconstraints, bool include_box) : dim(dim), fdim(dim), bbox(box), empty(false), flat(false), - nbInVertex(0), nbInLinks(0) { + nbIn(0) { int nb_eq_dim= eqconstraints.size(); if (include_box) { for (int i=0;iadd_constraint_box(bbox); } -std::list::iterator DDbuildF2V::addDDvertex(const IntervalVector &v) { +std::forward_list::iterator DDbuildF2V::addDDvertex(const IntervalVector &v) { nbIn++; vertices.emplace_front(DDvertex(v,nbIn)); return vertices.begin(); } -std::list::iterator DDbuildF2V::addDDvertex(IntervalVector &&v) { +std::forward_list::iterator DDbuildF2V::addDDvertex(IntervalVector &&v) { nbIn++; vertices.emplace_front(DDvertex(v,nbIn)); return vertices.begin(); } +inline std::vector fctSon(Index fct,const std::vector fcts1, + const std::vector fcts2) { + std::vector nfcts; + Index a1=0; Index a2=0; + bool idFacetAdded=(fct<0); + while (a1<(Index)fcts1.size() && a2<(Index)fcts2.size()) { + if (fcts1[a1]fct && !idFacetAdded) { + nfcts.push_back(fct); + idFacetAdded=true; + } + nfcts.push_back(fcts1[a1]); + a1++; a2++; + } + } + if (!idFacetAdded) nfcts.push_back(fct); + + return nfcts; +} + +#if 0 /* p1 has lambda>rhs, p2 has lambdafdim); + return vect.tail(this->fdim)/vect[0]; + } + if (vect[0]==0.0) return this->M_EQ*vect; + return this->M_EQ*(vect/vect[0]); +} +struct CmpDD { + bool operator()(const std::forward_list::iterator &a, + const std::forward_list::iterator &b) { + return (b->vertex[0].lb()*a->lambda.lb()<=a->vertex[0].lb()*b->lambda.lb()); + } +}; + +bool DDbuildF2V::addDDlink(const std::forward_list::iterator& V1, + const std::forward_list::iterator& V2) { + if (V1->addLnk(V2,true)) return V2->addLnk(V1,false); + return false; +// return (V1->addLnk(V2,true) && V2->addLnk(V1,false)); +} int DDbuildF2V::add_facet(Index idFacet, const Facet& fct) { if (empty) return 0; @@ -228,7 +290,7 @@ int DDbuildF2V::add_facet(Index idFacet, const Facet& fct) { /* then consider lines */ Index bestline=-1; double maxmig=0.0; - for (Index i=0;iaddFct(a); } + /* ce nouveau sommet a une face spéciale, "négation" de la contrainte, + de code 0 */ + newVertex->addFct(reffacets.size()); + reffacets.push_back(0); + /* suppression de la ligne */ + this->lines.pop_back(); return 1; } /* end of line treatments , the following is when no line is available */ + int nbGT=0; + bool notempty=false; + for (DDvertex &vt : vertices) { + vt.lambda = facet.dot(vt.vertex); + notempty |= (vt.lambda.lb()<=0.0); + if (vt.lambda.ub()<0.0) { + vt.status = DDvertex::VERTEXLT; + continue; + } + if (vt.lambda.lb()<0.0) { + vt.status = DDvertex::VERTEXON; + continue; + } + if (vt.lambda.lb()tolerance) { + vt.status = DDvertex::VERTEXGE; + continue; + } + nbGT++; + vt.status=DDvertex::VERTEXGT; + } + if (!notempty) { this->empty=true; vertices.clear(); return -1; } + if (nbGT==0) return 0; std::forward_list::iterator itVertex = vertices.begin(); - std::vector::iterator> stack; + std::priority_queue::iterator, + std::vector::iterator>, + CmpDD> stack; Index refFacet=-1; while (itVertex!=vertices.end()) { +// bool inBoundary; std::forward_list::iterator actVertex; if (stack.size()>0) { - actVertex = stack.back(); - stack.pop_back(); - if (actVertex->status!=VERTEXGT) continue; + actVertex = stack.top(); + stack.pop(); + if (actVertex->status!=DDvertex::VERTEXSTACK) continue; +// inBoundary=true; } else { actVertex=itVertex; ++itVertex; - if (actVertex->status!=VERTEXTODO) continue; - actVertex->lambda = facet.dot(actVertex->vertex); - if (actVertex->lambda.ub()<0.0) { - actVertex->status = VERTEXLT; - continue; - } - if (actVertex->lambda.lb()<0.0) { - actVertex->status = VERTEXON; - continue; - } - if (actVertex->lambda.lb()tolerance) { - actVertex->status = VERTEXGE; - continue; - } - retFacet = reffacets.size(); /* new facet identification */ + if (actVertex->status!=DDvertex::VERTEXGT) continue; + refFacet = reffacets.size(); /* new facet identification */ reffacets.push_back(idFacet); +// inBoundary=false; } - actVertex->status = VERTEXREM; - - } - - - for (std::pair &v : vertices) { - DDvertex &d = v.second; - d.lambda = rowI*d.vertex; - if (d.lambda.ub()::iterator it = vertices.rbegin(); - it!=vertices.rend();++it) { - DDvertex &d = it->second; - std::cout << " vertex: " << d.Id << " " << d.status << "\n"; - if (d.status==DDvertex::VERTEXLT || - d.status==DDvertex::VERTEXON || - d.status==DDvertex::VERTEXNEW) - continue; - /* check the links towards LT and ON, if the facet is a valid - intersection */ - if (d.status==DDvertex::VERTEXGT) { - bool oktoremove=true; - for (auto &l : d.links) { - if (l==total_links.end()) continue; - auto &u = l->second.otherVertex(it); - if (u==vertices.end()) { - total_links.remove(l); - l=total_links.end(); - continue; - } - if (u->second.status!=VERTEXON - && u->second.status!=VERTEXLT) continue; - } - } - if (d.status==DDvertex::VERTEXGE) { - d.addFct(idFacet); - continue; - } - for (auto &l : d.links) { - if (l==total_links.end()) continue; - auto &u = l->second.otherVertex(it); - auto &d2 = u->second; - if (d2.status==DDvertex::VERTEXGE || d2.status==DDvertex::VERTEXGT) { - l->second.remove(it); - continue; - } - if (d2.status!=DDvertex::VERTEXLT && d2.status!=DDvertex::VERTEXON) { - continue; /* do nothing */ - } #ifdef DEBUG_CODAC2_DD - std::cout << " addVertex " << d.Id << " " << d2.Id << " " << d.status << " " << d2.status << std::endl; - std::cout << " (" << d.lambda << " " << d2.lambda << ") " << std::endl; + std::cout << " elimination sommet : " << actVertex->Id << ", " << actVertex->links.size() << "liens \n"; #endif + actVertex->status = DDvertex::VERTEXREM; + std::vector::iterator, + std::vector>> + adjacentVertices; + for (std::forward_list::iterator lnk : actVertex->links) { + DDvertex &destlink = *lnk; #ifdef DEBUG_CODAC2_DD - Index newId = this->addVertexSon(d,d2,rhs,idFacet); - std::cout << " ... done " << newId <addVertexSon(d,d2,idFacet); + std::cout << " lien : " << destlink.Id << "\n"; #endif - cnt++; - } - this->removeVertex(d.Id,false); - } - /*construction of new links for the eq vertices */ + if (destlink.status==DDvertex::VERTEXGT) { + destlink.status=DDvertex::VERTEXSTACK; + stack.push(lnk); + } + if (destlink.status==DDvertex::VERTEXGE || + destlink.status==DDvertex::VERTEXSTACK) { + std::cout << " GE ou STACK " << destlink.lambda << "\n"; + /* add the facet to the vertex, if it does not already exists */ + adjacentVertices.push_back(std::pair(lnk,fctSon(-1,lnk->fcts,actVertex->fcts))); + destlink.addFct(refFacet); + destlink.removeLnk(actVertex); + continue; + } + assert(destlink.status!=DDvertex::VERTEXREM); + /* should not happen */ + IntervalVector partV=destlink.vertex; + if (destlink.status==DDvertex::VERTEXON) { + IntervalRow x1(facet); + MulOp::bwd(Interval(-oo,0.0),x1,partV); + if (partV.is_empty()) { + std::cout << " Sommet conserve " << facet << " " << destlink.vertex << " " << destlink.lambda << "\n"; +#if 0 + IntervalVector partV2=destlink.vertex; + IntervalRow x2(facet); + MulOp::bwd(Interval(0.0,+oo),x2,partV2); + std::cout << " reste " << partV2 << "\n"; +#endif + adjacentVertices.push_back(std::pair(lnk,fctSon(-1,lnk->fcts,actVertex->fcts))); + destlink.addFct(refFacet); + destlink.removeLnk(actVertex); + continue; + } + } + if (-destlink.lambda.lb()/actVertex->lambda.lb()lambda.lb() << "\n"; + /* extend the vertex, and add fct */ + adjacentVertices.push_back(std::pair(lnk,fctSon(-1,lnk->fcts,actVertex->fcts))); + destlink.vertex |= + (partV - + (destlink.lambda.lb()/actVertex->lambda)*actVertex->vertex); + destlink.addFct(refFacet); + destlink.removeLnk(actVertex); + continue; + } + /* creation of a new vertex */ + IntervalVector newV = -destlink.lambda.lb()*actVertex->vertex + + actVertex->lambda.lb()*partV; + assert(!newV.is_empty()); +#if 0 + if (newV.is_empty()) { + std::cout << " newV : " << newV << "\n"; + std::cout << destlink.lambda << " " << actVertex->vertex << " " << actVertex->lambda << " " << partV << "\n"; + } +#endif + std::forward_list::iterator newVx = + this->addDDvertex(newV); #ifdef DEBUG_CODAC2_DD - std::cout << " (" << idFacet << ")" << " construction of PairMaxSet " << cnt << std::endl; + std::cout << " creation new V " << newV << "id : " << newVx->Id << "\n"; #endif - std::vector maxset; - for (std::map::iterator it = vertices.begin(); - it!=vertices.end();++it) { - DDvertex &d = it->second; - if (d.status==DDvertex::VERTEXGE || d.status==DDvertex::VERTEXNEW) { - for (std::map::iterator it2 = next(it); - it2!=vertices.end(); ++it2) { - DDvertex &d2 = it2->second; - if (d2.status==DDvertex::VERTEXGE - || d2.status==DDvertex::VERTEXNEW) { - PairMaxSets actuel(d.Id,d2.Id,d.fcts,d2.fcts); -// if (actuel.elems.size()lambda=0.0; + destlink.changeLnk(actVertex,newVx); + newVx->addLnk(lnk,false); + newVx->fcts = fctSon(-1,destlink.fcts,actVertex->fcts); + adjacentVertices.push_back(std::pair(newVx,newVx->fcts)); + newVx->addFct(refFacet); + newVx->status=DDvertex::VERTEXGE; + } +#ifdef DEBUG_CODAC2_DD + std::cout << " fin traitement liens : " << adjacentVertices.size() << "\n"; +#endif + std::vector::iterator>> maxset; + for (Index i=0;i<=(Index)adjacentVertices.size()-1;i++) { +#ifdef DEBUG_CODAC2_DD + std::cout << " sommet : " << adjacentVertices[i].first->Id << " : "; + for (Index u : adjacentVertices[i].second) { + std::cout << u << ","; + } + std::cout << std::endl; +#endif + for (Index j=i+1;j<(Index)adjacentVertices.size();j++) { + PairMaxSets::iterator> + actuel(adjacentVertices[i].first, + adjacentVertices[j].first, + adjacentVertices[i].second, + adjacentVertices[j].second,refFacet); + if (actuel.elems.size()Id << "\n"; +#endif + for (const PairMaxSets::iterator> + &pm : maxset) { +#ifdef DEBUG_CODAC2_DD + if (pm.elems.size()Id << " " << pm.b->Id << " " << pm.elems.size() << std::endl; } - } - } - } - for (const PairMaxSets &pm : maxset) { + std::cout << pm.a->Id << " " << pm.b->Id << "\n"; +#endif + bool res = this->addDDlink(pm.a,pm.b); #ifdef DEBUG_CODAC2_DD - std::cout << " maxset : " << pm.a << " " << pm.b << " " << pm.elems.size() << std::endl; + std::cout << (res ? " added " : " already here ") << "\n"; #endif - vertices.at(pm.a).addLnk(pm.b); - vertices.at(pm.b).addLnk(pm.a); + + } } - return cnt; + + vertices.remove_if([](const DDvertex &v) + { return v.status==DDvertex::VERTEXREM; }); + return 1; } + /* TODO : make a specific computation */ +#if 0 bool DDbuildF2V::vertex_compatible(const std::vector &fcts, const IntervalVector &v) { IntervalMatrix M = IntervalMatrix::zero(fcts.size(), v.size()); @@ -463,24 +576,36 @@ IntervalVector DDbuildF2V::recompute_vertex(const IntervalVector &vect) const { } return v; } +#endif + +IntervalVector normalise(const IntervalVector &v) { + IntervalVector res(v); + if (res[0].lb()<=0.0) return res; + return res/res[0].lb(); +} std::ostream& operator<<(std::ostream& os, const DDbuildF2V& build) { - os << "DDbuildF2V (" << build.vertices.size() << " vtx) : " << std::endl; - for (const auto &ivt : build.vertices) { - auto &vt = ivt.second; - os << vt.Id << " : " << vt.vertex << std::endl; - os << " fct: ("; - for (Index a : vt.fcts) { - os << a << ","; + os << "DDbuildF2V (" << /*build.vertices.size() <<*/ " vtx) : " << std::endl; + os << " " << build.lines.size() << " lines : " << std::endl; + for (const auto &l : build.lines) { + os << " " << l << std::endl; + } + Index nbvx=0, nbrays=0; + for (const auto &vt : build.vertices) { + if (vt.vertex[0]!=0) nbvx++; else nbrays++; + os << " " << vt.Id << " : " << normalise(vt.vertex) << std::endl; + os << " fct : ("; + for (const auto &a : vt.fcts) { + os << a << ":" << build.reffacets[a] << ","; } os << ")" << std::endl; os << " lnks: ("; - for (Index a : vt.links) { - os << a << ","; + for (const auto &a : vt.links) { + os << a->Id << ","; } os << ")" << std::endl; } - os << "endDDbuildF2V" << std::endl; + os << "endDDbuildF2V ( "<< nbrays << " rays, " << nbvx << " vertices, " << (nbrays+nbvx) << " total)" << std::endl; return os; } @@ -488,6 +613,7 @@ std::ostream& operator<<(std::ostream& os, const DDbuildF2V& build) { /*** DDbuildF2V::EqFacet *****/ /*****************************/ /* management of the equality constraints for DDbuildF2V */ +#if 0 void DDbuildF2V::EqFacet::adapt_eqconstraint(const IntervalVector &bbox, Row &cst, Interval &rhs) const { Interval alpha(cst[dim]); cst[dim]=0.0; @@ -531,7 +657,7 @@ void DDbuildF2V::EqFacet::adapt_box(IntervalVector &bbox) const { } bbox[this->dim]=0; } - +#endif /*****************************/ /*** DDbuildV2F::EqFacet *****/ @@ -721,7 +847,7 @@ int DDbuildV2F::add_vertex(Index idVertex, const IntervalVector& vertex) { } /*construction of new links for the eq facets */ - std::vector maxset; + std::vector> maxset; for (std::map::iterator it = facets.begin(); it!=facets.end();++it) { DDfacet &d = it->second; @@ -760,7 +886,7 @@ int DDbuildV2F::add_vertex(Index idVertex, const IntervalVector& vertex) { } } } - for (const PairMaxSets &pm : maxset) { + for (const PairMaxSets &pm : maxset) { // std::cout << " maxset : " << pm.a << " " << pm.b << std::endl; facets.at(pm.a).addLnk(pm.b); facets.at(pm.b).addLnk(pm.a); diff --git a/src/extensions/polytope/codac2_dd.h b/src/extensions/polytope/codac2_dd.h index e0995579e..39da26dbf 100644 --- a/src/extensions/polytope/codac2_dd.h +++ b/src/extensions/polytope/codac2_dd.h @@ -32,7 +32,10 @@ namespace codac2 { + +#if 0 struct DDlink; /* link between two vertices */ +#endif struct DDvertex { Index Id; @@ -44,46 +47,89 @@ struct DDvertex { enum ddvertexstatus { VERTEXNEW, /* created */ - VERTEXTODO, /* unknown status */ VERTEXLT, /* < (GT,LT) => new vertex */ VERTEXON, /* <,=,> (GT,LE) => for now, equivalent to LT */ VERTEXGE, /* = and possible small > keep the vertex. no change with (GT,GE) */ - VERTEXGT, /* vertex in stack for later treatment */ + VERTEXGT, /* vertex GT, untreated and not in stack */ + VERTEXSTACK, /* vertex in stack for later treatment */ VERTEXREM /* vertex GT treated, to be removed */ }; ddvertexstatus status; - std::vector::iterator> links; + std::vector::iterator> links; DDvertex(const IntervalVector &v, Index Id) : Id(Id), vertex(v), status(VERTEXNEW) { + reduce_vector(); } DDvertex(IntervalVector &&v, Index Id, - std::vector::iterator> &nlinks) : + std::vector::iterator> &nlinks) : Id(Id), vertex(v), status(VERTEXNEW) { this->links.swap(nlinks); + reduce_vector(); } - void addLnk(const std::map::iterator &a) { + bool addLnk(const std::forward_list::iterator &a, + bool check=false) { + if (check) { + for (auto b : links) { + if (a==b) return false; + } + } this->links.push_back(a); + return true; } - void addLnkVertex(const std::map::iterator &V1, - const std::vector &fcts); +// void addLnkVertex(const std::forward_list::iterator &V1); + + void removeLnk(const std::forward_list::iterator &a) { + for (Index i=0;i<(Index)links.size();i++) { + if (links[i]==a) { + links[i]=links[links.size()-1]; + links.pop_back(); + return; + } + } + std::cout << "not found !!???\n"; + } - void removeLnk(const std::map::iterator &a); + void changeLnk(const std::forward_list::iterator &a, + const std::forward_list::iterator &b) { + for (Index i=0;i<(Index)links.size();i++) { + if (links[i]==a) { links[i]=b; return; } + } + /* should not happel */ + links.push_back(b); + } - void addFct(Index a) { + bool addFct(Index a) { auto rev_it = this->fcts.rbegin(); - while (rev_it != this->fcts.rend() && (*rev_it)>a) it++; - if (rev_it!=this->fcts.rend() && (*rev_it)==a) return; + while (rev_it != this->fcts.rend() && (*rev_it)>a) rev_it++; + if (rev_it!=this->fcts.rend() && (*rev_it)==a) return false; this->fcts.insert(rev_it.base(),a); + return true; + } + + inline void reduce_vector() { + int fx; + frexp(vertex[0].mid(),&fx); + for (Index i=1;ifx) fx=fx2; + } + if (std::abs(fx)<10) return; + double mult=exp2(-fx); + for (Index i=0;i::iterator V1; std::forward_list::iterator V2; @@ -113,6 +159,7 @@ struct DDlink { } #endif }; +#endif class DDbuildF2V { public: @@ -123,18 +170,28 @@ class DDbuildF2V { int add_facet(Index idFacet, const Facet& fct); + IntervalVector compute_vertex(const IntervalVector &vect) const; + Index get_dim() const; + Index get_fdim() const; + + bool is_empty() const; + const IntervalMatrix &get_M_EQ() const; + + const std::vector &get_lines() const; + const std::forward_list &get_vertices() const; + const std::vector &get_reffacets() const; + friend std::ostream& operator<<(std::ostream& os, const DDbuildF2V& build); private: - Index initDDlinks(const std::map::iterator& V1, - const std::map::iterator& V2, - std::vector &fct); + bool addDDlink(const std::forward_list::iterator& V1, + const std::forward_list::iterator& V2); std::forward_list::iterator addDDvertex(const IntervalVector &v); - std::map::iterator addDDvertex(IntervalVector &&v); - std::map::iterator addVertexSon(const DDvertex &p1, - DDvertex &p2, double rhs, Index idFacet); - void removeVertex(Index Id, bool removeLnks); + std::forward_list::iterator addDDvertex(IntervalVector &&v); +/* std::map::iterator addVertexSon(const DDvertex &p1, + DDvertex &p2, double rhs, Index idFacet); */ +/* void removeVertex(Index Id, bool removeLnks); */ #if 0 /* x_dim = eqcst x + rhs */ @@ -173,29 +230,44 @@ class DDbuildF2V { }; #endif - IntervalVector recompute_vertex(const IntervalVector &vect) const; - Index dim; Index fdim; /* dimension - nb independant equalities constraints */ + IntervalVector bbox; /* if fdim lines; /* lines (for unbounded polyhedron) */ /* rays can be handled as classical vertices */ std::forward_list vertices; +#if 0 std::forward_list total_links; +#endif std::vector reffacets; /* indirection for facets */ bool empty, flat; - Index nbIdVertex; - Index nbIdLinks; + Index nbIn; double tolerance = 1e-9; }; +inline Index DDbuildF2V::get_dim() const { return this->dim; } +inline Index DDbuildF2V::get_fdim() const { return this->fdim; } +inline bool DDbuildF2V::is_empty() const { return this->empty; } +inline const IntervalMatrix &DDbuildF2V::get_M_EQ() const { return this->M_EQ; } +inline const std::vector &DDbuildF2V::get_lines() const + { return this->lines; } +inline const std::forward_list &DDbuildF2V::get_vertices() const + { return this->vertices; } +inline const std::vector &DDbuildF2V::get_reffacets() const + { return this->reffacets; } + + struct DDfacet { Index Id; Facet facet; From b300c48429dbee9df3f508b3f21f742b62ed919c Mon Sep 17 00:00:00 2001 From: damien-masse Date: Mon, 27 Oct 2025 18:12:21 +0100 Subject: [PATCH 08/26] example --- examples/11_polytope/CMakeLists.txt | 40 +++++++++++++++++ examples/11_polytope/main-translate-ine.cpp | 49 +++++++++++++++++++++ examples/11_polytope/main.cpp | 40 +++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 examples/11_polytope/CMakeLists.txt create mode 100644 examples/11_polytope/main-translate-ine.cpp create mode 100644 examples/11_polytope/main.cpp diff --git a/examples/11_polytope/CMakeLists.txt b/examples/11_polytope/CMakeLists.txt new file mode 100644 index 000000000..dfb06629e --- /dev/null +++ b/examples/11_polytope/CMakeLists.txt @@ -0,0 +1,40 @@ +# ================================================================== +# codac / basics example - cmake configuration file +# ================================================================== + +cmake_minimum_required(VERSION 3.5) +project(codac_example LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Adding IBEX + +# In case you installed IBEX in a local directory, you need +# to specify its path with the CMAKE_PREFIX_PATH option. +# set(CMAKE_PREFIX_PATH "~/ibex-lib/build_install") + +find_package(IBEX REQUIRED) +ibex_init_common() # IBEX should have installed this function +message(STATUS "Found IBEX version ${IBEX_VERSION}") + +# Adding Codac + +# In case you installed Codac in a local directory, you need +# to specify its path with the CMAKE_PREFIX_PATH option. +# set(CMAKE_PREFIX_PATH "~/codac/build_install") + +find_package(CODAC REQUIRED) +message(STATUS "Found Codac version ${CODAC_VERSION}") + +# Compilation + +if(FAST_RELEASE) + add_compile_definitions(FAST_RELEASE) + message(STATUS "You are running Codac in fast release mode. (option -DCMAKE_BUILD_TYPE=Release is required)") +endif() + +add_executable(${PROJECT_NAME} main.cpp) +target_compile_options(${PROJECT_NAME} PUBLIC ${CODAC_CXX_FLAGS}) +target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${CODAC_INCLUDE_DIRS}) +target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES} Ibex::ibex) \ No newline at end of file diff --git a/examples/11_polytope/main-translate-ine.cpp b/examples/11_polytope/main-translate-ine.cpp new file mode 100644 index 000000000..fb54ba7ca --- /dev/null +++ b/examples/11_polytope/main-translate-ine.cpp @@ -0,0 +1,49 @@ +// Author : Maël GODARD +// Adapted from CAPD examples + +#include +#include +#include +#include + +using namespace std; +using namespace codac2; + + +struct FacetComp { +bool operator()(const Facet &f1, const Facet &f2) const { + for (int i=0;i fc = read_ineFile("sampleh8.ine"); + std::cout << "fichier lu avec " << fc.size() << " facettes.\n"; + IntervalVector box = IntervalVector::constant(fc[0].row.size(), + Interval(-10,10)); + std::vector vide; + DDbuildF2V build2(box.size(),box,vide); + std::cout << "Initial build : " << build2 << std::endl; + Index i=0; +/* + for (const auto &f : fc) { + std::cout << "add facet : " << f << std::endl; + build2.add_facet(++i,f); + std::cout << build2; + } +*/ + for (int i=0;i +#include +#include +#include + +using namespace std; +using namespace codac2; + +int main() +{ + std::cout << std::scientific << std::setprecision(9); + + + std::vector fc = read_ineFile("cubocta.ine"); + std::cout << "fichier lu avec " << fc.size() << " facettes.\n"; + IntervalVector box = IntervalVector::constant(fc[0].row.size(), + Interval(-10,10)); + std::vector vide; + DDbuildF2V build(box.size(),box,vide); + Index i=0; + for (int i=0;i> facets3D=build_3Dfacets(build); + std::cout << "nb facets : " << facets3D.size() << "\n"; + + Figure3D fig_poly("3D polyhedron"); + + Vector center = Vector::zero(3); + Matrix transfo = Matrix::Identity(3,3); + for (const std::vector &vec : facets3D) { + fig_poly.draw_polygon(center,transfo,vec); + } + + return 0; +} From ae60ce33522f03b63536948cd28e8fd6218bcd68 Mon Sep 17 00:00:00 2001 From: damien-masse Date: Tue, 4 Nov 2025 13:46:39 +0100 Subject: [PATCH 09/26] Change --- examples/11_polytope/CMakeLists.txt | 12 +- .../{main.cpp => main-3Dgraphics.cpp} | 24 +- examples/11_polytope/main-F2V.cpp | 34 ++ examples/11_polytope/main-V2F.cpp | 33 ++ examples/11_polytope/main-translate-ine.cpp | 49 --- src/extensions/polytope/CMakeLists.txt | 4 +- src/extensions/polytope/clp/codac2_clp.cpp | 159 ++++---- src/extensions/polytope/clp/codac2_clp.h | 37 +- src/extensions/polytope/codac2_Facet.cpp | 244 +++++++++++- src/extensions/polytope/codac2_Facet.h | 363 +++++++++++++++-- src/extensions/polytope/codac2_Polytope.cpp | 2 + src/extensions/polytope/codac2_Polytope.h | 17 +- .../{codac2_dd.cpp => codac2_Polytope_dd.cpp} | 365 ++++++++++-------- .../{codac2_dd.h => codac2_Polytope_dd.h} | 107 +++-- .../polytope/codac2_Polytope_util.cpp | 44 ++- .../polytope/codac2_Polytope_util.h | 12 +- 16 files changed, 1096 insertions(+), 410 deletions(-) rename examples/11_polytope/{main.cpp => main-3Dgraphics.cpp} (51%) create mode 100644 examples/11_polytope/main-F2V.cpp create mode 100644 examples/11_polytope/main-V2F.cpp delete mode 100644 examples/11_polytope/main-translate-ine.cpp rename src/extensions/polytope/{codac2_dd.cpp => codac2_Polytope_dd.cpp} (72%) rename src/extensions/polytope/{codac2_dd.h => codac2_Polytope_dd.h} (75%) diff --git a/examples/11_polytope/CMakeLists.txt b/examples/11_polytope/CMakeLists.txt index dfb06629e..be8dae032 100644 --- a/examples/11_polytope/CMakeLists.txt +++ b/examples/11_polytope/CMakeLists.txt @@ -34,7 +34,11 @@ if(FAST_RELEASE) message(STATUS "You are running Codac in fast release mode. (option -DCMAKE_BUILD_TYPE=Release is required)") endif() -add_executable(${PROJECT_NAME} main.cpp) -target_compile_options(${PROJECT_NAME} PUBLIC ${CODAC_CXX_FLAGS}) -target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${CODAC_INCLUDE_DIRS}) -target_link_libraries(${PROJECT_NAME} PUBLIC ${CODAC_LIBRARIES} Ibex::ibex) \ No newline at end of file +set(PROGS main-F2V main-3Dgraphics main-V2F) + +foreach(PROG ${PROGS}) + add_executable(${PROG} ${PROG}.cpp) + target_compile_options(${PROG} PUBLIC ${CODAC_CXX_FLAGS}) + target_include_directories(${PROG} SYSTEM PUBLIC ${CODAC_INCLUDE_DIRS}) + target_link_libraries(${PROG} PUBLIC ${CODAC_LIBRARIES} Ibex::ibex) +endforeach() diff --git a/examples/11_polytope/main.cpp b/examples/11_polytope/main-3Dgraphics.cpp similarity index 51% rename from examples/11_polytope/main.cpp rename to examples/11_polytope/main-3Dgraphics.cpp index 18b6b3838..849cfb91b 100644 --- a/examples/11_polytope/main.cpp +++ b/examples/11_polytope/main-3Dgraphics.cpp @@ -1,28 +1,32 @@ -// Author : Maël GODARD -// Adapted from CAPD examples +// Author : Damien Massé +// Read a description of 3D polyhedron in inequalities form (.ine file) +// and draw it #include #include #include -#include +#include using namespace std; using namespace codac2; -int main() +int main(int argc, char *argv[]) { std::cout << std::scientific << std::setprecision(9); - std::vector fc = read_ineFile("cubocta.ine"); - std::cout << "fichier lu avec " << fc.size() << " facettes.\n"; - IntervalVector box = IntervalVector::constant(fc[0].row.size(), + const char *namefile = "cubocta.ine"; + if (argc>1) namefile= argv[1]; + std::shared_ptr fc = + std::make_shared(read_ineFile(namefile)); + std::cout << "fichier lu avec " << fc->nbfcts() << " facettes.\n"; + IntervalVector box = IntervalVector::constant((*fc)[0]->first.row.size(), Interval(-10,10)); std::vector vide; - DDbuildF2V build(box.size(),box,vide); + DDbuildF2V build(box.size(),box,fc,false); Index i=0; - for (int i=0;inbfcts();i++) { + build.add_facet(i); } std::cout << "built \n"; std::vector> facets3D=build_3Dfacets(build); diff --git a/examples/11_polytope/main-F2V.cpp b/examples/11_polytope/main-F2V.cpp new file mode 100644 index 000000000..e23c148b3 --- /dev/null +++ b/examples/11_polytope/main-F2V.cpp @@ -0,0 +1,34 @@ +// Author : Damien Massé +// Transformation facets to vertex from a .ine file + +#include +#include +#include +#include +#include + +using namespace std; +using namespace codac2; + +int main(int argc, char *argv[]) +{ + std::cout << std::scientific << std::setprecision(9); + + + const char *namefile = "cross12.ine"; + if (argc>1) namefile= argv[1]; + std::shared_ptr fc = + std::make_shared(read_ineFile(namefile)); + std::cout << "fichier lu avec " << fc->nbfcts() << " facettes.\n"; + IntervalVector box = IntervalVector::constant((*fc)[0]->first.row.size(), + Interval(-10,10)); + DDbuildF2V build2(box.size(),box,fc,false); + std::cout << "Initial build : " << build2 << std::endl; + for (int i=0;inbfcts();i++) { + std::cout << "add facet " << (i+1) << " : " << (*(*fc)[i]) << std::endl; + build2.add_facet(i); +// std::cout << build2; + } + std::cout << build2; + return 0; +} diff --git a/examples/11_polytope/main-V2F.cpp b/examples/11_polytope/main-V2F.cpp new file mode 100644 index 000000000..bedc6bb9e --- /dev/null +++ b/examples/11_polytope/main-V2F.cpp @@ -0,0 +1,33 @@ +// Author : Damien Massé +// Transformation vertices -> facets from a .ext file + +#include +#include +#include +#include +#include + +using namespace std; +using namespace codac2; + + +int main(int argc, char *argv[]) +{ + std::cout << std::scientific << std::setprecision(9); + + + const char *namefile = "irbox20-4.ext"; + if (argc>1) namefile= argv[1]; + std::vector vx = read_extFile(namefile); + std::cout << "File read with " << vx.size() << " vertices.\n"; + std::vector vide; + DDbuildV2F build2(1,vx[0]); + std::cout << "Initial build : " << build2 << std::endl; + for (int i=1;i -#include -#include -#include - -using namespace std; -using namespace codac2; - - -struct FacetComp { -bool operator()(const Facet &f1, const Facet &f2) const { - for (int i=0;i fc = read_ineFile("sampleh8.ine"); - std::cout << "fichier lu avec " << fc.size() << " facettes.\n"; - IntervalVector box = IntervalVector::constant(fc[0].row.size(), - Interval(-10,10)); - std::vector vide; - DDbuildF2V build2(box.size(),box,vide); - std::cout << "Initial build : " << build2 << std::endl; - Index i=0; -/* - for (const auto &f : fc) { - std::cout << "add facet : " << f << std::endl; - build2.add_facet(++i,f); - std::cout << build2; - } -*/ - for (int i=0;i &facets, +LPclp::LPclp (Index dim, std::shared_ptr &facets, const Row &objvect) : - nbRows(facets.size()), nbCols(dim), + nbRows(facets->nbfcts()), nbCols(dim), Afacets(facets), objvect(objvect), cststat(nbRows,0), rowBasis(nbRows,false), built(false), model(nullptr), @@ -46,8 +46,8 @@ LPclp::LPclp (Index dim, const std::vector &facets, { } -LPclp::LPclp (Index dim, const std::vector &facets) : - nbRows(facets.size()), nbCols(dim), +LPclp::LPclp (Index dim, std::shared_ptr &facets) : + nbRows(facets->nbfcts()), nbCols(dim), Afacets(facets), objvect(Row::Zero(nbCols)), cststat(nbRows,0), rowBasis(nbRows,false), built(false), model(nullptr), @@ -56,9 +56,9 @@ LPclp::LPclp (Index dim, const std::vector &facets) : { } -LPclp::LPclp (Index dim, const std::vector &facets, +LPclp::LPclp (Index dim, std::shared_ptr &facets, const IntervalVector &box) : - nbRows(facets.size()), nbCols(dim), + nbRows(facets->nbfcts()), nbCols(dim), Afacets(facets), objvect(Row::Zero(nbCols)), cststat(nbRows,0), rowBasis(nbRows,false), built(false), model(nullptr), @@ -70,43 +70,25 @@ LPclp::LPclp (Index dim, const std::vector &facets, LPclp::LPclp(const Matrix &mat, const Row &objvect, const Vector &rhsvect, const std::vector &eqSet) : nbRows(mat.rows()), nbCols(mat.cols()), - Afacets(nbRows), + Afacets(std::make_shared(mat,rhsvect,eqSet)), objvect(objvect), cststat(nbRows,0), rowBasis(nbRows,false), built(false), model(nullptr), primalSol(nbCols), dualSol(nbRows), primalRay(nbCols), dualRay(nbRows), bbox(nbCols) { - for (Index i=0;i=0 && i &eqSet) : nbRows(mat.rows()), nbCols(mat.cols()), - Afacets(nbRows), + Afacets(std::make_shared(mat,rhsvect,eqSet)), objvect(Row::Zero(nbCols)), cststat(nbRows,0), rowBasis(nbRows,false), built(false), model(nullptr), primalSol(nbCols), dualSol(nbRows), primalRay(nbCols), dualRay(nbRows), bbox(nbCols) { - for (Index i=0;i=0 && iAfacets.push_back(facet); + std::pair a = this->Afacets->insert_facet(facet); + if (a.first==0) return 0; this->cststat.push_back(0); this->dualSol.resize(nbRows+1); this->dualRay.resize(nbRows+1); @@ -174,8 +158,7 @@ Index LPclp::addConstraint(const Facet &facet) { } Index LPclp::addConstraint(const Row &vst, double rhs, bool isEq) { - Facet newFacet(vst,rhs,isEq); - return this->addConstraint(newFacet); + return this->addConstraint(Facet_::make(vst,rhs,isEq)); } void LPclp::setObjective(const Row &objvect) { @@ -193,8 +176,10 @@ void LPclp::setActive(Index cst, bool state) { if (!cststat[cst][INACTIVE]) return; cststat[cst][INACTIVE]=false; if (built) { - model->setRowUpper(cst,Afacets[cst].rhs); - if (Afacets[cst].eqcst) model->setRowLower(cst,Afacets[cst].rhs); + const auto &fct = (*Afacets)[cst]; + model->setRowUpper(cst,fct->second.rhs); + if (fct->second.eqcst) + model->setRowLower(cst,fct->second.rhs); } this->status=1<addFacetToCoinBuild(buildObject, - Afacets[k],cststat[k],row2Index,row2Vals); + *((*Afacets)[k]),cststat[k],row2Index,row2Vals); } model->addRows(buildObject); delete row2Vals; @@ -264,7 +249,7 @@ void LPclp::setModel(bool emptytest) { for(Index i=0;isecond.eqcst ? 0.0 : 1.0; } model->addColumn (nbRows,col2Index,col2Vals,-COIN_DBL_MAX,COIN_DBL_MAX,1.0); for (Index i =0; i mainCol; for (Index i = 0; iAfacets[i].eqcst) continue; + if (!(*this->Afacets)[i]->second.eqcst) continue; if (cststat[i][REMOVED] || cststat[i][INACTIVE] || cststat[i][REDUNDANT]) continue; - triMat.emplace_back(this->Afacets[i].row); - triRhs.emplace_back(this->Afacets[i].rhs); + triMat.emplace_back((*this->Afacets)[i]->first.row); + triRhs.emplace_back((*this->Afacets)[i]->second.rhs); for (Index j=0;j=0;i--) { + const auto &fct = (*this->Afacets)[i]; if (cststat[i][REMOVED] || cststat[i][INACTIVE] || cststat[i][REDUNDANT]) continue; - if (this->Afacets[i].eqcst) { nb++; continue; } + if (fct->second.eqcst) { nb++; continue; } this->setActive(i,false); - this->setObjective(this->Afacets[i].row); + this->setObjective(fct->first.row); lp_result_stat stat=this->solve(false,(solvedOnce ? 4 : 0)); if (stat[EMPTY]) return -1; if (stat[BOUNDED]) { - if (this->Valobj.ub()<=this->Afacets[i].rhs+tolerance.ub() - && this->Valobj.lb()<=this->Afacets[i].rhs+tolerance.lb()) { + if (this->Valobj.ub()<=fct->second.rhs+tolerance.ub() + && this->Valobj.lb()<=fct->second.rhs+tolerance.lb()) { cststat[i][REDUNDANT]=true; continue; } @@ -479,7 +465,7 @@ void LPclp::correctBasisInverse(IntervalMatrix &basisInverse, Interval LPclp::dotprodrhs(const IntervalRow &d) const { Interval r(0.0); for (Index i=0;iAfacets[i].rhs; + r += d[i]*(*this->Afacets)[i]->second.rhs; } return r; } @@ -494,8 +480,9 @@ BoolInterval LPclp::checkDualForm(IntervalRow &dualVect, const std::vector &wholeBasis, Index nbRowsInBasis, bool checkempty) const { for (Index i=0;ifirst.row; + if (checkempty && !(*Afacets)[i]->second.eqcst) + initSum[nbCols]+=dualVect[i]; } /* calcul de "l'erreur" à appliquer sur dualVect */ IntervalRow error = initSum*basisInverse; @@ -504,7 +491,8 @@ BoolInterval LPclp::checkDualForm(IntervalRow &dualVect, int r = wholeBasis[i]; dualVect[r] -= error[i]; /* modify the ray with the error, the value must stay positive... */ - if (Afacets[r].eqcst) continue; /* ... except for equalities */ + if ((*Afacets)[r]->second.eqcst) + continue; /* ... except for equalities */ if (dualVect[r].ub()<0.0) { return BoolInterval::FALSE; } if (dualVect[r].lb()<0.0) { ok = BoolInterval::UNKNOWN; } } @@ -583,7 +571,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { for (Index c=0;cAfacets[r].row[c]; + basis(i,c)=(*this->Afacets)[r]->first.row[c]; } } if (checkempty) { @@ -717,7 +705,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { if (!checkempty && !bbox.is_unbounded()) { /* restart with the ray */ for (Index i=0;isecond.eqcst) assert_release(ray[i]<0.0); dualRay[i]=ray[i]; } Interval productNeum = this->dotprodrhs(dualRay); @@ -736,9 +724,9 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { { IntervalVector v = IntervalVector::Ones(nbRows); for (Index j=0;jAfacets[j].row))* + comp.row(j)=((IntervalRow)((*Afacets)[j]->first.row))* basisInverse.topRows(nbCols); - if (this->Afacets[j].eqcst) v[j]=0.0; + if ((*Afacets)[j]->second.eqcst) v[j]=0.0; } if (checkempty) comp += v*basisInverse.row(nbCols); } @@ -751,7 +739,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { okrow[j].set_empty(); nbok--; continue; } if (rowBasis[j]) { okrow[j].set_empty(); nbok--; continue; } - if (Afacets[j].eqcst) { okrow[j].init(); } + if ((*Afacets)[j]->second.eqcst) { okrow[j].init(); } } for (Index i=0;isecond.eqcst ? + -oo : 0.0, u.lb()); if (okrow[j].is_empty()) { nbok--; continue; } } @@ -804,8 +793,8 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { } errorNeum=IntervalRow::Zero(nbCols2); for (Index i=0;ifirst.row; + if (checkempty && !(*Afacets)[i]->second.eqcst) errorNeum[nbCols]+=dualRay[i]; } if (checkempty) { @@ -816,7 +805,8 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { for (Index i=0;isecond.eqcst) + dualRay[r] &= Interval(0.0,oo); // if (dualSol[r].ub()<0.0) { ok = BoolInterval::FALSE; break; } // if (dualSol[r].lb()<0.0) { ok = BoolInterval::UNKNOWN; break; } } @@ -871,7 +861,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { bool ok=true; for (Index i=0;isecond.eqcst && dSol[i].lb()<0.0) { ok=false; break; } dualSol[r]=dSol[i]; } @@ -900,7 +890,8 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { (we do not _really_ want a box around the "center" of the polytope */ IntervalVector errorPart(nbRows); for (Index i=0;iAfacets[i].row*primalSol - this->Afacets[i].rhs; + errorPart[i] = (*Afacets)[i]->first.row*primalSol - + (*Afacets)[i]->second.rhs; } if (!checkempty) { IntervalVector errorInBasis= IntervalVector::Zero(nbCols2); @@ -909,7 +900,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { for (Index i=0;iAfacets[r].eqcst) { + if ((*Afacets)[r]->second.eqcst) { errorInBasis[i] = errorPart[r]; continue; } errorInBasis[i]=max(errorPart[r],Interval::zero()); @@ -919,7 +910,8 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { IntervalVector correction=basisInverse*errorInBasis; primalSol -= correction; for (Index i=0;iAfacets[i].row*primalSol - this->Afacets[i].rhs; + errorPart[i] = (*Afacets)[i]->first.row*primalSol + - (*Afacets)[i]->second.rhs; } } @@ -940,7 +932,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { #ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG std::cout << "check " << i << "\n"; #endif - if (Afacets[i].eqcst) errorPart[i]=abs(errorPart[i]); + if ((*Afacets)[i]->second.eqcst) errorPart[i]=abs(errorPart[i]); mxVal=max(errorPart[i],mxVal); if (mxVal.lb()>0.0) break; } @@ -955,15 +947,17 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { it back if needed */ IntervalVector primalSol2=primalSol.mid(); for (Index i=0;isecond.eqcst) continue; /* we should not even try, in this case */ - Interval err = this->Afacets[i].row*primalSol2 - this->Afacets[i].rhs; + Interval err = (*Afacets)[i]->first.row*primalSol2 + - (*Afacets)[i]->second.rhs; if (err.ub()>=0.0) - primalSol2 -= (1.1*err.ub()/this->Afacets[i].row.squaredNorm())*this->Afacets[i].row; + primalSol2 -= (1.1*err.ub()/(*Afacets)[i]->first.row.squaredNorm())*(*Afacets)[i]->first.row; } primalSol2=primalSol2.mid(); for (Index i=0;iAfacets[i].row*primalSol2 - this->Afacets[i].rhs; + errorPart[i] = (*Afacets)[i]->first.row*primalSol2 + - (*Afacets)[i]->second.rhs; } #ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG std::cout << "new primalSol : " << primalSol2 << "\n"; @@ -976,7 +970,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { ok=false; /* failed */ break; } - if (Afacets[i].eqcst && errorPart[i].lb()<0.0) { + if ((*Afacets)[i]->second.eqcst && errorPart[i].lb()<0.0) { ok=false; /* failed */ break; } @@ -1007,7 +1001,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { } IntervalVector errorPart(nbRows); for (Index i=0;iAfacets[i].row*primalRay; + errorPart[i] = (*Afacets)[i]->first.row*primalRay; } if (!checkempty) { /* if !checkempty, the ray "follows" some facets, @@ -1016,7 +1010,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { for (Index i=0;iAfacets[r].eqcst) + if ((*Afacets)[r]->second.eqcst) { errorInBasis[i] = errorPart[r]; continue; } errorInBasis[i]=max(errorPart[r],Interval::zero()); /* negative is ok */ @@ -1025,14 +1019,14 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { IntervalVector correction=basisInverse*errorInBasis; primalRay -= correction; for (Index i=0;iAfacets[i].row*primalRay; + errorPart[i] = (*Afacets)[i]->first.row*primalRay; } } Interval mxVal(-1.0); for (Index i=0;isecond.eqcst) errorPart[i]=abs(errorPart[i]); mxVal=max(errorPart[i],mxVal); if (mxVal.lb()>0.0) break; } @@ -1044,16 +1038,16 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { it back if needed */ IntervalVector primalRay2=primalRay.mid(); for (Index i=0;isecond.eqcst) continue; /* we should not even try, in this case */ - Interval err = this->Afacets[i].row*primalRay2; + Interval err = (*Afacets)[i]->first.row*primalRay2; if (err.ub()>=0.0) { - primalRay2 -= (1.1*err.ub()/this->Afacets[i].row.squaredNorm())*this->Afacets[i].row; + primalRay2 -= (1.1*err.ub()/(*Afacets)[i]->first.row.squaredNorm())*(*Afacets)[i]->first.row; } } primalRay2=primalRay2.mid(); for (Index i=0;iAfacets[i].row*primalRay2; + errorPart[i] = (*Afacets)[i]->first.row*primalRay2; } #ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG std::cout << "new primalRay : " << primalRay2 << "\n"; @@ -1128,7 +1122,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { IntervalMatrix comp(nbRows,nbCols); { for (Index j=0;jAfacets[j].row))* + comp.row(j)=((IntervalRow)((*Afacets)[j]->first.row))* basisInverse; } } @@ -1139,7 +1133,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { okrow[j].set_empty(); nbok--; continue; } if (rowBasis[j]) { okrow[j].set_empty(); nbok--; continue; } - if (Afacets[j].eqcst) { okrow[j].init(); } + if ((*Afacets)[j]->second.eqcst) { okrow[j].init(); } } for (Index i=0;isecond.eqcst ? -oo : 0.0, u.lb()); if (okrow[j].is_empty()) { nbok--; continue; } } @@ -1188,14 +1182,15 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { } errorNeum=-objvect; for (Index i=0;iAfacets[i].row; + errorNeum += dualSol[i]*(*Afacets)[i]->first.row; } IntervalRow error = errorNeum*basisInverse; ok=BoolInterval::TRUE; for (Index i=0;isecond.eqcst) + dualRay[r] &= Interval(0.0,oo); // if (dualSol[r].ub()<0.0) { ok = BoolInterval::FALSE; break; } // if (dualSol[r].lb()<0.0) { ok = BoolInterval::UNKNOWN; break; } } diff --git a/src/extensions/polytope/clp/codac2_clp.h b/src/extensions/polytope/clp/codac2_clp.h index 725b71bd7..e97f23c30 100644 --- a/src/extensions/polytope/clp/codac2_clp.h +++ b/src/extensions/polytope/clp/codac2_clp.h @@ -41,35 +41,35 @@ namespace codac2 { */ class LPclp { public: - /** \brief Constructor with the set of facets + /** \brief Constructor with the collection of facets * * LP problem : max objvect X s.t. mat X <= rhsvect * * \param dim dimension of the space * \param facets the facets * \param objvect the objective */ - LPclp (Index dim, const std::vector &facets, + LPclp (Index dim, std::shared_ptr &facets, const Row &objvect); - /** \brief Constructor with the set of facets, no objective + /** \brief Constructor with the collection of facets, no objective * * LP problem : max objvect X s.t. mat X <= rhsvect * * \param dim dimension of the space * \param facets the facets */ - LPclp (Index dim, const std::vector &facets); + LPclp (Index dim, std::shared_ptr &facets); - /** \brief Constructor with the set of facets, bbox, no objective + /** \brief Constructor with the collection of facets, bbox, no objective * * LP problem : max objvect X s.t. mat X <= rhsvect * * \param dim dimension of the space * \param facets the facets * \param box the bounding box for Neumaier's approx */ - LPclp (Index dim, const std::vector &facets, + LPclp (Index dim, std::shared_ptr &facets, const IntervalVector &box); - /** \brief Constructor from a matrix + /** \brief Constructor from a matrix : create a new CollectFacets * * LP problem : max objvect X s.t. mat X <= rhsvect * @@ -115,15 +115,17 @@ class LPclp { */ void set_bbox(const IntervalVector &box); - /** \brief Add a constraint + /** \brief Add a constraint (warning : only for "local" CollectFacets) * \param facet the facet - * \return the row number of the constraint */ + * \return the row number of the constraint, or -1 if the + * insertion failed (an constraint with the same base exists) */ Index addConstraint(const Facet &facet); - /** \brief Add a constraint + /** \brief Add a constraint (warning : only for "local" CollectFacets) * \param vst the (row) vector * \param rhs its RHS * \param isEq true if it is an equality - * \return the row number of the constraint */ + * \return the row number of the constraint, or the number of the + * existing constraint if a constraint with the same base was modified */ Index addConstraint(const Row &vst, double rhs, bool isEq=false); /** \brief Set the objvective * \param objvect the new objective @@ -151,7 +153,11 @@ class LPclp { /** \brief Get the set of facets \return the facets */ - const std::vector &getFacets() const; + const CollectFacets &getFacets() const; + /** \brief Get the collections of facets as pointer + \return the facets + */ + std::shared_ptr getFacetsPtr() ; /** \brief Get the objective row * \return the objective */ @@ -167,7 +173,7 @@ class LPclp { /** \brief Get a single constraint, as an facet * \return the rhs vector */ - const Facet &getCst(Index i) const; + CollectFacets::mapCIterator getCst(Index i) const; /** \brief Status a constraint (internal) * \arg \c REMOVED Constraint removed or never added @@ -295,7 +301,7 @@ class LPclp { private: /* initial problem */ Index nbRows, nbCols; /* dimension of the matrix */ - std::vector Afacets; /* the facets */ + std::shared_ptr Afacets; /* the facets */ Row objvect; /* objective vector */ std::vector cststat; @@ -345,7 +351,8 @@ std::ostream& print_lp_stat(std::ostream& os, const LPclp::lp_result_stat x); // inline const Matrix &LPclp::getMat() const { return Amat; } /* TODO */ inline const Row &LPclp::getObjvect() const { return objvect; } // inline const Vector &LPclp::getRhsvect() const { return rhsvect; } /* TODO */ -inline const Facet &LPclp::getCst(Index i) const { return Afacets[i]; } +inline CollectFacets::mapCIterator + LPclp::getCst(Index i) const { return (*Afacets)[i]; } inline const Interval &LPclp::getValobj() const { return Valobj; } inline bool LPclp::isRedundant(Index cst) const { return cststat[cst][REDUNDANT]; } inline const IntervalVector &LPclp::getFeasiblePoint() const { return this->primalSol; } diff --git a/src/extensions/polytope/codac2_Facet.cpp b/src/extensions/polytope/codac2_Facet.cpp index aff74eb97..17cb719de 100644 --- a/src/extensions/polytope/codac2_Facet.cpp +++ b/src/extensions/polytope/codac2_Facet.cpp @@ -1,5 +1,5 @@ /** - * \file codac2_Facet.h + * \file codac2_Facet.cpp * ---------------------------------------------------------------------------- * \date 2025 * \author Damien Massé @@ -10,6 +10,7 @@ #include /* uses swap */ +#include #include "codac2_Index.h" #include "codac2_Vector.h" @@ -22,7 +23,12 @@ namespace codac2 { -polytope_inclrel Facet::relation_Box(const IntervalVector &b) const { +namespace Facet_ { + +polytope_inclrel relation_Box(const Facet &f, const IntervalVector &b) { + const Row &row = f.first.row; + const double &rhs = f.second.rhs; + const bool &eqcst = f.second.eqcst; if (b.is_empty()) return inclrel_includes | inclrel_disjoint; IntervalVector a(b); /* check the vertex that maximizes row */ @@ -63,23 +69,243 @@ polytope_inclrel Facet::relation_Box(const IntervalVector &b) const { } } -void Facet::contract_Box(IntervalVector &b) const { +void contract_Box(const Facet &f, IntervalVector &b) { + const Row &row = f.first.row; + const double &rhs = f.second.rhs; /* use MulOp:bwd */ - IntervalRow x1(this->row); - MulOp::bwd(Interval(-oo,this->rhs),x1,b); + IntervalRow x1(row); + MulOp::bwd(Interval(-oo,rhs),x1,b); if (b[0].is_empty()) b.set_empty(); } -void Facet::contract_out_Box(IntervalVector &b) const { +void contract_out_Box(const Facet &f, IntervalVector &b) { + const Row &row = f.first.row; + const double &rhs = f.second.rhs; /* use MulOp:bwd */ - IntervalRow x1(this->row); - MulOp::bwd(Interval(this->rhs,oo),x1,b); + IntervalRow x1(row); + MulOp::bwd(Interval(rhs,oo),x1,b); if (b[0].is_empty()) b.set_empty(); } +Interval bound_linear_form(const Facet &f, + Row &row2, const IntervalVector &b) { + const Row &row = f.first.row; + const double &rhs = f.second.rhs; + const Index &bdim = f.first.bdim; + const bool &eqcst = f.second.eqcst; + if (bdim==-1) return row2.dot(b); + Index abdim = bdim/(2*row.size()); + bool neg = (abdim>=row.size()); + if (neg) { abdim -= row.size(); } + if (!eqcst && + ((!neg && row2[abdim]<=0.0) || (neg && row2[abdim]>=0))) + return row2.dot(b); + Interval quotient = Interval(row2[abdim])/Interval(row[abdim]); + Interval res=quotient*rhs; + IntervalRow rem=row2-quotient*row; + rem[abdim]=0.0; + return res + rem.dot(b); +} + +} /* end of namespace Facet_ */ + std::ostream& operator<<(std::ostream& os, const Facet& f) { - os << f.row << (f.eqcst ? "=" : "<=" ) << f.rhs; + os << f.first.row << (f.second.eqcst ? "=" : "<=" ) << f.second.rhs; return os; } +CollectFacets::CollectFacets(const Matrix &mat, const Vector &rhsvect, + const std::vector &eqSet) { + assert(mat.rows()==rhsvect.size()); + for (Index i=0;i a =this->insert_facet(mat.row(i),rhsvect[i],false); + if (!a.second) + throw std::domain_error("CollectFacets with duplicate rows"); + } + for (Index i: eqSet) { + _eqFacets.push_back(i); + (_allFacets[i])->second.eqcst=true; + } +} + +std::pair CollectFacets::result_insertion + (Index id, std::pair res, + double rhs, bool eqcst, ACTION_DUPLICATE act) { + if (res.second) { + _allFacets.push_back(res.first); + if (eqcst) _eqFacets.push_back(id-1); + return { id, true }; + } else { + double old_rhs=res.first->second.rhs; + if (act==CHANGE_RHS || + (act==MAX_RHS && old_rhsrhs)) { + bool old_eqcst = res.first->second.eqcst; + res.first->second.rhs=rhs; + res.first->second.eqcst=eqcst; + if (eqcst && !old_eqcst) + _eqFacets.push_back(res.first->second.Id-1); + else if (!eqcst && old_eqcst) + remove_in_eqFacets(res.first->second.Id-1); + return { res.first->second.Id, false }; + } + } + return { 0, false }; +} + +void CollectFacets::remove_in_eqFacets(Index id) { + for (Index i=0; i<(Index)_eqFacets.size(); i++) { + if (_eqFacets[i]==id) { + _eqFacets[i]=_eqFacets.back(); + _eqFacets.pop_back(); + return; + } + } +} + +std::pair CollectFacets::insert_facet(const Row &row, + double rhs, bool eqcst, ACTION_DUPLICATE act) { + Index id = _allFacets.size()+1; + std::pair res = + _map.emplace(std::move(Facet_::make(row,rhs,eqcst,id))); + return result_insertion(id,res,rhs,eqcst,act); +} +std::pair CollectFacets::insert_facet(Row &&row, double rhs, bool eqcst, ACTION_DUPLICATE act) { + Index id = _allFacets.size()+1; + std::pair res = + _map.emplace(std::move(Facet_::make(row,rhs,eqcst,id))); + return result_insertion(id, res,rhs,eqcst,act); +} +std::pair CollectFacets::insert_facet(const Facet &fct, ACTION_DUPLICATE act) { + Index id = _allFacets.size()+1; + std::pair res = + _map.emplace(std::pair(fct.first, FacetRhs(fct.second,id))); + return result_insertion(id, res,fct.second.rhs,fct.second.eqcst,act); +} + +std::pair + CollectFacets::change_eqFacet(Index id, Row &&nrow, double nrhs, + ACTION_DUPLICATE act) { + mapIterator fcIt = _allFacets[_eqFacets[id]]; + auto nde = _map.extract(fcIt); + nde.key().change_row(nrow); + nde.mapped().rhs=nrhs; + const auto result_insert = _map.insert(std::move(nde)); + if (!result_insert.inserted) { + double old_rhs=result_insert.position->second.rhs; + if (act==CHANGE_RHS || + (act==MAX_RHS && old_rhsnrhs)) { + bool old_eqcst = result_insert.position->second.eqcst; + result_insert.position->second.rhs=nrhs; + result_insert.position->second.eqcst=true; + result_insert.position->second.Id=_eqFacets[id]+1; + _allFacets[_eqFacets[id]] = result_insert.position; + if (old_eqcst) + remove_in_eqFacets(result_insert.position->second.Id-1); + _allFacets[result_insert.position->second.Id-1] = endFacet(); + return { result_insert.position, false }; + } else { + _allFacets[_eqFacets[id]] = this->_map.end(); + _eqFacets[id] = _eqFacets.back(); + _eqFacets.pop_back(); + return { this->_map.end(), false }; + } + } + _allFacets[_eqFacets[id]] = result_insert.position; + return { result_insert.position, true }; +} + +std::pair + CollectFacets::change_ineqFacet(Index id, Row &&nrow, double nrhs, + ACTION_DUPLICATE act) { + mapIterator fcIt = _allFacets[id-1]; + auto nde = _map.extract(fcIt); + nde.key().change_row(nrow); + nde.mapped().rhs=nrhs; + const auto result_insert = _map.insert(std::move(nde)); + if (!result_insert.inserted) { + double old_rhs=result_insert.position->second.rhs; + if (act==CHANGE_RHS || + (act==MAX_RHS && old_rhsnrhs)) { + bool old_eqcst = result_insert.position->second.eqcst; + result_insert.position->second.rhs=nrhs; + result_insert.position->second.eqcst=true; + if (old_eqcst) + remove_in_eqFacets(result_insert.position->second.Id-1); + _allFacets[result_insert.position->second.Id-1] = endFacet(); + result_insert.position->second.Id=id; + _allFacets[id-1] = result_insert.position; + return { result_insert.position, false }; + } else { + _allFacets[id-1] = this->_map.end(); + return { this->_map.end(), false }; + } + } + _allFacets[id-1] = result_insert.position; + return { result_insert.position, true }; +} + +CollectFacets::mapIterator CollectFacets::change_ineqFacet_rhs(mapIterator it, + double nrhs) { + it->second.rhs=nrhs; + return it; +} + +CollectFacets::mapIterator + CollectFacets::dissociate_eqFacet(Index id, double nbound, + ACTION_DUPLICATE act) { + Index aId = _eqFacets[id]; + /* start with removing id in _eqFacets */ + _eqFacets[id] = _eqFacets.back(); + _eqFacets.pop_back(); + mapIterator fcIt = _allFacets[aId]; + mapIterator ret = this->_map.end(); + double rhs = fcIt->second.rhs; + if (nbound<=rhs) { + fcIt->second.eqcst=false; + if (nbound>-oo) { + std::pair rinsert = + this->insert_facet(-fcIt->first.row,-nbound,false,act); + if (rinsert.first!=0) ret = _allFacets[rinsert.first-1]; + } else ret = fcIt; + } else { + fcIt->second.eqcst=false; + if (nbound<+oo) { + std::pair rinsert = this->insert_facet(-fcIt->first.row, + -fcIt->second.rhs,false,act); + fcIt->second.rhs = nbound; + if (rinsert.first!=0) ret = _allFacets[rinsert.first-1]; + } else { /* we need to extract and reinsert the facet */ + auto nde = _map.extract(fcIt); + nde.key().negate_row(); + double nrhs = -nde.mapped().rhs; + nde.mapped().rhs= nrhs; + nde.mapped().eqcst= false; + const auto result_insert = _map.insert(std::move(nde)); + if (!result_insert.inserted) { + double old_rhs=result_insert.position->second.rhs; + if (act==CHANGE_RHS || + (act==MAX_RHS && old_rhsnrhs)) { + bool old_eqcst = result_insert.position->second.eqcst; + result_insert.position->second.rhs=nrhs; + result_insert.position->second.eqcst=false; + if (old_eqcst) + remove_in_eqFacets(result_insert.position->second.Id-1); + _allFacets[result_insert.position->second.Id-1] = endFacet(); + result_insert.position->second.Id=aId+1; + _allFacets[aId] = result_insert.position; + ret=result_insert.position; + } else { + _allFacets[aId] = endFacet(); + ret=endFacet(); + } + } else { + _allFacets[aId] = result_insert.position; + ret=result_insert.position; + } + } + } + return ret; +} + } diff --git a/src/extensions/polytope/codac2_Facet.h b/src/extensions/polytope/codac2_Facet.h index 6e2ca503f..3149e8073 100644 --- a/src/extensions/polytope/codac2_Facet.h +++ b/src/extensions/polytope/codac2_Facet.h @@ -12,6 +12,8 @@ #include /* uses swap */ #include +#include +#include #include "codac2_Index.h" #include "codac2_Vector.h" @@ -24,57 +26,338 @@ namespace codac2 { -/** \brief Potential inclusion relation between sets - * INCLUDES : A is included in B - * MAYINCLUDE : A may be included in B - * NOTINCLUDE : A\\B is not empty - * INTERSECTS : A inter B is non empty - * MAYINTERSECT : A inter B may be non empty - * DISJOINT : A inter B is empty - */ -enum INCLREL { - INCLUDES, - MAYINCLUDE, - NOTINCLUDE, - INTERSECTS, - MAYINTERSECT, - DISJOINT, - SIZEINCLREL -}; -using polytope_inclrel = std::bitset; -const polytope_inclrel inclrel_includes = 1<0 v[B]>0 A*(2*dim)+A-B (+2*dim if B>A) + v[A]>0 v[B]<0 A*(2*dim)+A-(B+dim)+2*dim + v[A]<0 v[B]>0 (A+dim)*(2*dim)+A+dim-B + v[A]<0 v[B]<0 (A+dim)*(2*dim)+(A+dim)-(B+dim) + (+2*dim if B>A) + noting Adim = A if v[A]>0 and A+dim if v[A]<0 + and Bdim = B if v[B]>0 and B+dim if v[B]<0 + bdim = Adim*(2*dim+1) - Bdim (+2*dim if Adim>Bdim) + if null vector, value = -1 */ + double vdim; /* |v[B]|/|v[A]| (0 if no value for B) */ + Row row; + FacetBase(const Row &row) : bdim(-1), vdim(0.0), row(row) { + this->compute_key(); + } -struct Facet { - Row row; + FacetBase(Row &&row) : bdim(-1), vdim(0.0), row(row) { + this->compute_key(); + } + FacetBase(const FacetBase &fc) : bdim(fc.bdim), vdim(fc.vdim), row(fc.row) { + } + FacetBase(FacetBase &&fc) : bdim(fc.bdim), vdim(fc.vdim), row(fc.row) { + } + + inline void swap(FacetBase &fb) { + this->row.swap(fb.row); + std::swap(this->bdim,fb.bdim); + std::swap(this->bdim,fb.bdim); + } + + void change_row(Row &&row) { + this->row=row; this->bdim=-1; this->vdim=0.0; + this->compute_key(); + } + void change_row(const Row &row) { + this->row=row; this->bdim=-1; this->vdim=0.0; + this->compute_key(); + } + + void negate_row() { + this->row=-row; + Index u = 2*row.size()*row.size(); + if (this->bdim>=u) this->bdim-=u; else this->bdim+=u; + } + + private: + + void compute_key() { + if (row.size()==0) return; + double val=row[0]; + double valabs=std::abs(val); + if (val<0.0) bdim=row.size(); else if (val>0.0) bdim=0; + Index b2dim = -1; + double val2abs=0.0; + for (Index i=1;ivalabs) { + val2abs=valabs; + valabs=val_i; + val=row[i]; + b2dim=bdim; + if (val<0.0) bdim=row.size()+i; else bdim=i; + } else if (val_i>val2abs) { + val2abs=val_i; + if (val_i<0.0) b2dim=row.size()+i; else b2dim=i; + } + } + if (valabs==0.0) return; /* bdim=-1 */ + if (b2dim==-1) b2dim=bdim; + if (b2dim>bdim) + bdim = bdim * (2*row.size()+1) + 2*row.size() - b2dim; + else + bdim = bdim * (2*row.size()+1) - b2dim; + vdim = valabs/val2abs; + } +}; + +struct FacetRhs { double rhs; bool eqcst; + Index Id; - Facet(const Row &row, double rhs, bool eqcst) : - row(row), rhs(rhs), eqcst(eqcst) - {} - - Facet() {} + FacetRhs(double rhs, bool eqcst, Index Id) : rhs(rhs), eqcst(eqcst), Id(Id) + {}; + FacetRhs(const FacetRhs rhs, Index Id) : rhs(rhs.rhs), eqcst(rhs.eqcst), + Id(Id) + {}; - inline void swap(Facet &f) { - this->row.swap(f.row); - std::swap(this->rhs,f.rhs); - std::swap(this->eqcst,f.eqcst); + void swap(FacetRhs &r) { + std::swap(rhs,r.rhs); + std::swap(eqcst,r.eqcst); } +}; + +using Facet = std::pair; + +/* I want specific functions for Facet, but I do not want to create + a new class inheriting Facet (as the Map will have the pair) + => using namespace */ +namespace Facet_ { + + inline Facet make(const Row &row, double rhs, bool eqcst, Index Id) { + return std::make_pair(FacetBase(row),FacetRhs(rhs,eqcst,Id)); + } + inline Facet make(Row &&row, double rhs, bool eqcst, Index Id) { + return std::make_pair(FacetBase(row),FacetRhs(rhs,eqcst,Id)); + } + inline Facet make(const Row &row, double rhs, bool eqcst) { + return std::make_pair(FacetBase(row),FacetRhs(rhs,eqcst,0)); + } + inline Facet make(Row &&row, double rhs, bool eqcst) { + return std::make_pair(FacetBase(row),FacetRhs(rhs,eqcst,0)); + } - /* test only includes and intersects */ - polytope_inclrel relation_Box(const IntervalVector &b) const; - void contract_Box(IntervalVector &b) const; - void contract_out_Box(IntervalVector &b) const; - friend std::ostream& operator<<(std::ostream& os, const Facet& f); + inline void swap_Facet(Facet &f1,Facet &f2) { + f1.first.swap(f2.first); + f1.second.swap(f2.second); + } + + /** \brief Potential inclusion relation between sets + * INCLUDES : A is included in B + * MAYINCLUDE : A may be included in B + * NOTINCLUDE : A\\B is not empty + * INTERSECTS : A inter B is non empty + * MAYINTERSECT : A inter B may be non empty + * DISJOINT : A inter B is empty + */ + enum INCLREL { + INCLUDES, + MAYINCLUDE, + NOTINCLUDE, + INTERSECTS, + MAYINTERSECT, + DISJOINT, + SIZEINCLREL + }; + using polytope_inclrel = std::bitset; + const polytope_inclrel inclrel_includes = 1<rhs.bdim) return false; + if (lhs.vdimrhs.vdim) return false; + for (int i=0;irhs.row[i]) return false; + return false; + } }; +/** encapsulate a collection of facets with double index : + by the base vector, and by index, with storing of eqcsts */ +class CollectFacets { + public: + using mapType = std::map; + using mapIterator = mapType::iterator; + using mapCIterator = mapType::const_iterator; + + /** generate an empty collection */ + CollectFacets() {}; + /** generate the set of facets from a matrix and a vector, i.e. + * mat X <= rhs (eqSet states which rows of mat are equalities) + * @param mat the matrix + * @rhsvect the vector of rhs + * @eqSet the set of equalities */ + CollectFacets(const Matrix &mat, const Vector &rhsvect, + const std::vector &eqSet); + + /** return the number of facets, as the size of _allfacets */ + Index nbfcts() const; + /** return the number of equality facets, as the size of _eqfacets */ + Index nbeqfcts() const; + /** return the map */ + const mapType &get_map() const; + + enum ACTION_DUPLICATE { /* operation to do when a modification + create a duplicate facet */ + KEEP_RHS, /* change nothing (default behaviour) */ + CHANGE_RHS, /* change the value in all cases */ + MAX_RHS, /* maximize the RHS */ + MIN_RHS /* minimize the RHS */ + }; + + + /** insert a new facet row X <= rhs + * @param row Row + * @param rhs right-hand side + * @param eqcst equality constraint + * @param act action if an rhs with the same row exists + * @return the Id of the constraint, and true if a new facet + * has been inserted, false if old facet */ + std::pair insert_facet(const Row &row, + double rhs, bool eqcst, ACTION_DUPLICATE act=KEEP_RHS); + /** insert a new facet row X <= rhs + * @param row Row + * @param rhs right-hand side + * @param eqcst equality constraint + * @param act action if an rhs with the same row exists + * @return the Id of the new constraint, and true if a new facet + * has been inserted, false if old facet. If old facet and no change, + * returns 0 */ + std::pair insert_facet(Row &&row, double rhs, bool eqcst, + ACTION_DUPLICATE act=KEEP_RHS); + /** insert a new facet row X <= rhs, using the Facet constuction + * @param facet the facet + * @param act action if an rhs with the same row exists + * @return the Id of the constraint, and true if a new facet + * has been inserted, false if old facet. If old facet and no change + * returns 0 */ + std::pair insert_facet(const Facet &facet, + ACTION_DUPLICATE act=KEEP_RHS); + + /** get a facet by Id, starting from 0 (i.e. facet of index Id+1) + * @param id the Id of the facet, minus 1 + * @return an iterator on the facet */ + mapIterator operator[](Index id); + /** get a equality facet by the equality Id, + * starting from 0 (i.e. facet of index eqIndex(Id+1)) + * @param eqId the equality Id of the facet + * @return an iterator on the facet */ + mapIterator get_eqFacet(Index eqId); + /** get the end iterator of the facet + * @return an iterator on the facet */ + mapIterator endFacet(); + + /** change a eqfacet, keeping its Id (unless the new row creates + * a duplicate) + * @param eqId (the eqId of the facet) + * @param nrow (the new row of the facet) + * @param nrhs (the new right-hand side) + * @param act action if duplicate + * @return an iterator on the new facet, true if the modified facet + * has been inserted, false if there was a duplicate. In this case + * if the existing facet is not changed, return endFacet() */ + std::pair change_eqFacet(Index eqId, Row&& nrow, + double nrhs, ACTION_DUPLICATE act=KEEP_RHS); + /** change a facet, keeping its Id (unless the new row creates + * a duplicate) + * @param Id (the Id of the facet starting from 1) + * @param nrow (the new row of the facet) + * @param nrhs (the new right-hand side) + * @param act action if duplicate + * @return an iterator on the new facet, true if the modified facet + * has been inserted, false if there was a duplicate. In this + * cas, without change, return endFacet() */ + std::pair change_ineqFacet(Index id, Row&& nrow, + double nrhs, ACTION_DUPLICATE act=KEEP_RHS); + + /** change the rhs of a facet + * @param it the iterator of the facet + * @param nrhs the new right-hand side + * @return the iterator of the facet (=it) */ + mapIterator change_ineqFacet_rhs(mapIterator it, double nrhs); + + /** transform an equality facet to an unequality facet, and add + a new facet with a new bound nbound + if nboundrhs, put row x <= nbound and add -row x <= -rhs + if nbound = -oo or +oo, do not modify/add the new facet + @param eqId the equality Id of the equality facet + @param nbound the new bound + @return depends on the construction + nbound=-oo => current facet iterator + -oo new facet iterator (for nbound), or endFacet if + insertion "failed" + rhs new facet iterator (for rhs), or endFacet if + insertion "failed" + nbound=+oo => new facet iterator (for rhs), or endFacet if + insertion "failed" */ + mapIterator dissociate_eqFacet(Index eqId , double nbound, + ACTION_DUPLICATE act=KEEP_RHS); + + private: + mapType _map; + std::vector _allFacets; + std::vector _eqFacets; /* index in _allFacets */ + + /* look for and remove a value in eqFacet */ + void remove_in_eqFacets(Index id); + /* finish an insertion process */ + std::pair result_insertion(Index id, + std::pair res, + double rhs, bool eqcst, ACTION_DUPLICATE act); +}; + +inline const CollectFacets::mapType &CollectFacets::get_map() const + { return _map; } + +inline CollectFacets::mapIterator CollectFacets::operator[](Index id) + { return this->_allFacets[id]; } +inline CollectFacets::mapIterator CollectFacets::get_eqFacet(Index id) + { return this->_allFacets[this->_eqFacets[id]]; } + +inline Index CollectFacets::nbfcts() const { + return this->_allFacets.size(); +} + +inline Index CollectFacets::nbeqfcts() const { + return this->_eqFacets.size(); +} + +inline CollectFacets::mapIterator CollectFacets::endFacet() { + return this->_map.end(); +} + } diff --git a/src/extensions/polytope/codac2_Polytope.cpp b/src/extensions/polytope/codac2_Polytope.cpp index 41b7d8c48..741e3747f 100644 --- a/src/extensions/polytope/codac2_Polytope.cpp +++ b/src/extensions/polytope/codac2_Polytope.cpp @@ -32,6 +32,7 @@ using namespace codac2; +#if 0 namespace codac2 { Polytope::Polytope() : _dim(-1), _nbEqcsts(0), _nbineqCsts(0), _empty(true) @@ -272,4 +273,5 @@ void Polytope::clear() { } } +#endif diff --git a/src/extensions/polytope/codac2_Polytope.h b/src/extensions/polytope/codac2_Polytope.h index 5f119f96c..c0fadbea7 100644 --- a/src/extensions/polytope/codac2_Polytope.h +++ b/src/extensions/polytope/codac2_Polytope.h @@ -28,15 +28,18 @@ #include "codac2_IntervalMatrix.h" #include "codac2_BoolInterval.h" #include "codac2_clp.h" +#include "codac2_Polytope_util.h" +#include "codac2_Facet.h" +#include "codac2_Polytope_dd.h" +#if 0 namespace codac2 { /** * \class Polytope * \brief Represents a bounded convex polytope as a set of constraints - * and a bounding box (the bounding box may be, or not, inside - * the constraints) */ + * and a bounding box (the bounding box is part of the constraints) */ class Polytope { public: @@ -279,6 +282,14 @@ namespace codac2 { bool _empty; /* is empty */ IntervalVector _box; /* bounding box */ mutable std::unique_ptr _clpForm; /* LPclp formulation */ + mutable std::unique_ptr _DDbuildF2V; + /* DDbuildF2V formulation */ + mutable std::unique_ptr _DDbuildV2F; + /* DDbuildV2F formulation */ + bool _box_updated; + bool _clpFrom_updated; + bool _buildF2V_updated; + bool _buildV2F_updated; void build_clpForm_from_box(); void minimize_constraints(); }; @@ -291,5 +302,7 @@ inline bool Polytope::is_flat() const { return _nbEqcsts!=0; } inline Index Polytope::nbFacets() const { return _nbEqcsts!=0; } + } +#endif diff --git a/src/extensions/polytope/codac2_dd.cpp b/src/extensions/polytope/codac2_Polytope_dd.cpp similarity index 72% rename from src/extensions/polytope/codac2_dd.cpp rename to src/extensions/polytope/codac2_Polytope_dd.cpp index 3f94f4a35..29cbfa4a4 100644 --- a/src/extensions/polytope/codac2_dd.cpp +++ b/src/extensions/polytope/codac2_Polytope_dd.cpp @@ -1,5 +1,5 @@ /** - * \file codac2_dd.h implementation of the dd algorithm + * \file codac2_Polytope_dd.cpp implementation of the dd algorithm * ---------------------------------------------------------------------------- * \date 2025 * \author Damien Massé @@ -19,15 +19,16 @@ #include #include #include +#include #include "codac2_Index.h" #include "codac2_Vector.h" #include "codac2_Row.h" #include "codac2_Facet.h" -#include "codac2_dd.h" +#include "codac2_Polytope_dd.h" #include "codac2_IntvFullPivLU.h" -#define DEBUG_CODAC2_DD +#undef DEBUG_CODAC2_DD namespace codac2 { @@ -77,16 +78,17 @@ struct PairMaxSets { const std::vector &e2=pm.elems; std::vector::size_type i=0,j=0; bool leq=(e1.size()<=e2.size()), geq=(e1.size()>=e2.size()); + bool same_length = (e1.size()==e2.size()); /* leq=F,geq=T => +1 leq=F,geq=F => 2 leq=T,geq=F => -1 leq=T,geq=T => 0 */ while(ie2[j]) { - if (!leq) return 2; + if (!leq || same_length) return 2; geq=false; j++; } else { i++; j++; } } @@ -108,11 +110,11 @@ struct PairMaxSets { /********************/ DDbuildF2V::DDbuildF2V(Index dim, const IntervalVector &box, - const std::vector &eqconstraints, bool include_box) : dim(dim), - fdim(dim), - bbox(box), empty(false), flat(false), + std::shared_ptr facetsptr, bool include_box) : dim(dim), + fdim(dim), bbox(box), facetsptr(facetsptr), + empty(false), flat(false), nbIn(0) { - int nb_eq_dim= eqconstraints.size(); + int nb_eq_dim= (facetsptr==nullptr ? 0 : facetsptr->nbeqfcts()); if (include_box) { for (int i=0;inbeqfcts();i++) { + MatEQ.row(base+i) = facetsptr->get_eqFacet(i)->first.row; + RhsEQ[base+i] = facetsptr->get_eqFacet(i)->second.rhs; + } } IntvFullPivLU LUdec(MatEQ); #if 0 @@ -277,15 +281,22 @@ bool DDbuildF2V::addDDlink(const std::forward_list::iterator& V1, // return (V1->addLnk(V2,true) && V2->addLnk(V1,false)); } -int DDbuildF2V::add_facet(Index idFacet, const Facet& fct) { +int DDbuildF2V::add_facet(Index id) { + return this->add_facet((*facetsptr)[id]); +} + +int DDbuildF2V::add_facet(CollectFacets::mapCIterator itFacet) { + Index idFacet = itFacet->second.Id; if (empty) return 0; + if (itFacet->second.eqcst) + throw std::domain_error("add_facet with an equality facet."); /* the first step is to take the equalities constraints into account */ IntervalRow facet = IntervalRow::Zero(this->fdim+1); - facet[0] = -fct.rhs; + facet[0] = -itFacet->second.rhs; if (flat) { - facet += fct.row * this->M_EQ; + facet += itFacet->first.row * this->M_EQ; } else { - facet.tail(this->fdim) = fct.row; + facet.tail(this->fdim) = itFacet->first.row; } /* then consider lines */ Index bestline=-1; @@ -411,7 +422,9 @@ int DDbuildF2V::add_facet(Index idFacet, const Facet& fct) { } if (destlink.status==DDvertex::VERTEXGE || destlink.status==DDvertex::VERTEXSTACK) { +#ifdef DEBUG_CODAC2_DD std::cout << " GE ou STACK " << destlink.lambda << "\n"; +#endif /* add the facet to the vertex, if it does not already exists */ adjacentVertices.push_back(std::pair(lnk,fctSon(-1,lnk->fcts,actVertex->fcts))); destlink.addFct(refFacet); @@ -425,12 +438,8 @@ int DDbuildF2V::add_facet(Index idFacet, const Facet& fct) { IntervalRow x1(facet); MulOp::bwd(Interval(-oo,0.0),x1,partV); if (partV.is_empty()) { +#ifdef DEBUG_CODAC2_DD std::cout << " Sommet conserve " << facet << " " << destlink.vertex << " " << destlink.lambda << "\n"; -#if 0 - IntervalVector partV2=destlink.vertex; - IntervalRow x2(facet); - MulOp::bwd(Interval(0.0,+oo),x2,partV2); - std::cout << " reste " << partV2 << "\n"; #endif adjacentVertices.push_back(std::pair(lnk,fctSon(-1,lnk->fcts,actVertex->fcts))); destlink.addFct(refFacet); @@ -439,7 +448,9 @@ int DDbuildF2V::add_facet(Index idFacet, const Facet& fct) { } } if (-destlink.lambda.lb()/actVertex->lambda.lb()lambda.lb() << "\n"; +#endif /* extend the vertex, and add fct */ adjacentVertices.push_back(std::pair(lnk,fctSon(-1,lnk->fcts,actVertex->fcts))); destlink.vertex |= @@ -453,12 +464,6 @@ int DDbuildF2V::add_facet(Index idFacet, const Facet& fct) { IntervalVector newV = -destlink.lambda.lb()*actVertex->vertex + actVertex->lambda.lb()*partV; assert(!newV.is_empty()); -#if 0 - if (newV.is_empty()) { - std::cout << " newV : " << newV << "\n"; - std::cout << destlink.lambda << " " << actVertex->vertex << " " << actVertex->lambda << " " << partV << "\n"; - } -#endif std::forward_list::iterator newVx = this->addDDvertex(newV); #ifdef DEBUG_CODAC2_DD @@ -529,7 +534,7 @@ int DDbuildF2V::add_facet(Index idFacet, const Facet& fct) { } std::cout << pm.a->Id << " " << pm.b->Id << "\n"; #endif - bool res = this->addDDlink(pm.a,pm.b); + [[maybe_unused]] bool res = this->addDDlink(pm.a,pm.b); #ifdef DEBUG_CODAC2_DD std::cout << (res ? " added " : " already here ") << "\n"; #endif @@ -663,24 +668,28 @@ void DDbuildF2V::EqFacet::adapt_box(IntervalVector &bbox) const { /*** DDbuildV2F::EqFacet *****/ /*****************************/ -DDbuildV2F::DDbuildV2F(Index idVertex, const Vector &vertex) { +DDbuildV2F::DDbuildV2F(Index idVertex, const Vector &vertex) : + facetsptr(std::make_shared()) { Row r = Row::Zero(vertex.size()); for (Index i=0;iinsert_facet(r,vertex[i],true); r[i]=0.0; } idVertices.push_back(idVertex); } -Index DDbuildV2F::addFacetSon(const DDfacet &p1, DDfacet &p2, - Index idVertex) { +std::forward_list::iterator DDbuildV2F::addFacetSon( + const std::forward_list::iterator &It1, + std::forward_list::iterator &It2, Index idVertex) { + const CollectFacets::mapIterator &p1 = It1->facetIt; + const CollectFacets::mapIterator &p2 = It2->facetIt; #ifdef DEBUG_CODAC2_DD - std::cout << " p1.facet.row " << p1.facet.row << std::endl; - std::cout << " p2.facet.row " << p2.facet.row << std::endl; + std::cout << " p1.facet.row " << p1->first.row << std::endl; + std::cout << " p2.facet.row " << p2->first.row << std::endl; #endif - Row newV = - p2.lambda*p1.facet.row + p1.lambda*p2.facet.row; - double newRhs = - p2.lambda*p1.facet.rhs + p1.lambda*p2.facet.rhs; + Row newV = - It2->lambda*p1->first.row + It1->lambda*p2->first.row; + double newRhs = - It2->lambda*p1->second.rhs + It1->lambda*p2->second.rhs; #ifdef DEBUG_CODAC2_DD std::cout << " newV " << newV << std::endl; std::cout << " newRhs " << newRhs << std::endl; @@ -689,26 +698,28 @@ Index DDbuildV2F::addFacetSon(const DDfacet &p1, DDfacet &p2, std::vector nvtx; Index a1=0; Index a2=0; bool idVertexAdded=false; - while (a1<(Index)p1.vtx.size() && a2<(Index)p2.vtx.size()) { - if (p1.vtx[a1]vtx.size() && a2<(Index)It2->vtx.size()) { + if (It1->vtx[a1]vtx[a2]) { + a1++; if (a1==(Index)It1->vtx.size()) break; + } else if (It2->vtx[a2]vtx[a1]) { + a2++; if (a2==(Index)It2->vtx.size()) break; } else { - if (p1.vtx[a1]>idVertex && !idVertexAdded) { + if (It1->vtx[a1]>idVertex && !idVertexAdded) { nvtx.push_back(idVertex); idVertexAdded=true; } - nvtx.push_back(p1.vtx[a1]); + nvtx.push_back(It1->vtx[a1]); a1++; a2++; } } if (!idVertexAdded) nvtx.push_back(idVertex); - std::set lnks; - lnks.insert(p2.Id); - Index newId=this->addDDfacet(Facet(newV,newRhs,false),lnks,nvtx); - p2.replaceLnks(p1.Id,newId); - return newId; + std::vector::iterator> lnks; + lnks.push_back(It2); + std::forward_list::iterator + newF=this->addDDfacet(reduce_facet(newV,newRhs),lnks,nvtx); + if (newF!=facets.end()) It2->changeLnk(It1,newF); + else It2->removeLnk(It1); + return newF; } int DDbuildV2F::add_vertex(Index idVertex, const Vector& vertex) { @@ -718,96 +729,117 @@ int DDbuildV2F::add_vertex(Index idVertex, const Vector& vertex) { int DDbuildV2F::add_vertex(Index idVertex, const IntervalVector& vertex) { int cnt=0; /* first check equalities */ - if (!eqfacets.empty()) { + if (facetsptr->nbeqfcts()>0) { Index eqViolated=-1; + CollectFacets::mapIterator eqViolatedIt= facetsptr->endFacet(); Interval delta; - for (Index i=eqfacets.size()-1;i>=0;i--) { - Interval calc = eqfacets[i].row.dot(vertex)-eqfacets[i].rhs; + for (Index i=facetsptr->nbeqfcts()-1;i>=0;i--) { + CollectFacets::mapIterator fcIt = facetsptr->get_eqFacet(i); + Interval calc = fcIt->first.row.dot(vertex)-fcIt->second.rhs; if (calc.mig()tolerance) continue; - if (eqViolated==-1) { eqViolated=i; delta=calc; continue; } - else { + if (eqViolated==-1) { + eqViolated=i; + delta=calc; + eqViolatedIt = fcIt; + continue; + } else { double correction = -(calc/delta).mid(); - eqfacets[i].row += correction*eqfacets[eqViolated].row; - eqfacets[i].rhs += correction*eqfacets[eqViolated].rhs; + bool success; + std::tie(fcIt,success) = facetsptr->change_eqFacet(i, + fcIt->first.row + correction*eqViolatedIt->first.row, + fcIt->second.rhs + correction*eqViolatedIt->second.rhs); + /* if success is false, the eqFacet index may have changed */ + if (!success && + eqViolated==facetsptr->nbeqfcts()) eqViolated=i; } } if (eqViolated!=-1) { if (facets.empty()) { this->nbIn=0; - std::set lnk = { 2 }; + std::vector::iterator> lnk; std::vector vtx = { idVertex }; + Interval nrhsI = eqViolatedIt->first.row.dot(vertex); + double nrhs = delta.lb()>0.0 ? nrhsI.ub() : nrhsI.lb(); + CollectFacets::mapIterator newIt = + facetsptr->dissociate_eqFacet(eqViolated,nrhs); + assert_release(newIt!=facetsptr->endFacet()); if (delta.lb()>0.0) { - this->addDDfacet(Facet(eqfacets[eqViolated].row, - eqfacets[eqViolated].row.dot(vertex).ub(),false), - lnk,vtx); + auto a = this->addDDfacet(eqViolatedIt,lnk,vtx); vtx = idVertices; - lnk = { 1 }; - this->addDDfacet(Facet(-eqfacets[eqViolated].row, - -eqfacets[eqViolated].rhs,false), - lnk,vtx); + lnk = { a }; + auto b = this->addDDfacet(newIt,lnk,vtx); + a->addLnk(b,false); } else { - this->addDDfacet(Facet(-eqfacets[eqViolated].row, - (-eqfacets[eqViolated].row.dot(vertex)).ub(),false), - lnk,vtx); + auto a = this->addDDfacet(newIt,lnk,vtx); vtx = idVertices; - lnk = { 1 }; - this->addDDfacet(Facet(eqfacets[eqViolated].row, - eqfacets[eqViolated].rhs,false), - lnk,vtx); + lnk = { a }; + auto b = this->addDDfacet(eqViolatedIt,lnk,vtx); + a->addLnk(b,false); } cnt=2; } else { /* add the remaining inequality, linked with all existing facets */ - std::set lnk; - for (std::pair &f : facets) { - f.second.status=DDfacet::FACETOUT; /* to remove FACETNEW */ - lnk.insert(f.first); - } - Index u; + std::vector::iterator> lnk; + for (std::forward_list::iterator it = + facets.begin(); it!=facets.end(); ++it) { + it->status=DDfacet::FACETOUT; /* to remove FACETNEW */ + lnk.push_back(it); + } + Row saveEqrow = eqViolatedIt->first.row; + double saveRhs = eqViolatedIt->second.rhs; + CollectFacets::mapIterator nIt = + facetsptr->dissociate_eqFacet(eqViolated, + (delta.lb()>0.0 ? +oo : -oo)); + std::cout << "dissociated : " << (delta.lb()>0.0 ? +oo : -oo) << + " : " << (nIt == facetsptr->endFacet()) << "\n"; + eqViolatedIt=nIt; /* in the following, the case nIt==facets.end() + is not treated, as it should not happen */ std::vector vtx = idVertices; - if (delta.lb()>0.0) { - u = this->addDDfacet(Facet(-eqfacets[eqViolated].row, - -eqfacets[eqViolated].rhs,false),lnk,vtx); - } else { - u = this->addDDfacet(Facet(eqfacets[eqViolated].row, - eqfacets[eqViolated].rhs,false),lnk,vtx); - } + + std::forward_list::iterator u = + (eqViolatedIt != facetsptr->endFacet() ? + this->addDDfacet(eqViolatedIt,lnk,vtx) : + facets.end()); /* change all existing facets to take the new vertex into account */ - - for (std::pair &f : facets) { - if (f.second.status!=DDfacet::FACETOUT) continue; - Interval q = f.second.facet.rhs- - f.second.facet.row.dot(vertex); + /* no facet should be removed, theoretically, but we + * nevertheless consider this possibility */ + std::forward_list::iterator it_f=facets.before_begin(); + std::forward_list::iterator nxt = facets.begin(); + while (nxt!=facets.end()) { + DDfacet &f = (*nxt); + if (f.status!=DDfacet::FACETOUT) { it_f=nxt; ++nxt; continue; } + Interval q = f.facetIt->second.rhs- + f.facetIt->first.row.dot(vertex); double correction = (q/delta).mid(); - f.second.facet.row += correction*eqfacets[eqViolated].row; - f.second.facet.rhs += correction*eqfacets[eqViolated].rhs; - f.second.addLnk(u); - f.second.addVtx(idVertex); + if (u!=facets.end()) f.addLnk(u); + f.addVtx(idVertex); + bool success; + std::tie(f.facetIt, success) = + facetsptr->change_ineqFacet(f.facetIt->second.Id, + f.facetIt->first.row + correction*saveEqrow, + f.facetIt->second.rhs + correction*saveRhs); + if (!success) { this->removeFacet(nxt,true); + facets.erase_after(it_f); + nxt=it_f; ++nxt; continue; } + it_f=nxt; ++nxt; } - cnt=1; + cnt=(u!=facets.end() ? 1 : 0); } - if (eqViolated!=(Index)eqfacets.size()-1) { - eqfacets[eqViolated]=eqfacets[eqfacets.size()-1]; - } - eqfacets.pop_back(); - if (!eqfacets.empty()) idVertices.push_back(idVertex); + if (facetsptr->nbeqfcts()>0) idVertices.push_back(idVertex); return cnt; } else idVertices.push_back(idVertex); /* and follows */ } /* no equalities removed */ - for (std::pair &f : facets) { - DDfacet &d = f.second; - Interval calc = d.facet.row.dot(vertex)-d.facet.rhs; + for (DDfacet &d : facets) { + Interval calc = d.facetIt->first.row.dot(vertex)-d.facetIt->second.rhs; d.lambda = calc.lb(); if (calc.mig()tolerance) { d.status=DDfacet::FACETON; } else if (calc.lb()>0.0) { d.status=DDfacet::FACETOUT; } else { d.status=DDfacet::FACETIN; } } - Index endF = nbIn; - for (std::map::iterator it = facets.begin(); - it!=facets.end() && it->first<=endF;) { - DDfacet &d = it->second; - ++it; + for (std::forward_list::iterator it = facets.begin(); + it!=facets.end();++it) { + DDfacet &d = (*it); if (d.status==DDfacet::FACETON) { d.addVtx(idVertex); continue; @@ -817,15 +849,15 @@ int DDbuildV2F::add_vertex(Index idVertex, const IntervalVector& vertex) { /* very specific case of a 1-dimension polyhedra */ d.vtx.clear(); d.vtx.push_back(idVertex); - d.facet.rhs = d.facet.row.dot(vertex).ub(); + facetsptr->change_ineqFacet_rhs(d.facetIt, + d.facetIt->first.row.dot(vertex).ub()); continue; } - for (Index idL : d.links) { - auto it2 = facets.find(idL); - if (it2==facets.end()) continue; - DDfacet &d2 = it2->second; + for (std::forward_list::iterator idL : d.links) { + if (idL==facets.end()) continue; /* ? */ + DDfacet &d2 = (*idL); if (d2.status==DDfacet::FACETON) { - d2.removeLnk(d.Id); + d2.removeLnk(it); continue; } if (d2.status!=DDfacet::FACETIN) { @@ -835,28 +867,29 @@ int DDbuildV2F::add_vertex(Index idVertex, const IntervalVector& vertex) { std::cout << " addFacet " << d.Id << " " << d2.Id << " " << d.status << " " << d2.status << std::endl; std::cout << " (" << d.lambda << " " << d2.lambda << ") " << std::endl; #endif + [[maybe_unused]] std::forward_list::iterator + newIt = this->addFacetSon(it,idL,idVertex); #ifdef DEBUG_CODAC2_DD - Index newid = this->addFacetSon(d,d2,idVertex); - std::cout << " ... done " << newid <addFacetSon(d,d2,idVertex); + if (newIt==facets.end()) + std::cout << " ... FAILED! " << std::endl; + else + std::cout << " ... done " << newIt->Id << std::endl; #endif cnt++; } - this->removeFacet(d.Id,false); - + this->removeFacet(it,false); } /*construction of new links for the eq facets */ - std::vector> maxset; - for (std::map::iterator it = facets.begin(); + std::vector::iterator>> maxset; + for (std::forward_list::iterator it = facets.begin(); it!=facets.end();++it) { - DDfacet &d = it->second; + DDfacet &d = (*it); if (d.status==DDfacet::FACETON || d.status==DDfacet::FACETNEW) { - for (std::map::iterator it2 = next(it); + for (std::forward_list::iterator it2 = next(it); it2!=facets.end(); ++it2) { - DDfacet &d2 = it2->second; + DDfacet &d2 = (*it2); if (d2.status==DDfacet::FACETON || d2.status==DDfacet::FACETNEW) { - PairMaxSets actuel(d.Id,d2.Id,d.vtx,d2.vtx); + PairMaxSets actuel(it,it2,d.vtx,d2.vtx); bool placed=false; unsigned long int k=0, l=0; while (k &pm : maxset) { + for (const PairMaxSets::iterator> &pm : maxset) { // std::cout << " maxset : " << pm.a << " " << pm.b << std::endl; - facets.at(pm.a).addLnk(pm.b); - facets.at(pm.b).addLnk(pm.a); + pm.a->addLnk(pm.b); + pm.b->addLnk(pm.a); } + facets.remove_if([](const DDfacet &v) + { return v.status==DDfacet::FACETREM; }); return cnt; } -Index DDbuildV2F::addDDfacet(const Facet &f, std::set &links, - std::vector &vtx) { - - nbIn++; - facets.emplace(nbIn, - DDfacet(f,nbIn,links,vtx)); - return nbIn; +std::forward_list::iterator + DDbuildV2F::addDDfacet(const CollectFacets::mapIterator &it, + std::vector::iterator> &links, + std::vector &vtx) { + facets.emplace_front(it,links,vtx); + return facets.begin(); } -Index DDbuildV2F::addDDfacet(Facet &&f, std::set &links, - std::vector &vtx) { - - nbIn++; - facets.emplace(nbIn, - DDfacet(f,nbIn,links,vtx)); - return nbIn; +std::forward_list::iterator + DDbuildV2F::addDDfacet(const std::pair &row_rhs, + std::vector::iterator> &links, + std::vector &vtx) { + std::pair result = + facetsptr->insert_facet(row_rhs.first,row_rhs.second,false); + if (!result.second) { + return facets.end(); + } + facets.emplace_front((*facetsptr)[result.first-1],links,vtx); + return facets.begin(); } -void DDbuildV2F::removeFacet(Index Id, bool removeLnks) { - DDfacet &d = facets.at(Id); +std::forward_list::iterator + DDbuildV2F::addDDfacet(std::pair &&row_rhs, + std::vector::iterator> &links, + std::vector &vtx) { + std::pair result = + facetsptr->insert_facet(row_rhs.first,row_rhs.second,false); + if (!result.second) { + return facets.end(); + } + facets.emplace_front((*facetsptr)[result.first-1],links,vtx); + return facets.begin(); +} + +void DDbuildV2F::removeFacet(std::forward_list::iterator It, + bool removeLnks) { + DDfacet &d = (*It); if (removeLnks) { - for (Index l : d.links) { - auto it = facets.find(l); - if (it==facets.end()) continue; - it->second.removeLnk(Id); + for (std::forward_list::iterator it : d.links) { + it->removeLnk(It); } } - this->facets.erase(Id); + d.status=DDfacet::FACETREM; } std::ostream& operator<<(std::ostream& os, const DDbuildV2F& build) { - os << "DDbuildV2F (" << build.eqfacets.size() << " eq, " << - build.facets.size() << " ineq) : " << std::endl; - for (const Facet &eqf : build.eqfacets) { - os << eqf.row << " = " << eqf.rhs << std::endl; + os << "DDbuildV2F (" << build.facetsptr->nbeqfcts() << " eq, " << + build.facetsptr->nbfcts() << " total fcts) : " << std::endl; + for (Index i=0;inbeqfcts();++i) { + const Facet &eqf = (*build.facetsptr->get_eqFacet(i)); + os << eqf.first.row << " = " << eqf.second.rhs << std::endl; } for (const auto &ifacet : build.facets) { - auto &ieqf = ifacet.second; - os << ieqf.Id << " : " << ieqf.facet.row << " <= " << ieqf.facet.rhs << std::endl; + auto &ieqf = *(ifacet.facetIt); + os << ieqf.second.Id << " : " << ieqf.first.row << " <= " << ieqf.second.rhs << std::endl; os << " vtx: ("; - for (Index a : ieqf.vtx) { + for (Index a : ifacet.vtx) { os << a << ","; } os << ")" << std::endl; os << " lnks: ("; - for (Index a : ieqf.links) { - os << a << ","; + for (std::forward_list::iterator a : ifacet.links) { + os << a->facetIt->second.Id << ","; } os << ")" << std::endl; } diff --git a/src/extensions/polytope/codac2_dd.h b/src/extensions/polytope/codac2_Polytope_dd.h similarity index 75% rename from src/extensions/polytope/codac2_dd.h rename to src/extensions/polytope/codac2_Polytope_dd.h index 39da26dbf..edcf31454 100644 --- a/src/extensions/polytope/codac2_dd.h +++ b/src/extensions/polytope/codac2_Polytope_dd.h @@ -1,5 +1,5 @@ /** - * \file codac2_dd.h implementation of the dd algorithm + * \file codac2_Polytope_dd.h implementation of the dd algorithm * ---------------------------------------------------------------------------- * \date 2025 * \author Damien Massé @@ -93,7 +93,6 @@ struct DDvertex { return; } } - std::cout << "not found !!???\n"; } void changeLnk(const std::forward_list::iterator &a, @@ -101,7 +100,7 @@ struct DDvertex { for (Index i=0;i<(Index)links.size();i++) { if (links[i]==a) { links[i]=b; return; } } - /* should not happel */ + /* should not happen */ links.push_back(b); } @@ -163,12 +162,17 @@ struct DDlink { class DDbuildF2V { public: + /* create the build with a collection of facet, but + use only the equality facets */ DDbuildF2V(Index dim, const IntervalVector &box, - const std::vector &eqconstraints, bool include_box=true); + std::shared_ptr facetsptr, + bool include_box=true); // void add_constraint_box(const IntervalVector &box); - int add_facet(Index idFacet, const Facet& fct); + /* add an inequality facet in the build */ + int add_facet(Index id); + int add_facet(CollectFacets::mapCIterator itFacet); IntervalVector compute_vertex(const IntervalVector &vect) const; Index get_dim() const; @@ -234,6 +238,8 @@ class DDbuildF2V { Index fdim; /* dimension - nb independant equalities constraints */ IntervalVector bbox; + std::shared_ptr facetsptr; + /* if fdim &DDbuildF2V::get_reffacets() const struct DDfacet { - Index Id; - Facet facet; + CollectFacets::mapIterator facetIt; double lambda; enum ddfacetstatus { FACETNEW, FACETIN, FACETON, - FACETOUT + FACETOUT, + FACETREM }; ddfacetstatus status; - std::set links; + std::vector::iterator> links; std::vector vtx; - DDfacet(const Facet &v, Index Id, - std::set &nlinks, - std::vector &nvtx) : Id(Id), facet(v), status(FACETNEW) { + DDfacet(CollectFacets::mapIterator facetIt, + std::vector::iterator> &nlinks, + std::vector &nvtx) : facetIt(facetIt), status(FACETNEW) { this->links.swap(nlinks); this->vtx.swap(nvtx); } - DDfacet(Facet &&v, Index Id, - std::set &nlinks, - std::vector &nvtx) : Id(Id), facet(v), status(FACETNEW) { - this->links.swap(nlinks); - this->vtx.swap(nvtx); + + bool addLnk(std::forward_list::iterator a, bool check=false) { + if (check) { + for (auto b : links) { + if (a==b) return false; + } + } + this->links.push_back(a); + return true; } - void replaceLnks(Index old, Index nw) { - this->links.erase(old); - this->links.insert(nw); + void removeLnk(const std::forward_list::iterator &a) { + for (Index i=0;i<(Index)links.size();i++) { + if (links[i]==a) { + links[i]=links[links.size()-1]; + links.pop_back(); + return; + } + } } - void addLnk(Index a) { - this->links.insert(a); + void changeLnk(const std::forward_list::iterator &a, + const std::forward_list::iterator &b) { + for (Index i=0;i<(Index)links.size();i++) { + if (links[i]==a) { links[i]=b; return; } + } + /* should not happen */ + links.push_back(b); } void addVtx(Index a) { @@ -313,9 +333,6 @@ struct DDfacet { this->vtx.insert(it,a); } - void removeLnk(Index a) { - this->links.erase(a); - } }; class DDbuildV2F { @@ -328,15 +345,43 @@ class DDbuildV2F { friend std::ostream& operator<<(std::ostream& os, const DDbuildV2F& build); private: - Index addDDfacet(const Facet &f, std::set &links, + /* can NOT return facets.end() */ + std::forward_list::iterator addDDfacet( + const CollectFacets::mapIterator &it, + std::vector::iterator> &links, + std::vector &vtx); + /* can return facets.end() if insertion failed */ + std::forward_list::iterator addDDfacet( + std::pair &&row_rhs, + std::vector::iterator> &links, std::vector &vtx); - Index addDDfacet(Facet &&f, std::set &links, + /* can return facets.end() if insertion failed */ + std::forward_list::iterator addDDfacet( + const std::pair &row_rhs, + std::vector::iterator> &links, std::vector &vtx); - Index addFacetSon(const DDfacet &p1, DDfacet &p2, Index idVertex); - void removeFacet(Index Id, bool removeLnks); + /* can return facets.end() if insertion failed */ + std::forward_list::iterator + addFacetSon(const std::forward_list::iterator &It1, + std::forward_list::iterator &It2, Index idVertex); + void removeFacet(std::forward_list::iterator It, + bool removeLnks); + + inline std::pair reduce_facet(const Row &row, double rhs) { + int fx; + frexp(row[0],&fx); + for (Index i=1;ifx) fx=fx2; + } + if (std::abs(fx)<10) return std::pair(row,rhs); + double mult=exp2(-fx); + return std::pair(row*mult,rhs*mult); + } - std::map facets; - std::vector eqfacets; /* no need to 'link' eqfacets */ + std::shared_ptr facetsptr; + std::forward_list facets; std::vector idVertices; /* list of vertices entered, useful as long as there are eqfacets */ Index nbIn=0; diff --git a/src/extensions/polytope/codac2_Polytope_util.cpp b/src/extensions/polytope/codac2_Polytope_util.cpp index 0ad83034b..c42c9bef2 100644 --- a/src/extensions/polytope/codac2_Polytope_util.cpp +++ b/src/extensions/polytope/codac2_Polytope_util.cpp @@ -28,7 +28,7 @@ #include "codac2_Vector.h" #include "codac2_Row.h" #include "codac2_Facet.h" -#include "codac2_dd.h" +#include "codac2_Polytope_dd.h" #include "codac2_Polytope_util.h" @@ -36,8 +36,8 @@ namespace codac2 { /** read a .ine file and return a list of facets * this is a very crude function, which assumes the format of the - * file is correct, and no real check is done */ -std::vector read_ineFile(const char *filename) { + * file is correct, and no real check is done. Linearities are not treated. */ +CollectFacets read_ineFile(const char *filename) { std::ifstream fichier(filename); assert(!fichier.fail()); /* skip everything until begin */ @@ -52,8 +52,7 @@ std::vector read_ineFile(const char *filename) { fichier >> nbfacets >> dim >> dummy; std::cout << nbfacets << " facettes " << dim << " dimensions" << std::endl; dim = dim-1; /* non-normalised dimension */ - std::vector result; - result.reserve(nbfacets); + CollectFacets result; for (Index i=0;i read_ineFile(const char *filename) { fichier >> row[j]; } row = -row; - result.push_back(Facet(row,rhs,false)); + result.insert_facet(row,rhs,false); + } + fichier.close(); + return result; +} + +/** read a .ext file and return a list of vertices + * this is a very crude function, which assumes the format of the + * file is correct, and no real check is done. Rays (or lines) + * are not treated for now. */ +std::vector read_extFile(const char *filename) { + std::ifstream fichier(filename); + assert(!fichier.fail()); + /* skip everything until begin */ + std::string line; + while (getline(fichier,line)) { + std::cout << line << std::endl; + if (line.compare("begin")==0) break; + } + assert(fichier.good()); + Index nbvertices, dim; + char dummy[50]; + fichier >> nbvertices >> dim >> dummy; + std::cout << nbvertices << " sommets " << (dim-1) << " dimensions" << std::endl; + dim = dim-1; /* non-normalised dimension */ + std::vector result; + for (Index i=0;i> rhs; /* 'rhs' is supposed to be 1 */ + for (Index j=0;j> vrt[j]; + } + result.push_back(vrt); } fichier.close(); return result; diff --git a/src/extensions/polytope/codac2_Polytope_util.h b/src/extensions/polytope/codac2_Polytope_util.h index fc2f8d93c..1beb04d08 100644 --- a/src/extensions/polytope/codac2_Polytope_util.h +++ b/src/extensions/polytope/codac2_Polytope_util.h @@ -28,16 +28,22 @@ #include "codac2_Vector.h" #include "codac2_Row.h" #include "codac2_Facet.h" -#include "codac2_dd.h" +#include "codac2_Polytope_dd.h" namespace codac2 { /** read a .ine file and return a list of facets * this is a very crude function, which assumes the format of the - * file is correct + * file is correct. Equalities are not treated for now. * @param filename name of the file */ -std::vector read_ineFile(const char *filename); +CollectFacets read_ineFile(const char *filename); + +/** read a .ext file and return a list of vertices + * this is a very crude function, which assumes the format of the + * file is correct. Rays are not treated for now. + * @param filename name of the file */ +std::vector read_extFile(const char *filename); /** construct the list of facets of a 3D-polyhedron from the buildF2V * structure From dd0ecdf45338f30ebe53c2bff38ea7604c15439b Mon Sep 17 00:00:00 2001 From: damien-masse Date: Wed, 5 Nov 2025 15:13:20 +0100 Subject: [PATCH 10/26] temporary commit --- src/extensions/polytope/clp/codac2_clp.h | 1 + src/extensions/polytope/codac2_Facet.cpp | 126 +++++++++++- src/extensions/polytope/codac2_Facet.h | 80 +++++++- src/extensions/polytope/codac2_Polytope.cpp | 180 +++++++++++++----- src/extensions/polytope/codac2_Polytope.h | 69 ++++--- .../polytope/codac2_Polytope_dd.cpp | 6 +- 6 files changed, 374 insertions(+), 88 deletions(-) diff --git a/src/extensions/polytope/clp/codac2_clp.h b/src/extensions/polytope/clp/codac2_clp.h index e97f23c30..986df256f 100644 --- a/src/extensions/polytope/clp/codac2_clp.h +++ b/src/extensions/polytope/clp/codac2_clp.h @@ -310,6 +310,7 @@ class LPclp { bool built; bool built_emptytest; ClpSimplex *model=nullptr; + Index offset; /* offset between Id of Afacets and row in the model */ Interval Valobj; IntervalVector primalSol; IntervalRow dualSol; diff --git a/src/extensions/polytope/codac2_Facet.cpp b/src/extensions/polytope/codac2_Facet.cpp index 17cb719de..1a2eab5d5 100644 --- a/src/extensions/polytope/codac2_Facet.cpp +++ b/src/extensions/polytope/codac2_Facet.cpp @@ -93,13 +93,12 @@ Interval bound_linear_form(const Facet &f, const double &rhs = f.second.rhs; const Index &bdim = f.first.bdim; const bool &eqcst = f.second.eqcst; - if (bdim==-1) return row2.dot(b); - Index abdim = bdim/(2*row.size()); - bool neg = (abdim>=row.size()); - if (neg) { abdim -= row.size(); } + if (bdim==-1) return Interval(); + Index abdim = base.gtDim(); + bool neg = (f.row[abdim]<0.0); if (!eqcst && ((!neg && row2[abdim]<=0.0) || (neg && row2[abdim]>=0))) - return row2.dot(b); + return Interval(); Interval quotient = Interval(row2[abdim])/Interval(row[abdim]); Interval res=quotient*rhs; IntervalRow rem=row2-quotient*row; @@ -115,7 +114,7 @@ std::ostream& operator<<(std::ostream& os, const Facet& f) { } CollectFacets::CollectFacets(const Matrix &mat, const Vector &rhsvect, - const std::vector &eqSet) { + const std::vector &eqSet) : dim(mat.cols()) { assert(mat.rows()==rhsvect.size()); for (Index i=0;i a =this->insert_facet(mat.row(i),rhsvect[i],false); @@ -308,4 +307,119 @@ CollectFacets::mapIterator return ret; } +bool CollectFacets::removeFacetById(Index id) { + if (_allFacets[id-1]==_map.end()) return false; + if (_allFacets[id-1]->second.eqcst) { + this->remove_in_eqFacets(id-1); + } + _map.erase(_allFacets[id-1]); + _allFacets[id-1]=_map.end(); + return true; +} + + +IntervalVector CollectFacets::extractBox() { + assert_release(this->getDim()!=-1); + /* check emptiness with null row */ + Row row = Row::zero(this->dim); + mapIterator it = _map.find(FacetBase(row)); + if (it!=_map.end()) { + FacetRhs &rhs = it->second; + if (rhs.rhs<0.0 || (rhs.rhs>0.0 && rhs.eqcst)) + return IntervalVector::constant(this->getDim(),Interval::empty()); + _map.erase(it); + } + /* check bounds if IV */ + IntervalVector ret = IntervalVector(this->getDim()); + for (Index i=0;igetDim();i++) { + double lbound=-oo, ubound=+oo; + row[i]=1.0; + it = _map.find(FacetBase(row)); + if (it!=_map.end()) { + FacetRhs &rhs = it->second; + ubound = rhs.rhs; + if (rhs.eqcst) lbound=rhs.rhs; + _map.erase(it); + } + row[i]=-1.0; + it = _map.find(FacetBase(row)); + if (it!=_map.end()) { + FacetRhs &rhs = it->second; + lbound = std::max(lbound,-rhs.rhs); + if (rhs.eqcst) ubound= std::min(ubound,-rhs.rhs); + _map.erase(it); + } + ret[i] = Interval(lbound,ubound); + } + return ret; +} + +void CollectFacets::renumber() { + /* renumber the constraints */ + Index sineq=0, seq=0; + _allFacets.resize(_map.size()); + for (mapIterator it = _map.begin(); it!=_map.end(); ++it) { + if (it->second.eqcst) { + if (seq>=(Index)_eqFacets.size()) + _eqFacets.push_back(sineq); + else + _eqFacets[seq]=sineq; + ++seq; + } + _allFacets[sineq++]=it; + it->second.Id = sineq; + } + if (seq<(Index)_eqFacets.size()) + _eqFacets.resize(seq); +} + +void include_vertices(const std::vector &vertices, + IntervalVector &bbox, bool tight) { + assert_release(this->getDim()!=-1); + if (tight) bbox=IntervalVector(this->getDim(),Interval::empty()); + if (vertices.size()==0) return; + for (mapIterator it = _map.begin(); it!=_map.end(); ++it) { + IntervalRow row = it->first.row; + Interval a = (tight ? Interval::empty() : Interval(it->second.rhs)); + for (IntervalVector &v : vertices) { + a |= row.dot(v); + } + it->second.rhs=a.ub(); + if (it->second.eqcst && !a.is_degenerated()) { + this->remove_in_eqFacets(it->second.Id-1); + it->second.eqcst=false; + this->insert_facet(-row,-a.lb(),false); + /* note : possible that the new facet will be visited + later, but that should not be a real problem */ + } + } + for (IntervalVector &v : vertices) { + bbox |= v; + } +} +void include_vertices(const std::vector &vertices, + IntervalVector &bbox, bool tight) { + assert_release(this->getDim()!=-1); + if (tight) bbox=IntervalVector(this->getDim(),Interval::empty()); + if (vertices.size()==0) return; + for (mapIterator it = _map.begin(); it!=_map.end(); ++it) { + IntervalRow row = it->first.row; + Interval a = (tight ? Interval::empty() : Interval(it->second.rhs)); + for (Vector &v : vertices) { + a |= row.dot(v); + } + it->second.rhs=a.ub(); + if (it->second.eqcst && !a.is_degenerated()) { + this->remove_in_eqFacets(it->second.Id-1); + it->second.eqcst=false; + this->insert_facet(-row,-a.lb(),false); + /* note : possible that the new facet will be visited + later, but that should not be a real problem */ + } + } + for (Vector &v : vertices) { + bbox |= IntervalVector(v); + } +} + } diff --git a/src/extensions/polytope/codac2_Facet.h b/src/extensions/polytope/codac2_Facet.h index 3149e8073..c2a077e5d 100644 --- a/src/extensions/polytope/codac2_Facet.h +++ b/src/extensions/polytope/codac2_Facet.h @@ -59,17 +59,32 @@ struct FacetBase { std::swap(this->bdim,fb.bdim); std::swap(this->bdim,fb.bdim); } + + /** return true if the Row is zero */ + inline bool isNull() const { return (bdim==-1); } + + /** return true if the Row is zero outside the greatest dimension */ + inline bool isCoord() const { + if (row.size()==0) return false; + return (bdim%(2*row.size())==0); + } + + /** return the greatest dimension (between 0 and dim-1) */ + inline Index gtDim () const { + if (row.size()==0) return -1; + return (bdim/(2*row.size()))%row.size(); + } - void change_row(Row &&row) { + inline void change_row(Row &&row) { this->row=row; this->bdim=-1; this->vdim=0.0; this->compute_key(); } - void change_row(const Row &row) { + inline void change_row(const Row &row) { this->row=row; this->bdim=-1; this->vdim=0.0; this->compute_key(); } - void negate_row() { + inline void negate_row() { this->row=-row; Index u = 2*row.size()*row.size(); if (this->bdim>=u) this->bdim-=u; else this->bdim+=u; @@ -98,7 +113,7 @@ struct FacetBase { } } if (valabs==0.0) return; /* bdim=-1 */ - if (b2dim==-1) b2dim=bdim; + if (b2dim==-1) { bdim = 2*row.size()*bdim; vdim=0.0; return; } if (b2dim>bdim) bdim = bdim * (2*row.size()+1) + 2*row.size() - b2dim; else @@ -143,7 +158,9 @@ namespace Facet_ { inline Facet make(Row &&row, double rhs, bool eqcst) { return std::make_pair(FacetBase(row),FacetRhs(rhs,eqcst,0)); } - + inline Facet make(const FacetBase &base, double rhs, bool eqcst) { + return std::make_pair(base,FacetRhs(rhs,eqcst,0)); + } inline void swap_Facet(Facet &f1,Facet &f2) { f1.first.swap(f2.first); @@ -213,8 +230,12 @@ class CollectFacets { using mapIterator = mapType::iterator; using mapCIterator = mapType::const_iterator; - /** generate an empty collection */ - CollectFacets() {}; + /** generate an empty collection, without dimension */ + CollectFacets() : dim(-1) {}; + /** generate an empty collection, with dimension + * @param dim the dimension + */ + CollectFacets(Index dim) : dim(dim) {}; /** generate the set of facets from a matrix and a vector, i.e. * mat X <= rhs (eqSet states which rows of mat are equalities) * @param mat the matrix @@ -222,7 +243,12 @@ class CollectFacets { * @eqSet the set of equalities */ CollectFacets(const Matrix &mat, const Vector &rhsvect, const std::vector &eqSet); + + /** create a copy of the CollectFacets */ + CollectFacets(const CollectFacets& cf); + /** return the dimension of the facets */ + Index getDim() const; /** return the number of facets, as the size of _allfacets */ Index nbfcts() const; /** return the number of equality facets, as the size of _eqfacets */ @@ -327,7 +353,32 @@ class CollectFacets { mapIterator dissociate_eqFacet(Index eqId , double nbound, ACTION_DUPLICATE act=KEEP_RHS); + /** remove a facet by its Id + * @param id Id of the facet (starting from 1) + * @return true if the facet existed and has been removed */ + bool removeFacetById(Index id); + + /** extract the constraints corresponding to the bounding box, + * or a possible "emptiness constraint". + * remove some contraints, may put endFacet() for some Id */ + IntervalVector extractBox(); + + /** renumber the set, removing spurious Id. + * do not modify the iterators, but modify the indices of the + * facets and equality facets. */ + void renumber(); + + /** modify the rhs such that the polyhedron contains a list + * of intervalVector. Also adapt the bbox. + * if tight=true, may tighten the bound, and not only increase them. + * may add facets if needed to dissociate equality facets. */ + void include_vertices(const std::vector &vertices, + IntervalVector &bbox, bool tight); + void include_vertices(const std::vector &vertices, + IntervalVector &bbox, bool tight); + private: + mutable Index dim; mapType _map; std::vector _allFacets; std::vector _eqFacets; /* index in _allFacets */ @@ -340,6 +391,19 @@ class CollectFacets { double rhs, bool eqcst, ACTION_DUPLICATE act); }; +inline CollectFacets::CollectFacets(const CollectFacets& cf) : + dim(cf.getDim()), _map(cf._map), _allFacets(cf._allFacets), + _eqFacets(cf._eqFacets) +{ +} + +inline Index CollectFacets::getDim() const { + if (this->dim!=-1) return this->dim; + if (_map.size()==0) return -1; + this->dim=(_map.begin()->first.row.size()); + return this->dim; +} + inline const CollectFacets::mapType &CollectFacets::get_map() const { return _map; } @@ -349,7 +413,7 @@ inline CollectFacets::mapIterator CollectFacets::get_eqFacet(Index id) { return this->_allFacets[this->_eqFacets[id]]; } inline Index CollectFacets::nbfcts() const { - return this->_allFacets.size(); + return this->_map.size(); } inline Index CollectFacets::nbeqfcts() const { diff --git a/src/extensions/polytope/codac2_Polytope.cpp b/src/extensions/polytope/codac2_Polytope.cpp index 741e3747f..64245a6df 100644 --- a/src/extensions/polytope/codac2_Polytope.cpp +++ b/src/extensions/polytope/codac2_Polytope.cpp @@ -32,10 +32,9 @@ using namespace codac2; -#if 0 namespace codac2 { -Polytope::Polytope() : _dim(-1), _nbEqcsts(0), _nbineqCsts(0), _empty(true) +Polytope::Polytope() : _dim(-1), _empty(true) { } @@ -43,10 +42,14 @@ Polytope::Polytope(Index dim) : Polytope(dim,false) { } -Polytope::Polytope(Index dim, bool empty) : _dim(dim), _nbEqcsts(0), _nbineqCsts(0), _empty(empty) +Polytope::Polytope(Index dim, bool empty) : _dim(dim), _empty(empty), + _box(dim, empty ? Interval::empty() : Interval()), + _facets(std::make_shared(dim)), + _box_updated(true) { } +#if 0 void Polytope::build_clpForm_from_box() { _nbEqcsts=0; _nbineqCsts=0; @@ -72,65 +75,117 @@ void Polytope::build_clpForm_from_box() { } _clpForm=std::make_unique(_box.size(),built); } +#endif -Polytope::Polytope(const IntervalVector &box) : Polytope(box.size()) +Polytope::Polytope(const IntervalVector &box) : _dim(box.size()), + _empty(box.is_empty()), _box(box), + _facets(std::make_shared(dim)), + _box_updated(true) { - if (box.is_empty()) { _empty=true; return; } - _box=box; - this->build_clpForm_from_box(); } -Polytope::Polytope(const Polytope &P) : _dim(P._dim), _nbEqcsts(P._nbEqcsts), - _nbineqCsts(P._nbineqCsts), _empty(P._empty), - _box(P._box) { - if (P._clpForm) this->_clpForm = std::make_unique(*P._clpForm); - +Polytope::Polytope(const Polytope &P) : _dim(P._dim), _empty(P._empty), + _box(P._box), + _facets(P._empty ? std::make_shared(dim) : + std::make_shared(P._facets)), + _box_updated(P._box_updated) +{ } Polytope::Polytope(const std::vector &vertices) : Polytope() { - /* note : for now we just take the bounding box of the vertices, - so that's definitely not good */ - if (vertices.empty()) return; - _empty= false; - _box = *(vertices.begin()); - for (auto v : vertices) { - _box |= v; - } - this->build_clpForm_from_box(); + if (vertices.empty()) return; + _dim=vertices[0].size(); + _empty= false; + /* build the V2F form */ + _DDbuildV2F = std::make_unique(1,vertices[0]); + for (Index i=1;i<(Index)vertices.size();i++) { + _DDbuildV2F->add_vertex(i+1,vertices[i]); + } + /* get the CollectFacets */ + _facets=_DDbuildV2F->getFacets(); + _box = _facets->extractBox(); + _facets->include_vertices(vertices,_box,true); + _box_updated=true; + _buildV2F_updated=true; /* keep buildV2F ? */ } -Polytope::Polytope(const std::vector &vertices, - const std::vector &facetforms) - : _dim(facetforms[0].size()), - _nbEqcsts(0), _nbineqCsts(facetforms.size()), - _empty(vertices.empty()) { - if (vertices.empty()) { _nbineqCsts=0; return; } - _box = *(vertices.begin()); - for (auto v : vertices) { - _box |= v; - } - std::vector facets; - for (const auto &row : facetforms) { - IntervalRow rI(row); /* use intervals to ensure upward bound */ - double bnd = -oo; - for (const auto &vtx : vertices) { - bnd = std::max(bnd,rI.dot(vtx).ub()); - } - facets.emplace_back(row,bnd,false); +Polytope::Polytope(const std::vector &vertices, + const CollectFacets &facetsform) : Polytope() +{ + _dim = facetsform.getDim(); + if (_dim==-1) return; + _facets=std::make_shared(facetsform); + if (vertices.empty()) { + _box = InvervalVector::constant(_dim,Interval::empty()); + _empty=true; + return; } - _clpForm=std::make_unique(_dim,facets,_box); + _facets=_DDbuildV2F->getFacets(); + _box = _facets->extractBox(); + _facets->include_vertices(vertices,_box,true); + _box_updated=true; } -Polytope::Polytope(const IntervalVector &box, - const std::vector &facets, bool minimize) : Polytope(box.size()) { - _box=box; - _clpForm = std::make_unique(_box.size(), facets); +Polytope::Polytope(const IntervalVector &box, + const std::vector> &facets, + bool minimize) : Polytope(box) { + for (const std::pair &cst : facets) { + this->add_constraint(cst); + } if (minimize) this->minimize_constraints(); } + +Interval Polytope::fast_bound(const FacetBase &base) const { + if (base.isNull()) return Interval::zero(); + Index gdim = base.gtDim(); + Interval res; + if (base.row[gdim]>0.0) { + res= Interval(_box[gdim].ub())*base.row[gdim]; + } else { + res= Interval(-_box[gdim].lb())*base.row[gdim]; + } + if (!base.isCoord()) { + Row bcopy = base.row; + bcopy[gdim]=0.0; + res += bcopy.dot(_box); + std::pair + pIt = _facets->equal_range(base); + if (pIt.first!=pIt.end) { /* we found an exact key */ + return Interval(pIt.first->second.rhs); + } else { + if (pIt.first!=_facets->get_map().begin()) --pIt.first; + Interval a1 = bound_linear_form(*(pIt.first), base.row, _box); + if (pIt.second!=_facets->endFacet()) { + Interval a2 = bound_linear_form(*(pIt.second), base.row, _box); + if (a2.ub()nbeqfcts();i++) { + CollectFacets::mapIterator it = _facets.get_eqFacet(i); + Interval a = bound_linear_form(*(pIt.first), base.row, _box); + if (a.ub() &vertices=_DDbuildF2V->get_vertices(); + for (const DDvertex &vt : vertices) { + IntervalVector vtB = _DDbuildF2V->compute_vertex(vt); + Interval a = base.row.dot(vtB); + if (a.ub()build_clpForm_from_box(); } +#endif + +bool Polytope::add_constraint(const std::pair& facet, + double tolerance) { + if (_empty) return false; + FacetBase base = FacetBase(facet.first); + if (base.isNull()) { + if (facet.second>=tolerance) return false; + this->set_empty(); return true; + } + Interval act = this->fast_bound(base); + if (act.ub()<=facet.second.tolerance) return false; + if (base.isCoord()) { + Index gdim = base.gtDim(); + Interval val = Interval(facet.second)/row[gdim]; + if (row[gdim]>0.0) { + _box[gdim] &= Interval(-oo,val.ub()); + if (_box[gdim].is_empty()) { + this->set_empty(); + return true; + } + } else { + _box[gdim] &= Interval(-val.ub(),+oo); + if (_box[gdim].is_empty()) { + this->set_empty(); + return true; + } + } + /////////////// update ? + return true; + } + auto res= _facets->insert_facet(facet.first, facet.second, + false, CollectFacets::MIN_RHS); + //////// update ? + return res.second; +} + } #endif diff --git a/src/extensions/polytope/codac2_Polytope.h b/src/extensions/polytope/codac2_Polytope.h index c0fadbea7..01f821d7e 100644 --- a/src/extensions/polytope/codac2_Polytope.h +++ b/src/extensions/polytope/codac2_Polytope.h @@ -32,8 +32,6 @@ #include "codac2_Facet.h" #include "codac2_Polytope_dd.h" -#if 0 - namespace codac2 { /** @@ -76,14 +74,13 @@ namespace codac2 { Polytope(const std::vector &vertices); /** - * \brief Constructs a polytope from a set of vertices - * and a set of linear forms, i.e. the minimal polytope - * built on the linear forms that includes the vertices. + * \brief Constructs a polytope from a set of vertices (intervalVector) + * and a set of linear forms, described as a CollectFacets * \param vertices the vertices - * \param facetforms the facets description (must NOT be empty) + * \param facetforms the facets description (the CollectFacets) */ - Polytope(const std::vector &vertices, - const std::vector &facetforms); + Polytope(const std::vector &vertices, + const CollectFacets &facetsform); /** * \brief from a slanted box (M.V with reverse known) @@ -106,7 +103,7 @@ namespace codac2 { * a set of constraints */ Polytope(const IntervalVector &box, - const std::vector &facets, + const std::vector> &facets, bool minimize=false); /** @@ -165,14 +162,24 @@ namespace codac2 { */ Vector mid() const; + /** + * \brief bound a constraint (fast), either by a "close" one (+box) + * or by the vertices if they are computed. + * no update or LP are done + * return an interval I : row X is guaranteed to be less than I.ub() + * and I.lb gives an indication of the precision of the computation + * (i.e. if diam(I) is low, a constraint close to row was used + * \param fbase the constraint, expressed as a FacetBase + * \return the interval I */ + Interval fast_bound(const FacetBase &base) const; + /** \brief ``distance'' from the satisfaction of a constraint * ie the inflation of the rhs needed to satisfy the constraint * \param fc the constraint - * \return the distance + * \return an interval */ double distance_cst (const Facet &fc) const; - /** * \brief relationships with a box (fast check) * \param p the box @@ -217,8 +224,8 @@ namespace codac2 { * * \return the updated hull box */ - const IntervalVector &update_box(); - + const IntervalVector &update_box() const; + /** * \brief test if bounding box is included * (mainly useful if current bounding box is tight) @@ -235,6 +242,14 @@ namespace codac2 { /** \brief set to (singleton) 0 */ void clear(); + /** \brief add a inequality (pair row X <= rhs) + * do basic checks, but do not minimize the system + * \param cst the constraint + * \return false if the (basic) checks showed the constraint to + * be redundant (or inside tolerance), true if added */ + bool add_constraint(const std::pair& facet, + double tolerance=0.0); + /** \brief inflation by a cube * this <- this + [-rad,rad]^d * \param rad radius of the box @@ -265,31 +280,27 @@ namespace codac2 { /** - * \brief Computes the sets of vertices of the polytope + * \brief Computes a set of vertices enclosing the polytope * - * WARNING: the results is unsafe: no guarantee is given - * that the convex hull of the result fully encloses the polytope - * works only for bounded polyhedron, suppose _box exists and is bounded - * * \return set of vertices, as Vectors */ std::vector vertices() const; private: Index _dim; /* dimension */ - Index _nbEqcsts; /* nb of equality constraints */ - Index _nbineqCsts; /* nb of inequality constraints */ - bool _empty; /* is empty */ - IntervalVector _box; /* bounding box */ - mutable std::unique_ptr _clpForm; /* LPclp formulation */ + mutable bool _empty; /* is empty */ + mutable IntervalVector _box; /* bounding box */ + mutable std::share_ptr _facets; + /* "native" formulation , may be shared by other formulations */ + mutable std::unique_ptr _clpForm; /* LPclp formulation, if used */ mutable std::unique_ptr _DDbuildF2V; - /* DDbuildF2V formulation */ + /* DDbuildF2V formulation, if used */ mutable std::unique_ptr _DDbuildV2F; - /* DDbuildV2F formulation */ - bool _box_updated; - bool _clpFrom_updated; - bool _buildF2V_updated; - bool _buildV2F_updated; + /* DDbuildV2F formulation, generally not used */ + mutable bool _box_updated; + mutable bool _clpFrom_updated; + mutable bool _buildF2V_updated; + mutable bool _buildV2F_updated; void build_clpForm_from_box(); void minimize_constraints(); }; diff --git a/src/extensions/polytope/codac2_Polytope_dd.cpp b/src/extensions/polytope/codac2_Polytope_dd.cpp index 29cbfa4a4..bdfb5eb3c 100644 --- a/src/extensions/polytope/codac2_Polytope_dd.cpp +++ b/src/extensions/polytope/codac2_Polytope_dd.cpp @@ -831,6 +831,7 @@ int DDbuildV2F::add_vertex(Index idVertex, const IntervalVector& vertex) { } /* no equalities removed */ for (DDfacet &d : facets) { + if (d.status==DDfacet::FACETREM) continue; Interval calc = d.facetIt->first.row.dot(vertex)-d.facetIt->second.rhs; d.lambda = calc.lb(); if (calc.mig()tolerance) { d.status=DDfacet::FACETON; } @@ -854,7 +855,7 @@ int DDbuildV2F::add_vertex(Index idVertex, const IntervalVector& vertex) { continue; } for (std::forward_list::iterator idL : d.links) { - if (idL==facets.end()) continue; /* ? */ + if (idL==facets.end()) continue; /* can it happen ? */ DDfacet &d2 = (*idL); if (d2.status==DDfacet::FACETON) { d2.removeLnk(it); @@ -877,6 +878,7 @@ int DDbuildV2F::add_vertex(Index idVertex, const IntervalVector& vertex) { #endif cnt++; } + facetsptr->removeFacetById(it->facetIt->second.Id); this->removeFacet(it,false); } /*construction of new links for the eq facets */ @@ -926,6 +928,7 @@ int DDbuildV2F::add_vertex(Index idVertex, const IntervalVector& vertex) { } facets.remove_if([](const DDfacet &v) { return v.status==DDfacet::FACETREM; }); + facetsptr->renumber(); return cnt; } @@ -971,6 +974,7 @@ void DDbuildV2F::removeFacet(std::forward_list::iterator It, it->removeLnk(It); } } + d.facetIt=facetsptr->endFacet(); d.status=DDfacet::FACETREM; } From f90a8275f1520965537836fd4dec9b6cfbc73c7b Mon Sep 17 00:00:00 2001 From: damien-masse Date: Mon, 10 Nov 2025 11:27:23 +0100 Subject: [PATCH 11/26] correction? --- src/CMakeLists.txt | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 628fad0ca..c98bbd3d7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -137,25 +137,6 @@ endif() - #if(WITH_IBEX) # included by default for now - - file(APPEND ${CODAC_CMAKE_CONFIG_FILE} " - - # Optional 3rd party: - - find_package(IBEX REQUIRED) - - find_path(CODAC_IBEX_INCLUDE_DIR ${PROJECT_NAME}-ibex.h - PATH_SUFFIXES include/${PROJECT_NAME}-ibex) - set(CODAC_INCLUDE_DIRS \${CODAC_INCLUDE_DIRS} \${CODAC_IBEX_INCLUDE_DIR}) - - find_library(CODAC_IBEX_LIBRARY NAMES ${PROJECT_NAME}-ibex - PATH_SUFFIXES lib) - - set(CODAC_LIBRARIES \${CODAC_LIBRARIES} \${CODAC_IBEX_LIBRARY} Ibex::ibex) - ") - - #endif() file(APPEND ${CODAC_CMAKE_CONFIG_FILE} " set(CODAC_LIBRARIES \${CODAC_LIBRARIES} \${CODAC_GRAPHICS_LIBRARY} \${CODAC_CORE_LIBRARY}) From 5f0dcdc7ed960266feda8cd5af414df87aca6385 Mon Sep 17 00:00:00 2001 From: damien-masse Date: Fri, 14 Nov 2025 13:53:40 +0100 Subject: [PATCH 12/26] First tests, hull... --- examples/11_polytope/main-3Dgraphics.cpp | 44 - examples/11_polytope/main-V2F.cpp | 33 - .../CMakeLists.txt | 2 +- examples/polytope_examples/bla.ine | 17 + examples/polytope_examples/bug45.ine | 14 + examples/polytope_examples/cross10.ine | 1030 +++++ examples/polytope_examples/cross12.ine | 4102 +++++++++++++++++ examples/polytope_examples/cross8.ine | 261 ++ examples/polytope_examples/cube3.ine | 12 + examples/polytope_examples/cubocta.ine | 20 + examples/polytope_examples/grcubocta.ine | 32 + examples/polytope_examples/icododeca.ine | 40 + examples/polytope_examples/irbox20-4.ext | 26 + examples/polytope_examples/irbox200-4.ext | 206 + .../polytope_examples/main-3Dgraphics.cpp | 48 + examples/polytope_examples/main-3Dhull.cpp | 57 + .../main-F2V.cpp | 15 +- examples/polytope_examples/main-V2F.cpp | 28 + examples/polytope_examples/sampleh8.ine | 110 + src/extensions/polytope/clp/codac2_clp.cpp | 450 +- src/extensions/polytope/clp/codac2_clp.h | 33 +- src/extensions/polytope/codac-polytope.h | 5 + src/extensions/polytope/codac2_Facet.cpp | 68 +- src/extensions/polytope/codac2_Facet.h | 28 +- src/extensions/polytope/codac2_Polytope.cpp | 311 +- src/extensions/polytope/codac2_Polytope.h | 92 +- .../polytope/codac2_Polytope_dd.cpp | 90 +- src/extensions/polytope/codac2_Polytope_dd.h | 79 +- .../polytope/codac2_Polytope_util.cpp | 6 +- .../extensions/polytope/codac2_tests_clp.cpp | 259 ++ 30 files changed, 7087 insertions(+), 431 deletions(-) delete mode 100644 examples/11_polytope/main-3Dgraphics.cpp delete mode 100644 examples/11_polytope/main-V2F.cpp rename examples/{11_polytope => polytope_examples}/CMakeLists.txt (96%) create mode 100644 examples/polytope_examples/bla.ine create mode 100644 examples/polytope_examples/bug45.ine create mode 100644 examples/polytope_examples/cross10.ine create mode 100644 examples/polytope_examples/cross12.ine create mode 100644 examples/polytope_examples/cross8.ine create mode 100644 examples/polytope_examples/cube3.ine create mode 100644 examples/polytope_examples/cubocta.ine create mode 100644 examples/polytope_examples/grcubocta.ine create mode 100644 examples/polytope_examples/icododeca.ine create mode 100644 examples/polytope_examples/irbox20-4.ext create mode 100644 examples/polytope_examples/irbox200-4.ext create mode 100644 examples/polytope_examples/main-3Dgraphics.cpp create mode 100644 examples/polytope_examples/main-3Dhull.cpp rename examples/{11_polytope => polytope_examples}/main-F2V.cpp (72%) create mode 100644 examples/polytope_examples/main-V2F.cpp create mode 100644 examples/polytope_examples/sampleh8.ine create mode 100644 src/extensions/polytope/codac-polytope.h create mode 100644 tests/extensions/polytope/codac2_tests_clp.cpp diff --git a/examples/11_polytope/main-3Dgraphics.cpp b/examples/11_polytope/main-3Dgraphics.cpp deleted file mode 100644 index 849cfb91b..000000000 --- a/examples/11_polytope/main-3Dgraphics.cpp +++ /dev/null @@ -1,44 +0,0 @@ -// Author : Damien Massé -// Read a description of 3D polyhedron in inequalities form (.ine file) -// and draw it - -#include -#include -#include -#include - -using namespace std; -using namespace codac2; - -int main(int argc, char *argv[]) -{ - std::cout << std::scientific << std::setprecision(9); - - - const char *namefile = "cubocta.ine"; - if (argc>1) namefile= argv[1]; - std::shared_ptr fc = - std::make_shared(read_ineFile(namefile)); - std::cout << "fichier lu avec " << fc->nbfcts() << " facettes.\n"; - IntervalVector box = IntervalVector::constant((*fc)[0]->first.row.size(), - Interval(-10,10)); - std::vector vide; - DDbuildF2V build(box.size(),box,fc,false); - Index i=0; - for (int i=0;inbfcts();i++) { - build.add_facet(i); - } - std::cout << "built \n"; - std::vector> facets3D=build_3Dfacets(build); - std::cout << "nb facets : " << facets3D.size() << "\n"; - - Figure3D fig_poly("3D polyhedron"); - - Vector center = Vector::zero(3); - Matrix transfo = Matrix::Identity(3,3); - for (const std::vector &vec : facets3D) { - fig_poly.draw_polygon(center,transfo,vec); - } - - return 0; -} diff --git a/examples/11_polytope/main-V2F.cpp b/examples/11_polytope/main-V2F.cpp deleted file mode 100644 index bedc6bb9e..000000000 --- a/examples/11_polytope/main-V2F.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// Author : Damien Massé -// Transformation vertices -> facets from a .ext file - -#include -#include -#include -#include -#include - -using namespace std; -using namespace codac2; - - -int main(int argc, char *argv[]) -{ - std::cout << std::scientific << std::setprecision(9); - - - const char *namefile = "irbox20-4.ext"; - if (argc>1) namefile= argv[1]; - std::vector vx = read_extFile(namefile); - std::cout << "File read with " << vx.size() << " vertices.\n"; - std::vector vide; - DDbuildV2F build2(1,vx[0]); - std::cout << "Initial build : " << build2 << std::endl; - for (int i=1;i +#include +#include +#include + +using namespace std; +using namespace codac2; + +void draw_polytope(Figure3D &fig, const Polytope &P, const StyleProperties &st) { + std::vector> facets3D=P.compute_3Dfacets(); + Vector center = Vector::zero(3); + Matrix transfo = Matrix::Identity(3,3); + for (const std::vector &vec : facets3D) { + fig.draw_polygon(center,transfo,vec,st); + } +} + +int main(int argc, char *argv[]) +{ + Figure3D fig("3D polytopes"); + + /* box */ + IntervalVector box1 { { 1, 3 }, { 1, 2 }, { 2, 2.5 } }; + Polytope pbox1(box1); + draw_polytope(fig,pbox1,StyleProperties(Color::dark_green(),"box")); + + /* flat box */ + IntervalVector box2 { { 1, 3 }, { -1, -1 }, { 2, 2.5 } }; + Polytope pbox2(box2); + draw_polytope(fig,pbox2,StyleProperties(Color::dark_red(),"flat box")); + + /* parallelepiped */ + Polytope paral(Parallelepiped({-1,1,1}, {{ 0.5,0.6,0.0 }, + { -0.3,0.0,0.4 }, + { 0.0, -0.6, 0.8 }})); + draw_polytope(fig,paral,StyleProperties(Color::dark_blue(),"parallelepiped")); + + /* zonotope */ + Polytope zono(Zonotope({-4,1,1}, {{ 0.5,0.6,0.0,0.4,0.0,1.1 }, + { -0.3,0.0,0.4,-0.5,0.3,-0.3 }, + { -0.2,0.0,0.3,-0.4,-0.2,-0.2 }})); + draw_polytope(fig,zono,StyleProperties(Color::dark_yellow(),"zonotope")); + + return 0; +} diff --git a/examples/polytope_examples/main-3Dhull.cpp b/examples/polytope_examples/main-3Dhull.cpp new file mode 100644 index 000000000..1f4196718 --- /dev/null +++ b/examples/polytope_examples/main-3Dhull.cpp @@ -0,0 +1,57 @@ +// Author : Damien Massé +// Generate random points in [-100,100] and draw the convex hull + +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace codac2; + +void draw_polytope(Figure3D &fig, const Polytope &P, const StyleProperties &st) { + std::vector> facets3D=P.compute_3Dfacets(); + Vector center = Vector::zero(3); + Matrix transfo = Matrix::Identity(3,3); + for (const std::vector &vec : facets3D) { + fig.draw_polygon(center,transfo,vec,st); + } +} + + +std::random_device rd; // Will be used to obtain a seed for the random number engine +std::mt19937 gen(rd()); // Standard mersenne_twister_engine seeded with rd() +std::uniform_real_distribution<> dis(-1.0,1.0); + +double radius=10.0; + +Vector random_point() { + double a=dis(gen)*radius; + double b=dis(gen)*std::sqrt(radius*radius-a*a); + double c=(dis(gen)>0.0 ? 1.0 : -1.0) * std::sqrt(radius*radius-a*a-b*b); + return { a,b,c }; +} + +int main(int argc, char *argv[]) +{ + Figure3D fig("3D hull"); + + std::cout << std::scientific << std::setprecision(9); + + int nbvertices=20; + if (argc>1) nbvertices= std::atoi(argv[1]); + std::vector vertices; + Matrix scale = 0.1*Matrix::Identity(3,3); + for (int i=0;i1) namefile= argv[1]; - std::shared_ptr fc = - std::make_shared(read_ineFile(namefile)); - std::cout << "fichier lu avec " << fc->nbfcts() << " facettes.\n"; + Polytope pol = Polytope::from_ineFile(namefile); + + std::cout << " Polytope lu." << std::endl; + std::vector + lvect = pol.compute_vertices(); + std::cout << lvect.size() << " vertices" << std::endl; + for (IntervalVector a : lvect) + std::cout << a << std::endl; + +#if 0 IntervalVector box = IntervalVector::constant((*fc)[0]->first.row.size(), Interval(-10,10)); DDbuildF2V build2(box.size(),box,fc,false); @@ -30,5 +36,6 @@ int main(int argc, char *argv[]) // std::cout << build2; } std::cout << build2; +#endif return 0; } diff --git a/examples/polytope_examples/main-V2F.cpp b/examples/polytope_examples/main-V2F.cpp new file mode 100644 index 000000000..edd3497bd --- /dev/null +++ b/examples/polytope_examples/main-V2F.cpp @@ -0,0 +1,28 @@ +// Author : Damien Massé +// Transformation vertices -> facets from a .ext file + +#include +#include +#include +#include +#include + +using namespace std; +using namespace codac2; + + +int main(int argc, char *argv[]) +{ + std::cout << std::scientific << std::setprecision(9); + + const char *namefile = "irbox20-4.ext"; + if (argc>1) namefile= argv[1]; + Polytope pol = Polytope::from_extFile(namefile); + + std::vector lst = pol.compute_vertices(); + std::cout << "nb vertices : " << lst.size() << std::endl; + for (int i=0;i &facets, nbRows(facets->nbfcts()), nbCols(dim), Afacets(facets), objvect(objvect), cststat(nbRows,0), - rowBasis(nbRows,false), built(false), model(nullptr), - primalSol(nbCols), dualSol(nbRows), primalRay(nbCols), dualRay(nbRows), + rowBasis(nbRows+nbCols,false), built(false), model(nullptr), + primalSol(nbCols), dualSol(nbRows+nbCols), + primalRay(nbCols), dualRay(nbRows+nbCols), bbox(nbCols) { } @@ -50,8 +51,9 @@ LPclp::LPclp (Index dim, std::shared_ptr &facets) : nbRows(facets->nbfcts()), nbCols(dim), Afacets(facets), objvect(Row::Zero(nbCols)), cststat(nbRows,0), - rowBasis(nbRows,false), built(false), model(nullptr), - primalSol(nbCols), dualSol(nbRows), primalRay(nbCols), dualRay(nbRows), + rowBasis(nbRows+nbCols,false), built(false), model(nullptr), + primalSol(nbCols), dualSol(nbRows+nbCols), + primalRay(nbCols), dualRay(nbRows+nbCols), bbox(nbCols) { } @@ -61,8 +63,9 @@ LPclp::LPclp (Index dim, std::shared_ptr &facets, nbRows(facets->nbfcts()), nbCols(dim), Afacets(facets), objvect(Row::Zero(nbCols)), cststat(nbRows,0), - rowBasis(nbRows,false), built(false), model(nullptr), - primalSol(nbCols), dualSol(nbRows), primalRay(nbCols), dualRay(nbRows), + rowBasis(nbRows+nbCols,false), built(false), model(nullptr), + primalSol(nbCols), dualSol(nbRows+nbCols), + primalRay(nbCols), dualRay(nbRows+nbCols), bbox(box) { } @@ -73,8 +76,9 @@ LPclp::LPclp(const Matrix &mat, const Row &objvect, Afacets(std::make_shared(mat,rhsvect,eqSet)), objvect(objvect), cststat(nbRows,0), - rowBasis(nbRows,false), built(false), model(nullptr), - primalSol(nbCols), dualSol(nbRows), primalRay(nbCols), dualRay(nbRows), + rowBasis(nbRows+nbCols,false), built(false), model(nullptr), + primalSol(nbCols), dualSol(nbRows+nbCols), + primalRay(nbCols), dualRay(nbRows+nbCols), bbox(nbCols) { } @@ -85,8 +89,9 @@ LPclp::LPclp(const Matrix &mat, Afacets(std::make_shared(mat,rhsvect,eqSet)), objvect(Row::Zero(nbCols)), cststat(nbRows,0), - rowBasis(nbRows,false), built(false), model(nullptr), - primalSol(nbCols), dualSol(nbRows), primalRay(nbCols), dualRay(nbRows), + rowBasis(nbRows+nbCols,false), built(false), model(nullptr), + primalSol(nbCols), dualSol(nbRows+nbCols), + primalRay(nbCols), dualRay(nbRows+nbCols), bbox(nbCols) { } @@ -103,8 +108,9 @@ LPclp::LPclp(Index dim) : LPclp::LPclp(const LPclp &P) : nbRows(P.nbRows), nbCols(P.nbCols), Afacets(P.Afacets), objvect(P.objvect), cststat(P.cststat), - rowBasis(nbRows,false), built(false), model(nullptr), - primalSol(nbCols), dualSol(nbRows), primalRay(nbCols), dualRay(nbRows), + rowBasis(nbRows+nbCols,false), built(false), model(nullptr), + primalSol(nbCols), dualSol(nbRows+nbCols), + primalRay(nbCols), dualRay(nbRows+nbCols), bbox(P.bbox), timeout(P.timeout), maxIteration(P.maxIteration), tolerance(P.tolerance) { @@ -140,8 +146,8 @@ Index LPclp::addConstraint(const Facet &facet) { std::pair a = this->Afacets->insert_facet(facet); if (a.first==0) return 0; this->cststat.push_back(0); - this->dualSol.resize(nbRows+1); - this->dualRay.resize(nbRows+1); + this->dualSol.resize(nbRows+nbCols+1); + this->dualRay.resize(nbRows+nbCols+1); if (built) { CoinBuild buildObject; Index nbCols2=nbCols+(built_emptytest ? 1 : 0); @@ -161,6 +167,37 @@ Index LPclp::addConstraint(const Row &vst, double rhs, bool isEq) { return this->addConstraint(Facet_::make(vst,rhs,isEq)); } +Index LPclp::updateConstraint(Index id) { + const Facet &fct = (*(*this->Afacets)[id]); + if (idsetRowUpper(id,fct.second.rhs); + if (fct.second.eqcst) + model->setRowLower(id,fct.second.rhs); + this->status=1<dualSol.resize(nbRows+nbCols); + this->dualRay.resize(nbRows+nbCols); + this->cststat.push_back(0); + if (built) { + CoinBuild buildObject; + Index nbCols2=nbCols+(built_emptytest ? 1 : 0); + int* row2Index = new int[nbCols2]; + for(Index i=0;iaddFacetToCoinBuild(buildObject,fct,0,row2Index,row2Vals); + delete row2Index; + if (built_emptytest) delete row2Vals; + model->addRows(buildObject); + this->status=1<objvect=objvect; if (built && !built_emptytest) { /* change the values of the objective */ @@ -193,6 +230,13 @@ void LPclp::setActive(Index cst, bool state) { } } +void LPclp::update_model_bbox() { + for (Index i=0;isetColumnBounds(i,bbox[i].lb(), bbox[i].ub()); + } +} + + /******* coin-or building and configuration **********/ void LPclp::buildModel(bool emptytest) { @@ -210,8 +254,9 @@ void LPclp::buildModel(bool emptytest) { /* set dimension */ Index nbCols2=nbCols+(emptytest ? 1 : 0); model->resize(0,nbCols2); - for (Index i=0;isetColumnBounds(i,-COIN_DBL_MAX, COIN_DBL_MAX); + this->update_model_bbox(); + if (emptytest) { + model->setColumnBounds(nbCols,-COIN_DBL_MAX,COIN_DBL_MAX); } /* adding the rows using BuildObject ? */ @@ -267,7 +312,7 @@ void LPclp::setModel(bool emptytest) { /* reinitialise the solution */ void LPclp::reset_solution() { status=0; -// rowBasis.reset(); + rowBasis.assign(nbRows+nbCols,false); Valobj=Interval(); primalSol.setZero(); dualSol.setZero(); @@ -292,6 +337,7 @@ LPclp::lp_result_stat LPclp::solve(bool checkempty, int option) { } int LPclp::minimize_eqpolytope() { + /* TODO : manage equalities for bbox */ std::vector triMat; std::vector triRhs; Index nbCsts=0; @@ -343,7 +389,28 @@ int LPclp::minimize_polytope(const Interval &tolerance, bool checkempty) { lp_result_stat stat=this->solve(true); if (stat[EMPTY]) return -1; } - bool solvedOnce=checkempty; + /* minimize box */ + for (Index i=0;isetObjective(a); + lp_result_stat stat=this->solve(false,(i!=0 || checkempty ? 4 : 0)); + if (stat[EMPTY]) return -1; + if (stat[BOUNDED]) { + bbox[i] = min(bbox[i],this->Valobj.ub()); + } + } + for (Index i=0;isetObjective(a); + lp_result_stat stat=this->solve(false,4); + if (stat[EMPTY]) return -1; + if (stat[BOUNDED]) { + bbox[i] = min(bbox[i],this->Valobj.ub()); + } + } + this->update_model_bbox(); for (Index i=nbRows-1;i>=0;i--) { const auto &fct = (*this->Afacets)[i]; if (cststat[i][REMOVED] || @@ -353,7 +420,7 @@ int LPclp::minimize_polytope(const Interval &tolerance, bool checkempty) { if (fct->second.eqcst) { nb++; continue; } this->setActive(i,false); this->setObjective(fct->first.row); - lp_result_stat stat=this->solve(false,(solvedOnce ? 4 : 0)); + lp_result_stat stat=this->solve(false,4); if (stat[EMPTY]) return -1; if (stat[BOUNDED]) { if (this->Valobj.ub()<=fct->second.rhs+tolerance.ub() @@ -364,7 +431,6 @@ int LPclp::minimize_polytope(const Interval &tolerance, bool checkempty) { } nb++; this->setActive(i,true); - solvedOnce=true; } return nb; } @@ -467,6 +533,9 @@ Interval LPclp::dotprodrhs(const IntervalRow &d) const { for (Index i=0;iAfacets)[i]->second.rhs; } + for (Index i=0;isecond.eqcst) initSum[nbCols]+=dualVect[i]; } + for (Index i=0;i0.0) { - return BoolInterval::FALSE; + être libres si bbox est unbounded, + ainsi que les contraintes inactives */ + for (Index i=nbRowsInBasis;i0.0) { return BoolInterval::FALSE; } + if (dualVect[r].ub()>0.0) { ok = BoolInterval::UNKNOWN; } + } + if (bbox[c].lb()==-oo) { + if (dualVect[r].ub()<0.0) { return BoolInterval::FALSE; } + if (dualVect[r].lb()<0.0) { ok = BoolInterval::UNKNOWN; } } } for (Index i=0;idualRowSolution()[i]; } /* col basics */ double slack=0.0; /* only for emptiness checking */ for (Index i=0;igetColumnStatus(i)!=ClpSimplex::Status::basic) + if (model->getColumnStatus(i)!=ClpSimplex::Status::basic) { wholeBasis.push_back(i+nbRows); -// dualVals[i+nbRows]=model->dualColumnSolution()[i]; /* useless for us*/ - if (iprimalColumnSolution()[i]; + rowBasis[i+nbRows]=true; + } + if (idualColumnSolution()[i]; + primalSol[i]=model->primalColumnSolution()[i]; + } else slack = model->primalColumnSolution()[i]; } assert((Index) wholeBasis.size()==nbCols2); @@ -662,13 +747,11 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { otherwise we build it */ double *ray=nullptr; if (!checkempty) { - double *ray = model->infeasibilityRay(false); + double *ray = model->infeasibilityRay(true); assert_release(ray!=nullptr); /* false => bug of CLP */ /* true for infeasibilityRay means that we have also - the negation of the "error" term after the rows, - but we will compute it ourselves. - unless the variables are bounded, this is not needed */ - for (Index i=0;i val * Amat : variables (~0) val*Amat*basisInverse => basisInverse.cols */ @@ -704,8 +787,10 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { /* attempt with Neumaier */ if (!checkempty && !bbox.is_unbounded()) { /* restart with the ray */ - for (Index i=0;isecond.eqcst) assert_release(ray[i]<0.0); + for (Index i=0;isecond.eqcst) + assert_release(ray[i]<0.0); dualRay[i]=ray[i]; } Interval productNeum = this->dotprodrhs(dualRay); @@ -775,7 +860,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { } /* recompute errorNeum */ if (nbok>0) { - for (Index i=0;i0.0) { - ok=BoolInterval::FALSE; - break; - } - } + être libres, ainsi que les contraintes inactives */ + for (Index i=nbRowsInBasis;i0.0) + { ok=BoolInterval::FALSE; break; } + if (dualRay[r].ub()>0.0) + { ok = BoolInterval::UNKNOWN; } + } + if (bbox[c].lb()==-oo) { + if (dualRay[r].ub()<0.0) + { ok= BoolInterval::FALSE; break; } + if (dualRay[r].lb()<0.0) + { ok = BoolInterval::UNKNOWN; } + } + } if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) for (Index i=0;idotprodrhs(dSol); dualSol.setZero(); bool ok=true; for (Index i=0;i0.0) { - if (bbox.is_unbounded()) { ok=false; break; } - status[BOUNDED_BBOX]=true; - product -= dSol[i] * - bbox[wholeBasis[i]-nbRows]; - } + if (dSol[i].mag()==0.0) continue; + int r = wholeBasis[i]; + dualSol[r]=dSol[i]; + int c = r-nbRows; + if (dSol[i].ub()>0.0 && bbox[c].ub()==+oo) { ok=false; break; } + if (dSol[i].lb()<0.0 && bbox[c].lb()==-oo) { ok=false; break; } } } if (ok) { + Interval product = this->dotprodrhs(dualSol); status[BOUNDED]=true; Valobj = min(Valobj,product); } @@ -906,6 +1003,14 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { errorInBasis[i]=max(errorPart[r],Interval::zero()); /* negative is ok */ } + for (Index i=nbRowsInBasis;ibbox[c].ub()) + errorInBasis[i] = primalSol[c]-bbox[c].ub(); + else if (primalSol[c].lb()second.eqcst) errorPart[i]=abs(errorPart[i]); - mxVal=max(errorPart[i],mxVal); - if (mxVal.lb()>0.0) break; + for (Index i=0;isecond.eqcst) errorPart[i]=abs(errorPart[i]); + mxVal=max(errorPart[i],mxVal); + if (mxVal.lb()>0.0) break; + } } if (mxVal.ub()<=0.0) { status[NOTEMPTY]=true; @@ -945,36 +1055,42 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { primalSol and hope to prove the non-emptiness we use another vector to save primalSol and get it back if needed */ - IntervalVector primalSol2=primalSol.mid(); - for (Index i=0;isecond.eqcst) continue; + IntervalVector primalSol2=primalSol & bbox; + bool ok=true; + if (!primalSol2.is_empty()) { + primalSol2=primalSol2.mid(); + for (Index i=0;isecond.eqcst) continue; /* we should not even try, in this case */ - Interval err = (*Afacets)[i]->first.row*primalSol2 - - (*Afacets)[i]->second.rhs; - if (err.ub()>=0.0) - primalSol2 -= (1.1*err.ub()/(*Afacets)[i]->first.row.squaredNorm())*(*Afacets)[i]->first.row; - } - primalSol2=primalSol2.mid(); - for (Index i=0;ifirst.row*primalSol2 - - (*Afacets)[i]->second.rhs; - } + Interval err = (*Afacets)[i]->first.row*primalSol2 + - (*Afacets)[i]->second.rhs; + if (err.ub()>=0.0) + primalSol2 -= (1.1*err.ub()/(*Afacets)[i]->first.row.squaredNorm())*(*Afacets)[i]->first.row; + } + primalSol2=primalSol2 & bbox; + if (!primalSol2.is_empty()) { + primalSol2=primalSol2.mid(); + for (Index i=0;ifirst.row*primalSol2 + - (*Afacets)[i]->second.rhs; + } #ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG - std::cout << "new primalSol : " << primalSol2 << "\n"; - std::cout << "errorPart 2 : " << errorPart << "\n"; + std::cout << "new primalSol : " << primalSol2 << "\n"; + std::cout << "errorPart 2 : " << errorPart << "\n"; #endif - bool ok=true; - for (Index i=0;i0.0) { - ok=false; /* failed */ - break; - } - if ((*Afacets)[i]->second.eqcst && errorPart[i].lb()<0.0) { - ok=false; /* failed */ - break; - } - } + for (Index i=0;i0.0) { + ok=false; /* failed */ + break; + } + if ((*Afacets)[i]->second.eqcst && errorPart[i].lb()<0.0) { + ok=false; /* failed */ + break; + } + } + } else ok=false; + } else ok=false; if (ok) { status[NOTEMPTY]=true; primalSol = primalSol2; @@ -1015,6 +1131,18 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { errorInBasis[i]=max(errorPart[r],Interval::zero()); /* negative is ok */ } + for (Index i=nbRowsInBasis;i-oo) + errorInBasis[i]=primalRay[r]; + else + errorInBasis[i]=max(primalRay[r],Interval::zero()); + else + if (bbox[c].lb()>-oo) + errorInBasis[i]=min(primalRay[r],Interval::zero()); + } /* compute the correction */ IntervalVector correction=basisInverse*errorInBasis; primalRay -= correction; @@ -1023,58 +1151,83 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { } } Interval mxVal(-1.0); - for (Index i=0;isecond.eqcst) errorPart[i]=abs(errorPart[i]); - mxVal=max(errorPart[i],mxVal); + for (Index i=0;i-oo) mxVal = max(-primalSol[i],mxVal); if (mxVal.lb()>0.0) break; - } - if (mxVal.lb()<=0.0) { + } + if (mxVal.lb()<=0.0) { + for (Index i=0;isecond.eqcst) errorPart[i]=abs(errorPart[i]); + mxVal=max(errorPart[i],mxVal); + if (mxVal.lb()>0.0) break; + } + } + if (mxVal.lb()<=0.0) { if (mxVal.ub()>0.0 && !checkempty) { /* change of approach, we slightly move the value and hope to prove the validity we use another vector to save primalSol and get it back if needed */ - IntervalVector primalRay2=primalRay.mid(); - for (Index i=0;isecond.eqcst) continue; - /* we should not even try, in this case */ - Interval err = (*Afacets)[i]->first.row*primalRay2; - if (err.ub()>=0.0) { - primalRay2 -= (1.1*err.ub()/(*Afacets)[i]->first.row.squaredNorm())*(*Afacets)[i]->first.row; - } + /* for bbox, we create a IntervalVector for possible + primalRay */ + IntervalVector admissibleRay(nbCols); + for (Index i=0;i-oo) + admissibleRay[i] &= Interval(-oo,0); } - primalRay2=primalRay2.mid(); - for (Index i=0;ifirst.row*primalRay2; - } + IntervalVector primalRay2=primalRay & admissibleRay; + if (!primalRay2.is_empty()) { + primalRay2 = primalRay2.mid(); + for (Index i=0;isecond.eqcst) continue; + /* we should not even try, in this case */ + Interval err = (*Afacets)[i]->first.row*primalRay2; + if (err.ub()>=0.0) { + primalRay2 -= (1.1*err.ub()/(*Afacets)[i]->first.row.squaredNorm())*(*Afacets)[i]->first.row; + } + } + primalRay2=primalRay2 & admissibleRay; + if (!primalRay2.is_empty()) { + primalRay2.mid(); + for (Index i=0;ifirst.row*primalRay2; + } #ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG - std::cout << "new primalRay : " << primalRay2 << "\n"; - std::cout << "errorPart 2 : " << errorPart << "\n"; + std::cout << "new primalRay : " << primalRay2 << "\n"; + std::cout << "errorPart 2 : " << errorPart << "\n"; #endif - bool ok=true; - for (Index i=0;i0.0) { - ok=false; /* failed */ - break; + bool ok=true; + for (Index i=0;i0.0) { + ok=false; /* failed */ + break; + } + } + if (ok) { + Interval evolobj = + checkempty ? Interval(1) : objvect.dot(primalRay2); + if (evolobj.lb()>0.0) { + status[UNBOUNDED]=true; + primalRay = primalRay2; + return status; + } } - } - if (ok) { - Interval evolobj = objvect.dot(primalRay2); - if (evolobj.lb()>0.0) { - status[UNBOUNDED]=true; - primalRay = primalRay2; - return status; - } - } + } + } } - Interval evolobj = objvect.dot(primalRay); + Interval evolobj = checkempty ? Interval(1) : + objvect.dot(primalRay); if (mxVal.ub()<=0.0 && evolobj.lb()>0.0) - status[UNBOUNDED]=true; + status[UNBOUNDED]=true; else if (evolobj.ub()>0.0) - status[UNBOUNDED_APPROX]=true; + status[UNBOUNDED_APPROX]=true; else status[ERROR_DUAL_CHECK]=true; } else { status[ERROR_DUAL_CHECK]=true; @@ -1108,6 +1261,11 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { /* rebuild the solution */ for (Index i=0;idualRowSolution()[i]; + if (!(*Afacets)[i]->second.eqcst) + assert_release(model->dualRowSolution()[i]>=0.0); + } + for (Index i=0;idualColumnSolution()[i]; } Interval productNeum = this->dotprodrhs(dualSol); productNeum -= errorNeum.head(nbCols).dot(bbox); @@ -1170,6 +1328,9 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { for (Index i=0;idualRowSolution()[i]; } + for (Index i=0;idualColumnSolution()[i]; + } for (Index j=0;j0.0) { - ok=BoolInterval::FALSE; - break; - } + if (error[i].mig()==0.0) continue; + int r = wholeBasis[i]; + dualSol[r] -= error[i]; + int c = r - nbRows; + if (bbox[c].ub()==+oo) { + if (dualSol[r].lb()>0.0) + { ok=BoolInterval::FALSE; break; } + if (dualSol[r].ub()>0.0) + { ok = BoolInterval::UNKNOWN; } + } + if (bbox[c].lb()==-oo) { + if (dualSol[r].ub()<0.0) + { ok= BoolInterval::FALSE; break; } + if (dualSol[r].lb()<0.0) + { ok = BoolInterval::UNKNOWN; } + } + } } - if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) + if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) { for (Index i=0;i0.0)) { ok=BoolInterval::FALSE; break; } } } - if (ok==BoolInterval::TRUE) { - status[BOUNDED]=true; - Valobj = min(Valobj,this->dotprodrhs(dualSol)); - return status; - } - } + } + if (ok==BoolInterval::TRUE) { + status[BOUNDED]=true; + Valobj = min(Valobj,this->dotprodrhs(dualSol)); + return status; + } } if (ok==BoolInterval::UNKNOWN) { status[BOUNDED_APPROX]=true; diff --git a/src/extensions/polytope/clp/codac2_clp.h b/src/extensions/polytope/clp/codac2_clp.h index 986df256f..2217b03ea 100644 --- a/src/extensions/polytope/clp/codac2_clp.h +++ b/src/extensions/polytope/clp/codac2_clp.h @@ -65,7 +65,7 @@ class LPclp { * * \param dim dimension of the space * \param facets the facets - * \param box the bounding box for Neumaier's approx */ + * \param box the bounding box for Neumaier's approx and constraints */ LPclp (Index dim, std::shared_ptr &facets, const IntervalVector &box); @@ -115,6 +115,11 @@ class LPclp { */ void set_bbox(const IntervalVector &box); + /** \brief get bbox (after minimization) + * \return the bbox + */ + IntervalVector get_bbox(); + /** \brief Add a constraint (warning : only for "local" CollectFacets) * \param facet the facet * \return the row number of the constraint, or -1 if the @@ -127,6 +132,10 @@ class LPclp { * \return the row number of the constraint, or the number of the * existing constraint if a constraint with the same base was modified */ Index addConstraint(const Row &vst, double rhs, bool isEq=false); + /** \brief Update or add a facet (for "external" CollectFacets) + * \param id the index of the facet + * \return id if ok, or -1 if failure */ + Index updateConstraint(Index id); /** \brief Set the objvective * \param objvect the new objective * \param rhs its RHS */ @@ -203,7 +212,7 @@ class LPclp { * \arg \c EMPTY Polytope is empty * \arg \c EMPTY_APPROX LP-coin says empty, but the verification * was not conclusive - * \arg \c EMPTY_BBOX Emptiness was proved thanks to the bbox + * \arg \c EMPTY_BBOX Emptiness was proved thanks to Neumaier's approach * \arg \c NOTEMPTY The analysis gave a guaranteed point * \arg \c NOTEMPTY_APPROX LP-coin says non-empty, but the * verification of its result is not clear @@ -306,16 +315,15 @@ class LPclp { std::vector cststat; lp_result_stat status; - std::vector rowBasis; /* limited to the rows */ + std::vector rowBasis; /* rows + columns */ bool built; bool built_emptytest; ClpSimplex *model=nullptr; - Index offset; /* offset between Id of Afacets and row in the model */ Interval Valobj; - IntervalVector primalSol; - IntervalRow dualSol; - IntervalVector primalRay; - IntervalRow dualRay; + IntervalVector primalSol; /* primal solution , size nbCols */ + IntervalRow dualSol; /* dual solution, size nbRows + nbCols */ + IntervalVector primalRay; /* infinite ray, size nbCols */ + IntervalRow dualRay; /* emptiness solution, size nbRows + nbCols */ IntervalVector bbox; double timeout=4.0; @@ -328,6 +336,7 @@ class LPclp { void buildModel(bool emptytest); void setModel(bool emptytest); + void update_model_bbox(); void correctBasisInverse(IntervalMatrix &basisInverse, const IntervalMatrix &basis) const; @@ -360,7 +369,13 @@ inline bool LPclp::isRedundant(Index cst) const { return cststat[cst][REDUNDANT] inline const IntervalRow &LPclp::getBoundedRow() const { return this->dualSol; } inline const IntervalRow &LPclp::getEmptyRow() const { return this->dualRay; } inline const IntervalVector &LPclp::getUnboundedVect() const { return this->primalRay; } -inline void LPclp::set_bbox(const IntervalVector &box) { this->bbox=box; } +inline void LPclp::set_bbox(const IntervalVector &box) { + this->bbox=box; + if (this->built) this->update_model_bbox(); +} +inline IntervalVector LPclp::get_bbox() { + return this->bbox; +} } diff --git a/src/extensions/polytope/codac-polytope.h b/src/extensions/polytope/codac-polytope.h new file mode 100644 index 000000000..fb91465ef --- /dev/null +++ b/src/extensions/polytope/codac-polytope.h @@ -0,0 +1,5 @@ +/* This file is generated by CMake */ + +#pragma once + +#include diff --git a/src/extensions/polytope/codac2_Facet.cpp b/src/extensions/polytope/codac2_Facet.cpp index 1a2eab5d5..2cf6f8120 100644 --- a/src/extensions/polytope/codac2_Facet.cpp +++ b/src/extensions/polytope/codac2_Facet.cpp @@ -88,14 +88,13 @@ void contract_out_Box(const Facet &f, IntervalVector &b) { } Interval bound_linear_form(const Facet &f, - Row &row2, const IntervalVector &b) { + const Row &row2, const IntervalVector &b) { + if (f.first.isNull()) return Interval(); const Row &row = f.first.row; const double &rhs = f.second.rhs; - const Index &bdim = f.first.bdim; const bool &eqcst = f.second.eqcst; - if (bdim==-1) return Interval(); - Index abdim = base.gtDim(); - bool neg = (f.row[abdim]<0.0); + const Index abdim = f.first.gtDim(); + bool neg = (row[abdim]<0.0); if (!eqcst && ((!neg && row2[abdim]<=0.0) || (neg && row2[abdim]>=0))) return Interval(); @@ -109,7 +108,21 @@ Interval bound_linear_form(const Facet &f, } /* end of namespace Facet_ */ std::ostream& operator<<(std::ostream& os, const Facet& f) { - os << f.first.row << (f.second.eqcst ? "=" : "<=" ) << f.second.rhs; + os << f.second.Id << " : " << f.first.row << (f.second.eqcst ? "=" : "<=" ) << f.second.rhs; + return os; +} + +void output_normalised_facet(std::ostream& os, const Facet& f) { + os << f.second.Id << " : " << (f.first.row/std::abs(f.first.row[f.first.gtDim()])) << (f.second.eqcst ? "=" : "<=" ) << (f.second.rhs/std::abs(f.first.row[f.first.gtDim()])); +} + +std::ostream& operator<<(std::ostream& os, const CollectFacets& cf) { + os << " Collectfacets : " << cf._map.size() << " facets" << std::endl; + for (const auto &f : cf._map) { + output_normalised_facet(os,f); + os << std::endl; + } + os << " end Collectfacets" << std::endl; return os; } @@ -373,53 +386,72 @@ void CollectFacets::renumber() { _eqFacets.resize(seq); } -void include_vertices(const std::vector &vertices, - IntervalVector &bbox, bool tight) { +void CollectFacets::encompass_vertices(const + std::vector &vertices, + IntervalVector &bbox, bool tight) { assert_release(this->getDim()!=-1); - if (tight) bbox=IntervalVector(this->getDim(),Interval::empty()); + if (tight) bbox=IntervalVector::constant(this->getDim(),Interval::empty()); if (vertices.size()==0) return; for (mapIterator it = _map.begin(); it!=_map.end(); ++it) { IntervalRow row = it->first.row; Interval a = (tight ? Interval::empty() : Interval(it->second.rhs)); - for (IntervalVector &v : vertices) { + for (const IntervalVector &v : vertices) { a |= row.dot(v); } it->second.rhs=a.ub(); if (it->second.eqcst && !a.is_degenerated()) { this->remove_in_eqFacets(it->second.Id-1); it->second.eqcst=false; - this->insert_facet(-row,-a.lb(),false); + this->insert_facet(-it->first.row,-a.lb(),false); /* note : possible that the new facet will be visited later, but that should not be a real problem */ } } - for (IntervalVector &v : vertices) { + for (const IntervalVector &v : vertices) { bbox |= v; } } -void include_vertices(const std::vector &vertices, - IntervalVector &bbox, bool tight) { +void CollectFacets::encompass_vertices(const std::vector &vertices, + IntervalVector &bbox, bool tight) { assert_release(this->getDim()!=-1); - if (tight) bbox=IntervalVector(this->getDim(),Interval::empty()); + if (tight) bbox=IntervalVector::constant(this->getDim(),Interval::empty()); if (vertices.size()==0) return; for (mapIterator it = _map.begin(); it!=_map.end(); ++it) { IntervalRow row = it->first.row; Interval a = (tight ? Interval::empty() : Interval(it->second.rhs)); - for (Vector &v : vertices) { + for (const Vector &v : vertices) { a |= row.dot(v); } it->second.rhs=a.ub(); if (it->second.eqcst && !a.is_degenerated()) { this->remove_in_eqFacets(it->second.Id-1); it->second.eqcst=false; - this->insert_facet(-row,-a.lb(),false); + this->insert_facet(-it->first.row,-a.lb(),false); /* note : possible that the new facet will be visited later, but that should not be a real problem */ } } - for (Vector &v : vertices) { + for (const Vector &v : vertices) { bbox |= IntervalVector(v); } } +void CollectFacets::encompass_zonotope(const IntervalVector &z, + const IntervalMatrix &A, + const IntervalVector &range, bool tight) { + assert_release(this->getDim()!=-1); + for (mapIterator it = _map.begin(); it!=_map.end(); ++it) { + Interval a = (tight ? Interval::empty() : Interval(it->second.rhs)); + a |= ((z.dot(it->first.row)) + (it->first.row * A).dot(range)); + it->second.rhs=a.ub(); + if (it->second.eqcst && !a.is_degenerated()) { + this->remove_in_eqFacets(it->second.Id-1); + it->second.eqcst=false; + this->insert_facet(-it->first.row,-a.lb(),false); + /* note : possible that the new facet will be visited + later, but that should not be a real problem */ + } + } +} + } diff --git a/src/extensions/polytope/codac2_Facet.h b/src/extensions/polytope/codac2_Facet.h index c2a077e5d..4b63bb1be 100644 --- a/src/extensions/polytope/codac2_Facet.h +++ b/src/extensions/polytope/codac2_Facet.h @@ -21,6 +21,7 @@ #include "codac2_Interval.h" #include "codac2_IntervalVector.h" #include "codac2_IntervalRow.h" +#include "codac2_Zonotope.h" #include "codac2_BoolInterval.h" #include "codac2_operators.h" @@ -368,15 +369,34 @@ class CollectFacets { * facets and equality facets. */ void renumber(); - /** modify the rhs such that the polyhedron contains a list + /** \brief modify the rhs such that the polyhedron encompasses a list * of intervalVector. Also adapt the bbox. * if tight=true, may tighten the bound, and not only increase them. - * may add facets if needed to dissociate equality facets. */ - void include_vertices(const std::vector &vertices, + * may add facets if needed to dissociate equality facets. + * \brief vertices the list of vertices + * \brief bbox the bbox (to be updated) + * \brief tight if false, the rhs are only increased */ + void encompass_vertices(const std::vector &vertices, IntervalVector &bbox, bool tight); - void include_vertices(const std::vector &vertices, + void encompass_vertices(const std::vector &vertices, IntervalVector &bbox, bool tight); + /** \brief modify the rhs such that the polyhedron encompasses an + * "interval zonotope" z+A v. + * if tight=true, may tighten the bound, and not only increase them. + * may add facets if needed to dissociate equality facets. + * \brief z center of the interval zonotope + * \brief A matrix of the interval zonotope + * \brief v interval vector for the interval zonotope + * \brief tight if false, the rhs are only increased */ + void encompass_zonotope(const IntervalVector &z, const IntervalMatrix &A, + const IntervalVector &range, bool tight); + + /** \brief dump the set of facets, for debugging purposes */ + friend std::ostream& operator<<(std::ostream& os, + const CollectFacets& cf); + + private: mutable Index dim; mapType _map; diff --git a/src/extensions/polytope/codac2_Polytope.cpp b/src/extensions/polytope/codac2_Polytope.cpp index 64245a6df..d4e21e8d8 100644 --- a/src/extensions/polytope/codac2_Polytope.cpp +++ b/src/extensions/polytope/codac2_Polytope.cpp @@ -25,6 +25,8 @@ #include "codac2_IntervalVector.h" #include "codac2_IntervalMatrix.h" #include "codac2_inversion.h" +#include "codac2_Parallelepiped.h" +#include "codac2_Zonotope.h" #include "codac2_clp.h" #include "codac2_Polytope.h" #include "codac2_Facet.h" @@ -43,52 +45,23 @@ Polytope::Polytope(Index dim) : Polytope(dim,false) } Polytope::Polytope(Index dim, bool empty) : _dim(dim), _empty(empty), - _box(dim, empty ? Interval::empty() : Interval()), - _facets(std::make_shared(dim)), - _box_updated(true) + _box(IntervalVector::constant(_dim, + empty ? Interval::empty() : Interval())), + _facets(std::make_shared(dim)) { } -#if 0 -void Polytope::build_clpForm_from_box() { - _nbEqcsts=0; - _nbineqCsts=0; - _dim=_box.size(); - std::vector built; - for (Index i=0;i<_box.size();i++) { - Row r = Row::zero(_box.size()); - r[i] = 1.0; - if (_box[i].is_degenerated()) { - _nbEqcsts++; - built.emplace_back(r,_box[i].mid(),true); - } - else { - if (_box[i].ub()-oo) { - _nbineqCsts++; - built.emplace_back(r,_box[i].ub(),false); - } - } - } - _clpForm=std::make_unique(_box.size(),built); -} -#endif - Polytope::Polytope(const IntervalVector &box) : _dim(box.size()), _empty(box.is_empty()), _box(box), - _facets(std::make_shared(dim)), - _box_updated(true) + _facets(std::make_shared(_dim)) { } Polytope::Polytope(const Polytope &P) : _dim(P._dim), _empty(P._empty), _box(P._box), - _facets(P._empty ? std::make_shared(dim) : - std::make_shared(P._facets)), - _box_updated(P._box_updated) + _facets(P._empty ? std::make_shared(_dim) : + std::make_shared(*(P._facets))), + _minimized(P._minimized) { } @@ -106,9 +79,9 @@ Polytope::Polytope(const std::vector &vertices) : /* get the CollectFacets */ _facets=_DDbuildV2F->getFacets(); _box = _facets->extractBox(); - _facets->include_vertices(vertices,_box,true); - _box_updated=true; - _buildV2F_updated=true; /* keep buildV2F ? */ + _facets->encompass_vertices(vertices,_box,true); + _minimized=true; /* considered */ + _DDbuildV2F.release(); /* encompass may have added constraints */ } Polytope::Polytope(const std::vector &vertices, @@ -118,14 +91,14 @@ Polytope::Polytope(const std::vector &vertices, if (_dim==-1) return; _facets=std::make_shared(facetsform); if (vertices.empty()) { - _box = InvervalVector::constant(_dim,Interval::empty()); + _box = IntervalVector::constant(_dim,Interval::empty()); _empty=true; return; } _facets=_DDbuildV2F->getFacets(); _box = _facets->extractBox(); - _facets->include_vertices(vertices,_box,true); - _box_updated=true; + _facets->encompass_vertices(vertices,_box,true); + _minimized=false; } @@ -135,7 +108,70 @@ Polytope::Polytope(const IntervalVector &box, for (const std::pair &cst : facets) { this->add_constraint(cst); } - if (minimize) this->minimize_constraints(); + if (minimize) this->minimize_constraints_clp(); + else _minimized=false; +} + + + +Polytope::Polytope(const Parallelepiped &par) : + Polytope(par.box()) { + /* we can use the vertices, or inverse the shape, + we consider the inversion */ + IntervalMatrix u = inverse_enclosure(par.A); + for (Index i=0;iadd_constraint_band(u.row(i), + u.row(i).dot(par.z)+Interval(-1.0,1.0),0.0); + } + _minimized=true; +} + +Polytope::Polytope(const Zonotope &zon) : + Polytope() { + _dim=zon.z.size(); + _empty= false; + /* we use the vertices (note : can do better) */ + IntervalVector v = IntervalVector::constant(zon.A.cols(),-1.0); + this->_DDbuildV2F = std::make_unique(1,(zon.z+zon.A*v).mid()); + Index idV = 2; + Index a = 0; + while (a_DDbuildV2F->add_vertex(idV++,IntervalVector(zon.z+zon.A*v)); + } + } + /* get the CollectFacets */ + _facets=_DDbuildV2F->getFacets(); + IntervalVector range = IntervalVector::constant(zon.A.cols(), + Interval(-1.0,1.0)); + _box = zon.z+zon.A*range; /* FIXME : use box */ + _facets->encompass_zonotope(IntervalVector(zon.z), + IntervalMatrix(zon.A), range, true); + _DDbuildV2F.release(); /* encompass may have added constraints */ + _minimized=true; +} + +Polytope Polytope::from_ineFile(const char *filename) { + std::shared_ptr fcts + = std::make_shared(read_ineFile(filename)); + if (fcts->getDim()==-1) return Polytope(); + Polytope p(fcts->getDim(),false); + p._facets = fcts; + p._minimized=false; + return p; +} + +Polytope Polytope::from_extFile(const char *filename) { + std::vector vts = read_extFile(filename); + return Polytope(vts); +} + +Polytope::~Polytope() { } @@ -152,23 +188,24 @@ Interval Polytope::fast_bound(const FacetBase &base) const { Row bcopy = base.row; bcopy[gdim]=0.0; res += bcopy.dot(_box); - std::pair - pIt = _facets->equal_range(base); - if (pIt.first!=pIt.end) { /* we found an exact key */ + if (_facets->get_map().empty()) return res; + std::pair + pIt = _facets->get_map().equal_range(base); + if (pIt.first!=pIt.second) { /* we found an exact key */ return Interval(pIt.first->second.rhs); } else { if (pIt.first!=_facets->get_map().begin()) --pIt.first; - Interval a1 = bound_linear_form(*(pIt.first), base.row, _box); + Interval a1 = Facet_::bound_linear_form(*(pIt.first), base.row, _box); if (pIt.second!=_facets->endFacet()) { - Interval a2 = bound_linear_form(*(pIt.second), base.row, _box); + Interval a2 = Facet_::bound_linear_form(*(pIt.second), base.row, _box); if (a2.ub()nbeqfcts();i++) { - CollectFacets::mapIterator it = _facets.get_eqFacet(i); - Interval a = bound_linear_form(*(pIt.first), base.row, _box); + CollectFacets::mapIterator it = _facets->get_eqFacet(i); + Interval a = Facet_::bound_linear_form(*it, base.row, _box); if (a.ub() &vertices=_DDbuildF2V->get_vertices(); for (const DDvertex &vt : vertices) { - IntervalVector vtB = _DDbuildF2V->compute_vertex(vt); + IntervalVector vtB = _DDbuildF2V->compute_vertex(vt.vertex); Interval a = base.row.dot(vtB); if (a.ub() built; - for (Index i=0;i<_dim;i++) { - IntervalRow rowI = rM.row(i); - if (rowI.is_unbounded()) continue; - Row rowIm = rowI.mid(); - Interval rhs = V[i] - (rowI-rowIm)*_box; - if (rhs.is_degenerated()) { - _nbEqcsts++; - built.emplace_back(rowIm,rhs.mid(),true); - } else { - if (rhs.ub()-oo) { - _nbineqCsts++; - built.emplace_back(-rowIm,-rhs.lb(),false); - } - - } - } +void Polytope::build_DDbuildF2V() const { + if (_buildF2V_updated) return; + if (_empty) { + _DDbuildF2V=nullptr; + _buildF2V_updated=true; + return; + } + /* TODO : insert box progressively ? */ + _DDbuildF2V = std::make_unique(_dim,_box,_facets,true); + for (CollectFacets::mapCIterator it = _facets->get_map().begin(); + it!=_facets->get_map().end(); ++it) { + if ((*it).second.eqcst) continue; +// std::cout << " add facet " << *it << "\n"; + _DDbuildF2V->add_facet(it); +// std::cout << " result " << *_DDbuildF2V << "\n"; + if (_DDbuildF2V->is_empty()) { + _empty=true; + _box.set_empty(); + _DDbuildF2V=nullptr; + break; + } + } + _buildF2V_updated=true; } -Polytope::Polytope(const IntervalMatrix &M, const IntervalVector &V) : - Polytope(M,inverse_enclosure(M),V) { +void Polytope::build_clpForm() const { + if (_clpForm_updated) return; + if (_empty) { + _clpForm=nullptr; + _clpForm_updated=true; + return; + } + _clpForm = std::make_unique(_dim,_facets,_box); + _clpForm_updated=true; } -Polytope::~Polytope() { +void Polytope::minimize_constraints_clp(const Interval &tolerance) const { + if (_empty) return; + this->build_clpForm(); + int ret = _clpForm->minimize_polytope(tolerance, true); + if (ret==-1) { + _empty=true; + _box.set_empty(); + _minimized=true; + return; + } + _box &= _clpForm->get_bbox(); + bool changed=false; + for (Index i=0;i<_facets->nbfcts();i++) { + if (_clpForm->isRedundant(i)) { + _facets->removeFacetById(i+1); + changed=true; + } + } + if (changed) { + _facets->renumber(); + _clpForm_updated=_buildF2V_updated=_buildV2F_updated=false; + _clpForm=nullptr; + } + _minimized=true; } -void Polytope::minimize_constraints() { /* TODO */ } +#if 0 const IntervalVector& Polytope::update_box() { if (_dim==-1 || _empty) return this->_box; if (!_clpForm) return this->_box; @@ -313,12 +374,6 @@ bool Polytope::box_is_included(const IntervalVector& x) const { return _box.is_subset(x); } -void Polytope::set_empty() { - _empty=true; - _box.set_empty(); - _nbEqcsts=_nbineqCsts=0; - _clpForm.reset(nullptr); -} void Polytope::clear() { assert_release(_dim>=0); @@ -338,11 +393,11 @@ bool Polytope::add_constraint(const std::pair& facet, this->set_empty(); return true; } Interval act = this->fast_bound(base); - if (act.ub()<=facet.second.tolerance) return false; + if (act.ub()<=facet.second+tolerance) return false; if (base.isCoord()) { Index gdim = base.gtDim(); - Interval val = Interval(facet.second)/row[gdim]; - if (row[gdim]>0.0) { + Interval val = Interval(facet.second)/facet.first[gdim]; + if (facet.first[gdim]>0.0) { _box[gdim] &= Interval(-oo,val.ub()); if (_box[gdim].is_empty()) { this->set_empty(); @@ -355,15 +410,77 @@ bool Polytope::add_constraint(const std::pair& facet, return true; } } - /////////////// update ? + if (_clpForm_updated) { + this->_clpForm->set_bbox(_box); + } + _buildF2V_updated = _buildV2F_updated = false; + /* TODO : update _DDbuildF2V */ + _minimized=false; return true; } auto res= _facets->insert_facet(facet.first, facet.second, false, CollectFacets::MIN_RHS); - //////// update ? - return res.second; + if (res.first==0) return false; + if (_clpForm_updated) { + this->_clpForm->updateConstraint(res.first); + } + if (_buildF2V_updated) { + this->_DDbuildF2V->add_facet((*_facets)[res.first-1]); + } + _buildV2F_updated = false; + _minimized=false; + return true; +} + +bool Polytope::add_constraint(const IntervalRow &cst, double rhs, + double tolerance) { + if (_empty) return false; + Row cstmid = cst.mid(); + IntervalRow rem = cst-cstmid; + Interval d = rhs+rem.dot(_box); + if (d.ub()==+oo) return false; + return this->add_constraint(std::pair(cstmid, d.ub()), tolerance); } +std::pair Polytope::add_constraint_band(const IntervalRow &cst, + const Interval &rhs, double tolerance) { + if (_empty) return { false, false }; + Row cstmid = cst.mid(); + IntervalRow rem = cst-cstmid; + Interval d = rhs+rem.dot(_box); + bool rub, rlb; + if (d.ub()==+oo) rub=false; + else rub = this->add_constraint(std::pair(cstmid, d.ub()), tolerance); + if (d.lb()==-oo) rlb=false; + else rlb = this->add_constraint(std::pair(-cstmid, -d.lb()), tolerance); + return { rlb, rub }; +} + +void Polytope::set_empty() const { + _empty=true; + _box.set_empty(); +} + +void Polytope::set_empty() { + _empty=true; + _box.set_empty(); +} + +std::vector Polytope::compute_vertices() const { + this->build_DDbuildF2V(); + if (_empty) return std::vector(); + std::vector ret; + for (const DDvertex &vtx : _DDbuildF2V->get_vertices()) { + ret.push_back(_DDbuildF2V->compute_vertex(vtx.vertex)); + } + return ret; +} + +std::vector> Polytope::compute_3Dfacets() const { + this->build_DDbuildF2V(); + if (_empty) return std::vector>(); + return build_3Dfacets(*_DDbuildF2V); +} + } -#endif diff --git a/src/extensions/polytope/codac2_Polytope.h b/src/extensions/polytope/codac2_Polytope.h index 01f821d7e..d363ad08d 100644 --- a/src/extensions/polytope/codac2_Polytope.h +++ b/src/extensions/polytope/codac2_Polytope.h @@ -32,8 +32,8 @@ #include "codac2_Facet.h" #include "codac2_Polytope_dd.h" -namespace codac2 { +namespace codac2 { /** * \class Polytope * \brief Represents a bounded convex polytope as a set of constraints @@ -83,20 +83,16 @@ namespace codac2 { const CollectFacets &facetsform); /** - * \brief from a slanted box (M.V with reverse known) - * \param M matrix - * \param rM inverse matrix - * \param V vector + * \brief from a parallelepiped + * \param par the parallelepiped */ - Polytope(const IntervalMatrix& M, const IntervalMatrix &rM, - const IntervalVector &V); + Polytope(const Parallelepiped &par); /** - * \brief from a slanted box (M.V, reverse computed) - * \param M matrix - * \param V vector + * \brief from a zonotope + * \param zon the zonotope */ - Polytope(const IntervalMatrix& M, const IntervalVector &V); + Polytope(const Zonotope &zon); /** * \brief Constructs a polytope from a bounding box and @@ -106,6 +102,15 @@ namespace codac2 { const std::vector> &facets, bool minimize=false); + /** + * \brief build a polytope from an ine File (cddlib format) + * \param filename the file */ + static Polytope from_ineFile(const char *filename); + /** + * \brief build a polytope from an ext File (cddlib format) + * \param filename the file */ + static Polytope from_extFile(const char *filename); + /** * \brief Destructor */ @@ -184,7 +189,7 @@ namespace codac2 { * \brief relationships with a box (fast check) * \param p the box * \return polytope_inclrel checking inclusion and intersection */ - polytope_inclrel relation_Box(const IntervalVector& p) const; + Facet_::polytope_inclrel relation_Box(const IntervalVector& p) const; /** * \brief Checks whether the polytope contains a given point, or @@ -244,12 +249,31 @@ namespace codac2 { /** \brief add a inequality (pair row X <= rhs) * do basic checks, but do not minimize the system - * \param cst the constraint + * \param facet the constraint * \return false if the (basic) checks showed the constraint to * be redundant (or inside tolerance), true if added */ bool add_constraint(const std::pair& facet, double tolerance=0.0); + /** \brief add a inequality with intervalVector (cst X <= rhs) + * using the bounding box + * do basic checks, but do not minimize the system + * \param cst the row constraint + *  \param x the rhs + * \return false if the (basic) checks showed the constraint to + * be redundant (or inside tolerance), true if added */ + bool add_constraint(const IntervalRow &cst, double rhs, + double tolerance=0.0); + + /** \brief two inequalities with intervalVector (cst X in rhs) + * using the bounding box + * do basic checks, but do not minimize the system + * \param cst the row constraint + *  \param x the rhs + * \return pair of booleans (one for each constraints possibly added */ + std::pair add_constraint_band(const IntervalRow &cst, + const Interval &rhs, double tolerance=0.0); + /** \brief inflation by a cube * this <- this + [-rad,rad]^d * \param rad radius of the box @@ -279,41 +303,53 @@ namespace codac2 { Polytope homothety(IntervalVector c, double delta); + /*********** Printing and other ``global access'' *********/ /** * \brief Computes a set of vertices enclosing the polytope * - * \return set of vertices, as Vectors + * \return set of vertices, as IntervalVectors + */ + std::vector compute_vertices() const; + + /** + * \brief Computes the set of 3D facets + * + * \return set of set of vertices, as Vectors */ - std::vector vertices() const; + std::vector> compute_3Dfacets() const; private: Index _dim; /* dimension */ mutable bool _empty; /* is empty */ mutable IntervalVector _box; /* bounding box */ - mutable std::share_ptr _facets; + mutable std::shared_ptr _facets; /* "native" formulation , may be shared by other formulations */ mutable std::unique_ptr _clpForm; /* LPclp formulation, if used */ mutable std::unique_ptr _DDbuildF2V; /* DDbuildF2V formulation, if used */ mutable std::unique_ptr _DDbuildV2F; /* DDbuildV2F formulation, generally not used */ - mutable bool _box_updated; - mutable bool _clpFrom_updated; - mutable bool _buildF2V_updated; - mutable bool _buildV2F_updated; - void build_clpForm_from_box(); - void minimize_constraints(); + mutable bool _minimized=true; + mutable bool _clpForm_updated=false; + mutable bool _buildF2V_updated=false; + mutable bool _buildV2F_updated=false; + void minimize_constraints_clp(const Interval &tolerance=Interval(0.0)) const; + void build_DDbuildF2V() const; + void build_clpForm() const; + void set_empty() const; }; + inline const IntervalVector &Polytope::box() const { return _box; } inline Index Polytope::dim() const { return _dim; } inline Index Polytope::size() const { return _dim; } inline bool Polytope::is_empty() const { return _empty; } -inline bool Polytope::is_flat() const { return _nbEqcsts!=0; } -inline Index Polytope::nbFacets() const { return _nbEqcsts!=0; } - - +inline bool Polytope::is_flat() const { return _empty || + _box.is_degenerated() || (_facets && _facets->nbeqfcts()>0); } +inline Index Polytope::nbFacets() const { + if (_empty) return -1; + if (!_facets) return (2*_dim); + return (2*_dim + _facets->nbfcts()); + } } - -#endif diff --git a/src/extensions/polytope/codac2_Polytope_dd.cpp b/src/extensions/polytope/codac2_Polytope_dd.cpp index bdfb5eb3c..c607a6e05 100644 --- a/src/extensions/polytope/codac2_Polytope_dd.cpp +++ b/src/extensions/polytope/codac2_Polytope_dd.cpp @@ -175,7 +175,33 @@ DDbuildF2V::DDbuildF2V(Index dim, const IntervalVector &box, line[i+1]=1.0; lines.push_back(line); } -// if (include_box) this->add_constraint_box(bbox); + if (include_box) this->add_constraint_box(bbox); +} + +void DDbuildF2V::add_bound_var(Index c, bool mx, double rhs) { + if (empty) return; + if (rhs==+oo) return; + IntervalRow facet = IntervalRow::Zero(this->fdim+1); + facet[0] = -rhs; + if (flat) { + facet += this->M_EQ.row(c); + } else { + facet[c+1] = 1.0; + } + if (!mx) facet=-facet; + Index idFacet = -c-1-(mx ? 0 : this->dim); + this->add_facet(idFacet,facet); +} + +void DDbuildF2V::add_constraint_box(const IntervalVector &box) { + for (Index i=0;iadd_bound_var(i,true,box[i].ub()); + } + for (Index i=0;iadd_bound_var(i,false,box[i].lb()); + } } std::forward_list::iterator DDbuildF2V::addDDvertex(const IntervalVector &v) { @@ -281,24 +307,8 @@ bool DDbuildF2V::addDDlink(const std::forward_list::iterator& V1, // return (V1->addLnk(V2,true) && V2->addLnk(V1,false)); } -int DDbuildF2V::add_facet(Index id) { - return this->add_facet((*facetsptr)[id]); -} - -int DDbuildF2V::add_facet(CollectFacets::mapCIterator itFacet) { - Index idFacet = itFacet->second.Id; - if (empty) return 0; - if (itFacet->second.eqcst) - throw std::domain_error("add_facet with an equality facet."); - /* the first step is to take the equalities constraints into account */ - IntervalRow facet = IntervalRow::Zero(this->fdim+1); - facet[0] = -itFacet->second.rhs; - if (flat) { - facet += itFacet->first.row * this->M_EQ; - } else { - facet.tail(this->fdim) = itFacet->first.row; - } - /* then consider lines */ +int DDbuildF2V::add_facet(Index idFacet, const IntervalRow &facet) { + /* consider lines */ Index bestline=-1; double maxmig=0.0; for (Index i=0;i<(Index)lines.size();i++) { @@ -322,6 +332,7 @@ int DDbuildF2V::add_facet(CollectFacets::mapCIterator itFacet) { for (Index i=0;i<(Index)lines.size()-1;i++) { Interval u = facet.dot(lines[i]); lines[i] = ubest*lines[i] - u*bstline; + reduce_vector_without_rounding(lines[i]); } /* traitement des sommets */ for (DDvertex &v : vertices) { @@ -361,10 +372,13 @@ int DDbuildF2V::add_facet(CollectFacets::mapCIterator itFacet) { /* end of line treatments , the following is when no line is available */ int nbGT=0; + int nbGE=0; + int nbTot=0; bool notempty=false; for (DDvertex &vt : vertices) { vt.lambda = facet.dot(vt.vertex); notempty |= (vt.lambda.lb()<=0.0); + nbTot++; if (vt.lambda.ub()<0.0) { vt.status = DDvertex::VERTEXLT; continue; @@ -373,6 +387,7 @@ int DDbuildF2V::add_facet(CollectFacets::mapCIterator itFacet) { vt.status = DDvertex::VERTEXON; continue; } + nbGE++; if (vt.lambda.lb()tolerance) { vt.status = DDvertex::VERTEXGE; continue; @@ -467,7 +482,7 @@ int DDbuildF2V::add_facet(CollectFacets::mapCIterator itFacet) { std::forward_list::iterator newVx = this->addDDvertex(newV); #ifdef DEBUG_CODAC2_DD - std::cout << " creation new V " << newV << "id : " << newVx->Id << "\n"; + std::cout << " creation new V " << newV << "id : " << newVx->Id << " coef " << (-destlink.lambda.lb()/actVertex->lambda.lb()) << "\n"; #endif newVx->lambda=0.0; destlink.changeLnk(actVertex,newVx); @@ -495,7 +510,7 @@ int DDbuildF2V::add_facet(CollectFacets::mapCIterator itFacet) { adjacentVertices[j].first, adjacentVertices[i].second, adjacentVertices[j].second,refFacet); - if (actuel.elems.size()::iterator> &pm : maxset) { #ifdef DEBUG_CODAC2_DD - if (pm.elems.size()Id << " " << pm.b->Id << " " << pm.elems.size() << std::endl; } std::cout << pm.a->Id << " " << pm.b->Id << "\n"; @@ -546,7 +561,28 @@ int DDbuildF2V::add_facet(CollectFacets::mapCIterator itFacet) { { return v.status==DDvertex::VERTEXREM; }); return 1; } - + + +int DDbuildF2V::add_facet(Index id) { + return this->add_facet((*facetsptr)[id]); +} + + +int DDbuildF2V::add_facet(CollectFacets::mapCIterator itFacet) { + Index idFacet = itFacet->second.Id; + if (empty) return 0; + if (itFacet->second.eqcst) + throw std::domain_error("add_facet with an equality facet."); + /* the first step is to take the equalities constraints into account */ + IntervalRow facet = IntervalRow::Zero(this->fdim+1); + facet[0] = -itFacet->second.rhs; + if (flat) { + facet += itFacet->first.row * this->M_EQ; + } else { + facet.tail(this->fdim) = itFacet->first.row; + } + return this->add_facet(idFacet,facet); +} /* TODO : make a specific computation */ #if 0 @@ -790,8 +826,8 @@ int DDbuildV2F::add_vertex(Index idVertex, const IntervalVector& vertex) { CollectFacets::mapIterator nIt = facetsptr->dissociate_eqFacet(eqViolated, (delta.lb()>0.0 ? +oo : -oo)); - std::cout << "dissociated : " << (delta.lb()>0.0 ? +oo : -oo) << - " : " << (nIt == facetsptr->endFacet()) << "\n"; +// std::cout << "dissociated : " << (delta.lb()>0.0 ? +oo : -oo) << +// " : " << (nIt == facetsptr->endFacet()) << "\n"; eqViolatedIt=nIt; /* in the following, the case nIt==facets.end() is not treated, as it should not happen */ std::vector vtx = idVertices; @@ -865,7 +901,7 @@ int DDbuildV2F::add_vertex(Index idVertex, const IntervalVector& vertex) { continue; /* do nothing for out */ } #ifdef DEBUG_CODAC2_DD - std::cout << " addFacet " << d.Id << " " << d2.Id << " " << d.status << " " << d2.status << std::endl; + std::cout << " addFacet " << d.facetIt->second.Id << " " << d2.facetIt->second.Id << " " << d.status << " " << d2.status << std::endl; std::cout << " (" << d.lambda << " " << d2.lambda << ") " << std::endl; #endif [[maybe_unused]] std::forward_list::iterator @@ -874,7 +910,7 @@ int DDbuildV2F::add_vertex(Index idVertex, const IntervalVector& vertex) { if (newIt==facets.end()) std::cout << " ... FAILED! " << std::endl; else - std::cout << " ... done " << newIt->Id << std::endl; + std::cout << " ... done " << newIt->facetIt->second.Id << std::endl; #endif cnt++; } diff --git a/src/extensions/polytope/codac2_Polytope_dd.h b/src/extensions/polytope/codac2_Polytope_dd.h index edcf31454..8ed970a40 100644 --- a/src/extensions/polytope/codac2_Polytope_dd.h +++ b/src/extensions/polytope/codac2_Polytope_dd.h @@ -36,6 +36,41 @@ namespace codac2 { #if 0 struct DDlink; /* link between two vertices */ #endif +inline double reduce_vector_without_rounding(IntervalVector &vec) { + int fx; + if (frexp(vec[0].mag(),&fx)==0.0) fx=INT_MIN; + for (Index i=1;ifx) fx=fx2; + } + assert_release (fx!=INT_MIN); + if (std::abs(fx)<10) return 1.0; + double mult=exp2(-fx); + for (Index i=0;ifx) fx=fx2; + } + assert_release (fx!=INT_MIN); + if (std::abs(fx)<10) return 1.0; + double mult=exp2(-fx); + for (Index i=0;i::iterator &a, bool check=false) { if (check) { @@ -112,20 +151,6 @@ struct DDvertex { return true; } - inline void reduce_vector() { - int fx; - frexp(vertex[0].mid(),&fx); - for (Index i=1;ifx) fx=fx2; - } - if (std::abs(fx)<10) return; - double mult=exp2(-fx); - for (Index i=0;i::iterator& V1, const std::forward_list::iterator& V2); @@ -342,8 +371,11 @@ class DDbuildV2F { int add_vertex(Index idVertex, const Vector& vertex); int add_vertex(Index idVertex, const IntervalVector& vertex); + std::shared_ptr getFacets(); + friend std::ostream& operator<<(std::ostream& os, const DDbuildV2F& build); + private: /* can NOT return facets.end() */ std::forward_list::iterator addDDfacet( @@ -368,16 +400,10 @@ class DDbuildV2F { bool removeLnks); inline std::pair reduce_facet(const Row &row, double rhs) { - int fx; - frexp(row[0],&fx); - for (Index i=1;ifx) fx=fx2; - } - if (std::abs(fx)<10) return std::pair(row,rhs); - double mult=exp2(-fx); - return std::pair(row*mult,rhs*mult); + std::pair ret(row,rhs); + double mult = reduce_row_without_rounding(ret.first); + ret.second *= mult; + return ret; } std::shared_ptr facetsptr; @@ -385,8 +411,11 @@ class DDbuildV2F { std::vector idVertices; /* list of vertices entered, useful as long as there are eqfacets */ Index nbIn=0; - double tolerance =1e-9; + double tolerance =1e-8; + }; +inline std::shared_ptr DDbuildV2F::getFacets() { return facetsptr; } + } diff --git a/src/extensions/polytope/codac2_Polytope_util.cpp b/src/extensions/polytope/codac2_Polytope_util.cpp index c42c9bef2..7cc51320b 100644 --- a/src/extensions/polytope/codac2_Polytope_util.cpp +++ b/src/extensions/polytope/codac2_Polytope_util.cpp @@ -117,7 +117,7 @@ Index fill_3Dfacet(const DDbuildF2V &build, std::vector::const_iterator> used; used.push_back(it); - std::cout << "add vert : " << it->Id << "\n"; +// std::cout << "add vert : " << it->Id << "\n"; Vector vt = build.compute_vertex(it->vertex).mid(); tofill.push_back(vt); while (it != vertices.end()) { @@ -140,7 +140,7 @@ Index fill_3Dfacet(const DDbuildF2V &build, } tofill.push_back(vt); used.push_back(it); - std::cout << "add vert : " << it->Id << "\n"; +// std::cout << "add vert : " << it->Id << "\n"; } return (Index)tofill.size(); } @@ -166,7 +166,7 @@ std::vector> build_3Dfacets(const DDbuildF2V &build, double if (build.get_reffacets()[i]==0) continue; std::vector nfac; Index nbV = fill_3Dfacet(build, nfac, i, bound); - std::cout << "facet build, " << nbV << " vertices.\n"; +// std::cout << "facet build, " << nbV << " vertices.\n"; if (nbV<3) continue; result.push_back(nfac); } diff --git a/tests/extensions/polytope/codac2_tests_clp.cpp b/tests/extensions/polytope/codac2_tests_clp.cpp new file mode 100644 index 000000000..602b5c9c0 --- /dev/null +++ b/tests/extensions/polytope/codac2_tests_clp.cpp @@ -0,0 +1,259 @@ +/** + * Codac tests + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2025 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include +#include +#include +#include +#include + +using namespace std; +using namespace codac2; + +TEST_CASE("Polytope-CLP-nobox") +{ + /* non empty, bounded polytope : deformed octahedron not with // constraints */ + { + Matrix x({ + { 1.5, 1.5, 1.4 }, + { 0.5, 1.4, -0.5 }, + { 1.4, -0.5, 0.5 }, + { -0.5, 0.5, 1.5 }, + { 0.5, -0.5, -1.5 }, + { -0.5, -1.5, 0.5 }, + { -1.5, 0.5, -0.5 }, + { -1.5, -1.5, -1.5 } + }); + Vector r({3.0,3.0,-1.0,3.0,-1.0,-1.0,3.0,-1.0}); + + LPclp clp(x,r); + LPclp::lp_result_stat res = clp.solve(true); + CHECK(res[LPclp::NOTEMPTY]); + IntervalVector diff = x*clp.getFeasiblePoint()-r; + CHECK(diff.ub().maxCoeff()<0.0); + CHECK(res[LPclp::BOUNDED]); + IntervalRow dual = clp.getBoundedRow().head(x.rows()); + CHECK(dual.lb().maxCoeff()>=0.0); + CHECK((dual*x).mig().maxCoeff()<=1e-10); + + Row obj({1.0,1.0,1.0}); + clp.setObjective(obj); + res = clp.solve(false); + CHECK(res[LPclp::NOTEMPTY]); + diff = x*clp.getFeasiblePoint()-r; + CHECK(diff.ub().maxCoeff()<1e-8); /* can be >0 near the basis */ + CHECK(res[LPclp::BOUNDED]); + dual = clp.getBoundedRow().head(x.rows()); + CHECK(dual.lb().maxCoeff()>=0.0); + CHECK((dual*x-obj).mig().maxCoeff()<=1e-10); + } + + { + /* non empty, bounded polytope : deformed octahedron with // constraints */ + Matrix x({ + { 1.5, 1.5, 1.5 }, + { 0.5, 1.5, -0.5 }, + { 1.5, -0.5, 0.5 }, + { -0.5, 0.5, 1.5 }, + { 0.5, -0.5, -1.5 }, + { -0.5, -1.5, 0.5 }, + { -1.5, 0.5, -0.5 }, + { -1.5, -1.5, -1.5 } + }); + Vector r({3.0,3.0,-1.0,3.0,-1.0,-1.0,3.0,-1.0}); + + LPclp clp(x,r); + LPclp::lp_result_stat res = clp.solve(true); + CHECK(res[LPclp::NOTEMPTY]); + IntervalVector diff = x*clp.getFeasiblePoint()-r; + CHECK(diff.ub().maxCoeff()<0.0); + CHECK(res[LPclp::BOUNDED_APPROX]); + IntervalRow dual = clp.getBoundedRow().head(x.rows()); + CHECK(dual.lb().maxCoeff()>=0.0); + CHECK((dual*x).mig().maxCoeff()<=1e-10); + + Row obj({1.0,1.0,1.0}); + clp.setObjective(obj); + res = clp.solve(false); + CHECK(res[LPclp::NOTEMPTY]); + diff = x*clp.getFeasiblePoint()-r; + CHECK(diff.ub().maxCoeff()<0.0); + CHECK(res[LPclp::BOUNDED]); + dual = clp.getBoundedRow().head(x.rows()); + CHECK(dual.lb().maxCoeff()>=0.0); + CHECK((dual*x-obj).mig().maxCoeff()<=1e-10); + } + + { + /* empty polytope : deformed octahedron with non// constraints */ + Matrix x({ + { 1.5, 1.5, 1.4 }, + { 0.5, 1.4, -0.5 }, + { 1.4, -0.5, 0.5 }, + { -0.5, 0.5, 1.5 }, + { 0.5, -0.5, -1.5 }, + { -0.5, -1.5, 0.5 }, + { -1.5, 0.5, -0.5 }, + { -1.5, -1.5, -1.5 } + }); + Vector r({3.0,3.0,-1.0,0.9,-1.0,-1.0,-2.0,-1.0}); + + LPclp clp(x,r); + LPclp::lp_result_stat res = clp.solve(true); + CHECK(res[LPclp::EMPTY]); + IntervalRow dual = clp.getEmptyRow().head(x.rows()); + CHECK(dual.lb().minCoeff()>=0.0); + CHECK((dual*x).mig().maxCoeff()<=1e-10); + CHECK((dual.dot(r)).ub()<0.0); + } + + { + /* empty polytope : deformed octahedron with // constraints */ + Matrix x({ + { 1.5, 1.5, 1.5 }, + { 0.5, 1.5, -0.5 }, + { 1.5, -0.5, 0.5 }, + { -0.5, 0.5, 1.5 }, + { 0.5, -0.5, -1.5 }, + { -0.5, -1.5, 0.5 }, + { -1.5, 0.5, -0.5 }, + { -1.5, -1.5, -1.5 } + }); + Vector r({3.0,3.0,-1.0,0.9,-1.0,-1.0,-2.0,-1.0}); + + LPclp clp(x,r); + LPclp::lp_result_stat res = clp.solve(true); + CHECK(res[LPclp::EMPTY_APPROX]); /* TO CHANGE */ + IntervalRow dual = clp.getEmptyRow().head(x.rows()); +// CHECK(dual.lb().minCoeff()>=0.0); + CHECK((dual*x).mig().maxCoeff()<=1e-10); + CHECK((dual.dot(r)).ub()<0.0); + } + + { + /* unbounded polytope : deformed octahedron with non // constraints */ + Matrix x({ + { 1.5, 1.5, 1.6 }, + { 0.5, 1.6, -0.5 }, + { 1.6, -0.5, 0.5 }, + { -0.5, 0.5, 1.5 }, + { 0.5, -0.5, -1.6 }, + { -1.6, 0.5, -0.6 }, + }); + Vector r({3.0,3.0,-1.0,3.0,-1.0,3.0}); + + LPclp clp(x,r); + LPclp::lp_result_stat res = clp.solve(true); + CHECK(res[LPclp::NOTEMPTY]); + IntervalVector diff = x*clp.getFeasiblePoint()-r; + CHECK(diff.ub().maxCoeff()<0.0); + CHECK(res[LPclp::UNBOUNDED]); + IntervalVector vect = clp.getUnboundedVect(); + CHECK((x*vect).ub().maxCoeff()<=0.0); + + Row obj({0.0,-1.0,-1.0}); + clp.setObjective(obj); + res = clp.solve(false); + CHECK(res[LPclp::NOTEMPTY]); + diff = x*clp.getFeasiblePoint()-r; + CHECK(diff.ub().maxCoeff()<1e-10); + CHECK(res[LPclp::UNBOUNDED]); + diff = x*clp.getUnboundedVect(); + CHECK(diff.ub().maxCoeff()<=1e-10); + } + + { + /* unbounded polytope : deformed octahedron with // constraints */ + Matrix x({ + { 1.5, 1.5, 1.5 }, + { 0.5, 1.5, -0.5 }, + { 1.5, -0.5, 0.5 }, + { -0.5, 0.5, 1.5 }, + { 0.5, -0.5, -1.5 }, + { -1.5, 0.5, -0.5 }, + }); + Vector r({3.0,3.0,-1.0,3.0,-1.0,3.0}); + + LPclp clp(x,r); + LPclp::lp_result_stat res = clp.solve(true); + CHECK(res[LPclp::NOTEMPTY]); + IntervalVector diff = x*clp.getFeasiblePoint()-r; + CHECK(diff.ub().maxCoeff()<0.0); + CHECK(res[LPclp::BOUNDED_APPROX]); + /* note : polytope is unbounded, but + the emptiness solving did not gave + an infinite direction because of // of some + constraints */ + IntervalRow dual = clp.getBoundedRow().head(x.rows()); + CHECK(dual.lb().maxCoeff()>=0.0); + CHECK((dual*x).mig().maxCoeff()<=1e-10); + + Row obj({-1.0,-1.0,-1.0}); + clp.setObjective(obj); + res = clp.solve(false); + CHECK(res[LPclp::NOTEMPTY]); + diff = x*clp.getFeasiblePoint()-r; + CHECK(diff.ub().maxCoeff()<1e-10); + CHECK(res[LPclp::UNBOUNDED_APPROX]); + /* can't prove that it is unbounded, once again due to // + of some constraints */ + diff = x*clp.getUnboundedVect(); + CHECK(diff.ub().maxCoeff()<1e-10); + } +} + +TEST_CASE("Polytope-CLP-box") +{ + /* non empty, bounded polytope */ + { + Matrix x({ + { 1.5, 1.5, 1.5 }, + { 0.5, 1.5, -0.5 }, + { 1.5, -0.5, 0.5 }, + { -0.5, 0.5, 1.5 } + }); + Vector r({3.0,3.0,-1.0,3.0}); + IntervalVector b { { -5,5 } , {-5,5}, {-5,5 } }; + + LPclp clp(x,r); + clp.set_bbox(b); + LPclp::lp_result_stat res = clp.solve(true); + CHECK(res[LPclp::NOTEMPTY]); + IntervalVector diff = x*clp.getFeasiblePoint()-r; + CHECK(diff.ub().maxCoeff()<0.0); + CHECK(res[LPclp::BOUNDED]); + + Row obj({1.0,-1.0,1.0}); + clp.setObjective(obj); + res = clp.solve(false); + CHECK(res[LPclp::NOTEMPTY]); + diff = x*clp.getFeasiblePoint()-r; + CHECK(diff.ub().maxCoeff()<1e-8); /* can be >0 near the basis */ + CHECK(res[LPclp::BOUNDED]); + } + + /* empty polytope */ + { + Matrix x({ + { 1.5, 1.5, 1.4 }, + { 0.5, 1.4, -0.5 }, + { 1.4, -0.5, 0.5 }, + { -0.5, 0.5, 1.5 } + }); + Vector r({3.0,3.0,-1.0,3.0}); + IntervalVector b { { 3,6 } , {0,6}, {3,6 } }; + + LPclp clp(x,r); + clp.set_bbox(b); + LPclp::lp_result_stat res = clp.solve(true); + CHECK(res[LPclp::EMPTY]); + } + + +} From 7d3f54fa553c22f14cb6c2b30a41d96348140426 Mon Sep 17 00:00:00 2001 From: damien-masse Date: Tue, 18 Nov 2025 13:39:13 +0100 Subject: [PATCH 13/26] Intersection of polytopes --- .../polytope_examples/main-3Dgraphics.cpp | 12 + examples/polytope_examples/main-F2V.cpp | 2 +- examples/polytope_examples/main-V2F.cpp | 2 + src/extensions/polytope/clp/codac2_clp.cpp | 38 +- src/extensions/polytope/clp/codac2_clp.h | 23 +- src/extensions/polytope/codac2_Facet.cpp | 85 +++- src/extensions/polytope/codac2_Facet.h | 52 ++- src/extensions/polytope/codac2_Polytope.cpp | 429 ++++++++++++++---- src/extensions/polytope/codac2_Polytope.h | 147 +++++- .../polytope/codac2_Polytope_dd.cpp | 277 +++++------ src/extensions/polytope/codac2_Polytope_dd.h | 54 +-- src/extensions/polytope/codac2_PosetMax.h | 145 ++++++ 12 files changed, 885 insertions(+), 381 deletions(-) create mode 100644 src/extensions/polytope/codac2_PosetMax.h diff --git a/examples/polytope_examples/main-3Dgraphics.cpp b/examples/polytope_examples/main-3Dgraphics.cpp index 6ca776196..f31f49586 100644 --- a/examples/polytope_examples/main-3Dgraphics.cpp +++ b/examples/polytope_examples/main-3Dgraphics.cpp @@ -25,24 +25,36 @@ int main(int argc, char *argv[]) /* box */ IntervalVector box1 { { 1, 3 }, { 1, 2 }, { 2, 2.5 } }; Polytope pbox1(box1); + std::cout << "Box : " << pbox1 << "\n"; draw_polytope(fig,pbox1,StyleProperties(Color::dark_green(),"box")); /* flat box */ IntervalVector box2 { { 1, 3 }, { -1, -1 }, { 2, 2.5 } }; Polytope pbox2(box2); + std::cout << "Flat box : " << pbox2 << "\n"; draw_polytope(fig,pbox2,StyleProperties(Color::dark_red(),"flat box")); /* parallelepiped */ Polytope paral(Parallelepiped({-1,1,1}, {{ 0.5,0.6,0.0 }, { -0.3,0.0,0.4 }, { 0.0, -0.6, 0.8 }})); + std::cout << "Parallelotop : " << paral << "\n"; draw_polytope(fig,paral,StyleProperties(Color::dark_blue(),"parallelepiped")); /* zonotope */ Polytope zono(Zonotope({-4,1,1}, {{ 0.5,0.6,0.0,0.4,0.0,1.1 }, { -0.3,0.0,0.4,-0.5,0.3,-0.3 }, { -0.2,0.0,0.3,-0.4,-0.2,-0.2 }})); + std::cout << "Zonotope : " << zono << "\n"; draw_polytope(fig,zono,StyleProperties(Color::dark_yellow(),"zonotope")); + /* intersection */ + zono.meet_with_polytope(paral); + + std::cout << "Intersection : " << zono << "\n"; + draw_polytope(fig,zono,StyleProperties(Color::dark_gray(),"intersection")); + zono.minimize_constraints(); + std::cout << "Intersection (minimized) : " << zono << "\n"; + return 0; } diff --git a/examples/polytope_examples/main-F2V.cpp b/examples/polytope_examples/main-F2V.cpp index a389cc213..51da18032 100644 --- a/examples/polytope_examples/main-F2V.cpp +++ b/examples/polytope_examples/main-F2V.cpp @@ -17,8 +17,8 @@ int main(int argc, char *argv[]) const char *namefile = "cross12.ine"; if (argc>1) namefile= argv[1]; Polytope pol = Polytope::from_ineFile(namefile); + std::cout << pol << std::endl; - std::cout << " Polytope lu." << std::endl; std::vector lvect = pol.compute_vertices(); std::cout << lvect.size() << " vertices" << std::endl; diff --git a/examples/polytope_examples/main-V2F.cpp b/examples/polytope_examples/main-V2F.cpp index edd3497bd..bff2a6161 100644 --- a/examples/polytope_examples/main-V2F.cpp +++ b/examples/polytope_examples/main-V2F.cpp @@ -18,6 +18,8 @@ int main(int argc, char *argv[]) const char *namefile = "irbox20-4.ext"; if (argc>1) namefile= argv[1]; Polytope pol = Polytope::from_extFile(namefile); + + std::cout << pol << std::endl; std::vector lst = pol.compute_vertices(); std::cout << "nb vertices : " << lst.size() << std::endl; diff --git a/src/extensions/polytope/clp/codac2_clp.cpp b/src/extensions/polytope/clp/codac2_clp.cpp index 30d5aa1f0..4790518d0 100644 --- a/src/extensions/polytope/clp/codac2_clp.cpp +++ b/src/extensions/polytope/clp/codac2_clp.cpp @@ -337,11 +337,20 @@ LPclp::lp_result_stat LPclp::solve(bool checkempty, int option) { } int LPclp::minimize_eqpolytope() { - /* TODO : manage equalities for bbox */ std::vector triMat; std::vector triRhs; Index nbCsts=0; std::vector mainCol; + for (Index i=0; iAfacets)[i]->second.eqcst) continue; if (cststat[i][REMOVED] || cststat[i][INACTIVE] || cststat[i][REDUNDANT]) @@ -382,19 +391,19 @@ int LPclp::minimize_eqpolytope() { return nbCsts; } -int LPclp::minimize_polytope(const Interval &tolerance, bool checkempty) { - int nb = this->minimize_eqpolytope(); - if (nb==-1) return -1; - if (checkempty) { - lp_result_stat stat=this->solve(true); - if (stat[EMPTY]) return -1; - } +int LPclp::check_emptiness() { + lp_result_stat stat=this->solve(true); + if (stat[EMPTY]) return -1; + return 0; +} + +int LPclp::minimize_box() { /* minimize box */ for (Index i=0;isetObjective(a); - lp_result_stat stat=this->solve(false,(i!=0 || checkempty ? 4 : 0)); + lp_result_stat stat=this->solve(false,(i!=0 ? 4 : 0)); if (stat[EMPTY]) return -1; if (stat[BOUNDED]) { bbox[i] = min(bbox[i],this->Valobj.ub()); @@ -411,6 +420,17 @@ int LPclp::minimize_polytope(const Interval &tolerance, bool checkempty) { } } this->update_model_bbox(); + return 0; +} + +int LPclp::minimize_polytope(const Interval &tolerance, bool checkempty, + bool checkbox) { + int nb = this->minimize_eqpolytope(); + if (nb==-1) return -1; + if (checkempty) + if (check_emptiness()==-1) return -1; + if (checkbox) + if (this->minimize_box()==-1) return -1; for (Index i=nbRows-1;i>=0;i--) { const auto &fct = (*this->Afacets)[i]; if (cststat[i][REMOVED] || diff --git a/src/extensions/polytope/clp/codac2_clp.h b/src/extensions/polytope/clp/codac2_clp.h index 2217b03ea..5012ddf22 100644 --- a/src/extensions/polytope/clp/codac2_clp.h +++ b/src/extensions/polytope/clp/codac2_clp.h @@ -267,13 +267,22 @@ class LPclp { * b) if not, and all coefficients are exactly 0, returns empty * c) if bounding box, use it to try to prove emptiness, if not * possible keep the row (and hope for emptiness later?) + * \return -1 if the polytope is empty, else the nb of irredudant + * equality constraints (box not included) */ int minimize_eqpolytope(); + /** \brief check emptiness + * \return -1 if the polytope is empty, else 0 + */ + int check_emptiness(); + + /** \brief minimize the box + * \return -1 if the polytope is empty, else 0 + */ + int minimize_box(); + /** \brief detect redundant constraints, with a tolerance. - * return the number of remaining constraints, -1 if the polytope - * is empty. - * if checkempty=true, we check emptiness first will all constraints. * tolerance works as follows : * max is the interval computed by maximizing without the constraint * rhs the rhs of the constraint @@ -281,8 +290,14 @@ class LPclp { * max_l <= rhs <= max_u => redundant if rhs + tol_up >= max_u * rhs <= max_l <= max_u => redundant if * rhs+tol_up >= max_u AND rhs+tol_down >= max_l + * \param tolerance tolerance for redundant constraints + * \param checkempty check if the polytope is empty + * \param checkbox minimize the box + * \return -1 if the polytope is empty, else the nb of irredundant + * contraints (box not included) */ - int minimize_polytope(const Interval &tolerance, bool checkempty=true); + int minimize_polytope(const Interval &tolerance, bool checkempty=true, + bool checkbox=true); /** \brief Returns the value of the objective after * solving. The value is guaranteed as an overapproximation. diff --git a/src/extensions/polytope/codac2_Facet.cpp b/src/extensions/polytope/codac2_Facet.cpp index 2cf6f8120..bb5cc91c6 100644 --- a/src/extensions/polytope/codac2_Facet.cpp +++ b/src/extensions/polytope/codac2_Facet.cpp @@ -141,25 +141,55 @@ CollectFacets::CollectFacets(const Matrix &mat, const Vector &rhsvect, } std::pair CollectFacets::result_insertion - (Index id, std::pair res, + (std::pair res, double rhs, bool eqcst, ACTION_DUPLICATE act) { if (res.second) { + Index id = _allFacets.size()+1; + res.first->second.Id=id; _allFacets.push_back(res.first); if (eqcst) _eqFacets.push_back(id-1); return { id, true }; } else { + if (act==KEEP_RHS) return { 0, false }; double old_rhs=res.first->second.rhs; - if (act==CHANGE_RHS || - (act==MAX_RHS && old_rhsrhs)) { - bool old_eqcst = res.first->second.eqcst; - res.first->second.rhs=rhs; - res.first->second.eqcst=eqcst; - if (eqcst && !old_eqcst) - _eqFacets.push_back(res.first->second.Id-1); - else if (!eqcst && old_eqcst) - remove_in_eqFacets(res.first->second.Id-1); - return { res.first->second.Id, false }; + bool old_eqcst = res.first->second.eqcst; + double new_rhs; + bool new_eqcst; + if (act==CHANGE_RHS) { new_eqcst = eqcst; new_rhs = rhs; } + else if (act==MAX_RHS) { + if (old_eqcst && eqcst) { + if (old_rhs==rhs) return { 0, false }; + /* complex case */ + res.first->second.rhs=std::max(old_rhs,rhs); + res.first->second.eqcst=false; + remove_in_eqFacets(res.first->second.Id-1); + std::pair rinsert = + this->insert_facet(-res.first->first.row, + -std::min(old_rhs,rhs),false,MAX_RHS); + /* can't loop, as we insert an inequality */ + return rinsert; + } else { + if (old_rhs>=rhs && !old_eqcst) return { 0, false }; + new_eqcst=false; + new_rhs=std::max(old_rhs,rhs); + } + } else { /* MIN_RHS */ + if (old_rhs<=rhs) { + if (!eqcst) return {0, false}; + if (old_rhssecond.rhs=new_rhs; + res.first->second.eqcst=new_eqcst; + if (new_eqcst && !old_eqcst) + _eqFacets.push_back(res.first->second.Id-1); + else if (!new_eqcst && old_eqcst) + remove_in_eqFacets(res.first->second.Id-1); + return { res.first->second.Id, false }; } return { 0, false }; } @@ -176,22 +206,18 @@ void CollectFacets::remove_in_eqFacets(Index id) { std::pair CollectFacets::insert_facet(const Row &row, double rhs, bool eqcst, ACTION_DUPLICATE act) { - Index id = _allFacets.size()+1; std::pair res = - _map.emplace(std::move(Facet_::make(row,rhs,eqcst,id))); - return result_insertion(id,res,rhs,eqcst,act); + _map.emplace(std::move(Facet_::make(row,rhs,eqcst,0))); + return result_insertion(res,rhs,eqcst,act); } std::pair CollectFacets::insert_facet(Row &&row, double rhs, bool eqcst, ACTION_DUPLICATE act) { - Index id = _allFacets.size()+1; std::pair res = - _map.emplace(std::move(Facet_::make(row,rhs,eqcst,id))); - return result_insertion(id, res,rhs,eqcst,act); + _map.emplace(std::move(Facet_::make(row,rhs,eqcst,0))); + return result_insertion(res,rhs,eqcst,act); } std::pair CollectFacets::insert_facet(const Facet &fct, ACTION_DUPLICATE act) { - Index id = _allFacets.size()+1; - std::pair res = - _map.emplace(std::pair(fct.first, FacetRhs(fct.second,id))); - return result_insertion(id, res,fct.second.rhs,fct.second.eqcst,act); + std::pair res = _map.insert(fct); + return result_insertion(res,fct.second.rhs,fct.second.eqcst,act); } std::pair @@ -295,6 +321,7 @@ CollectFacets::mapIterator const auto result_insert = _map.insert(std::move(nde)); if (!result_insert.inserted) { double old_rhs=result_insert.position->second.rhs; + /* CHECKME : not the best idea ? */ if (act==CHANGE_RHS || (act==MAX_RHS && old_rhsnrhs)) { @@ -367,8 +394,9 @@ IntervalVector CollectFacets::extractBox() { return ret; } -void CollectFacets::renumber() { +std::vector CollectFacets::renumber() { /* renumber the constraints */ + std::vector ret(_allFacets.size(),-1); Index sineq=0, seq=0; _allFacets.resize(_map.size()); for (mapIterator it = _map.begin(); it!=_map.end(); ++it) { @@ -380,10 +408,12 @@ void CollectFacets::renumber() { ++seq; } _allFacets[sineq++]=it; + ret[it->second.Id-1]=sineq; it->second.Id = sineq; } if (seq<(Index)_eqFacets.size()) _eqFacets.resize(seq); + return ret; } void CollectFacets::encompass_vertices(const @@ -454,4 +484,15 @@ void CollectFacets::encompass_zonotope(const IntervalVector &z, } } +int CollectFacets::merge_CollectFacets(const CollectFacets &collect, + ACTION_DUPLICATE act) { + int count=0; + for (const auto &fct : collect._map) { + std::pair r = this->insert_facet(fct,act); + if (r.first==-1) return -1; + if (r.second) count++; + } + return count; +} + } diff --git a/src/extensions/polytope/codac2_Facet.h b/src/extensions/polytope/codac2_Facet.h index 4b63bb1be..5b482059c 100644 --- a/src/extensions/polytope/codac2_Facet.h +++ b/src/extensions/polytope/codac2_Facet.h @@ -261,12 +261,14 @@ class CollectFacets { create a duplicate facet */ KEEP_RHS, /* change nothing (default behaviour) */ CHANGE_RHS, /* change the value in all cases */ - MAX_RHS, /* maximize the RHS */ - MIN_RHS /* minimize the RHS */ + MAX_RHS, /* maximize the RHS. if two equalities, + duplicate the constraint. if one equality, + change into inequality */ + MIN_RHS /* minimize the RHS. if needed, return "empty" (-1) */ }; - /** insert a new facet row X <= rhs + /** insert a new facet row X <= rhs (or row X = rhs) * @param row Row * @param rhs right-hand side * @param eqcst equality constraint @@ -275,7 +277,7 @@ class CollectFacets { * has been inserted, false if old facet */ std::pair insert_facet(const Row &row, double rhs, bool eqcst, ACTION_DUPLICATE act=KEEP_RHS); - /** insert a new facet row X <= rhs + /** insert a new facet row X <= rhs (or row X = rhs) * @param row Row * @param rhs right-hand side * @param eqcst equality constraint @@ -285,7 +287,8 @@ class CollectFacets { * returns 0 */ std::pair insert_facet(Row &&row, double rhs, bool eqcst, ACTION_DUPLICATE act=KEEP_RHS); - /** insert a new facet row X <= rhs, using the Facet constuction + /** insert a new facet row X <= rhs or row X = rhs, + * using the Facet constuction * @param facet the facet * @param act action if an rhs with the same row exists * @return the Id of the constraint, and true if a new facet @@ -306,6 +309,9 @@ class CollectFacets { /** get the end iterator of the facet * @return an iterator on the facet */ mapIterator endFacet(); + /** get the end iterator of the facet + * @return an iterator on the facet */ + mapCIterator endFacet() const; /** change a eqfacet, keeping its Id (unless the new row creates * a duplicate) @@ -352,7 +358,15 @@ class CollectFacets { nbound=+oo => new facet iterator (for rhs), or endFacet if insertion "failed" */ mapIterator dissociate_eqFacet(Index eqId , double nbound, - ACTION_DUPLICATE act=KEEP_RHS); + ACTION_DUPLICATE act=MAX_RHS); + + /** insert a complete collection of facets + @param collect the collection + @param act handling of the duplicate +  @return the number of added facets, -1 if empty + */ + int merge_CollectFacets(const CollectFacets &collect, + ACTION_DUPLICATE act=MIN_RHS); /** remove a facet by its Id * @param id Id of the facet (starting from 1) @@ -364,18 +378,19 @@ class CollectFacets { * remove some contraints, may put endFacet() for some Id */ IntervalVector extractBox(); - /** renumber the set, removing spurious Id. + /** \brief renumber the set, removing spurious Id. * do not modify the iterators, but modify the indices of the - * facets and equality facets. */ - void renumber(); + * facets and equality facets. + * \return the matching old Id => new Id */ + std::vector renumber(); /** \brief modify the rhs such that the polyhedron encompasses a list * of intervalVector. Also adapt the bbox. * if tight=true, may tighten the bound, and not only increase them. * may add facets if needed to dissociate equality facets. - * \brief vertices the list of vertices - * \brief bbox the bbox (to be updated) - * \brief tight if false, the rhs are only increased */ + * \param vertices the list of vertices + * \param bbox the bbox (to be updated) + * \param tight if false, the rhs are only increased */ void encompass_vertices(const std::vector &vertices, IntervalVector &bbox, bool tight); void encompass_vertices(const std::vector &vertices, @@ -406,15 +421,19 @@ class CollectFacets { /* look for and remove a value in eqFacet */ void remove_in_eqFacets(Index id); /* finish an insertion process */ - std::pair result_insertion(Index id, - std::pair res, + std::pair result_insertion(std::pair res, double rhs, bool eqcst, ACTION_DUPLICATE act); }; inline CollectFacets::CollectFacets(const CollectFacets& cf) : - dim(cf.getDim()), _map(cf._map), _allFacets(cf._allFacets), + dim(cf.getDim()), _map(cf._map), + _allFacets(cf._allFacets.size(),_map.end()), _eqFacets(cf._eqFacets) { + /* redirect the facets */ + for (mapIterator it = _map.begin(); it!=_map.end(); ++it) { + _allFacets[it->second.Id-1]=it; + } } inline Index CollectFacets::getDim() const { @@ -443,5 +462,8 @@ inline Index CollectFacets::nbeqfcts() const { inline CollectFacets::mapIterator CollectFacets::endFacet() { return this->_map.end(); } +inline CollectFacets::mapCIterator CollectFacets::endFacet() const { + return this->_map.cend(); +} } diff --git a/src/extensions/polytope/codac2_Polytope.cpp b/src/extensions/polytope/codac2_Polytope.cpp index d4e21e8d8..e3c133a84 100644 --- a/src/extensions/polytope/codac2_Polytope.cpp +++ b/src/extensions/polytope/codac2_Polytope.cpp @@ -36,7 +36,7 @@ using namespace codac2; namespace codac2 { -Polytope::Polytope() : _dim(-1), _empty(true) +Polytope::Polytope() : _dim(-1), state(pol_state_empty | pol_state(1<(dim)) + _facets(std::make_shared(dim)), + state(empty ? pol_state_empty : pol_state_init) { } Polytope::Polytope(const IntervalVector &box) : _dim(box.size()), - _empty(box.is_empty()), _box(box), - _facets(std::make_shared(_dim)) + _box(box), + _facets(std::make_shared(_dim)), + state(box.is_empty() ? pol_state_empty : pol_state_init) + { } -Polytope::Polytope(const Polytope &P) : _dim(P._dim), _empty(P._empty), - _box(P._box), - _facets(P._empty ? std::make_shared(_dim) : +Polytope::Polytope(const Polytope &P) : _dim(P._dim), _box(P._box), + _facets(P.state[EMPTY] ? std::make_shared(_dim) : std::make_shared(*(P._facets))), - _minimized(P._minimized) + state(P.state & (pol_state_empty | + pol_state(1< &vertices) : { if (vertices.empty()) return; _dim=vertices[0].size(); - _empty= false; /* build the V2F form */ _DDbuildV2F = std::make_unique(1,vertices[0]); for (Index i=1;i<(Index)vertices.size();i++) { @@ -80,7 +83,7 @@ Polytope::Polytope(const std::vector &vertices) : _facets=_DDbuildV2F->getFacets(); _box = _facets->extractBox(); _facets->encompass_vertices(vertices,_box,true); - _minimized=true; /* considered */ + state = pol_state_init; _DDbuildV2F.release(); /* encompass may have added constraints */ } @@ -90,15 +93,18 @@ Polytope::Polytope(const std::vector &vertices, _dim = facetsform.getDim(); if (_dim==-1) return; _facets=std::make_shared(facetsform); + _box = IntervalVector::constant(_dim,Interval::empty()); if (vertices.empty()) { - _box = IntervalVector::constant(_dim,Interval::empty()); - _empty=true; + state = pol_state_empty; return; } + for (Index i=0;i<(Index)vertices.size();i++) { + _box |= vertices[i]; + } _facets=_DDbuildV2F->getFacets(); - _box = _facets->extractBox(); _facets->encompass_vertices(vertices,_box,true); - _minimized=false; + _box = _facets->extractBox(); + state = pol_state_init; } @@ -108,8 +114,8 @@ Polytope::Polytope(const IntervalVector &box, for (const std::pair &cst : facets) { this->add_constraint(cst); } + state = 0; if (minimize) this->minimize_constraints_clp(); - else _minimized=false; } @@ -123,13 +129,14 @@ Polytope::Polytope(const Parallelepiped &par) : this->add_constraint_band(u.row(i), u.row(i).dot(par.z)+Interval(-1.0,1.0),0.0); } - _minimized=true; + IntervalVector range = IntervalVector::constant(par.A.cols(), + Interval(-1.0,1.0)); + _box = par.z+par.A*range; /* FIXME : use box */ } Polytope::Polytope(const Zonotope &zon) : Polytope() { _dim=zon.z.size(); - _empty= false; /* we use the vertices (note : can do better) */ IntervalVector v = IntervalVector::constant(zon.A.cols(),-1.0); this->_DDbuildV2F = std::make_unique(1,(zon.z+zon.A*v).mid()); @@ -153,7 +160,7 @@ Polytope::Polytope(const Zonotope &zon) : _facets->encompass_zonotope(IntervalVector(zon.z), IntervalMatrix(zon.A), range, true); _DDbuildV2F.release(); /* encompass may have added constraints */ - _minimized=true; + state = pol_state_init; } Polytope Polytope::from_ineFile(const char *filename) { @@ -162,7 +169,7 @@ Polytope Polytope::from_ineFile(const char *filename) { if (fcts->getDim()==-1) return Polytope(); Polytope p(fcts->getDim(),false); p._facets = fcts; - p._minimized=false; + p.state = 0; return p; } @@ -177,6 +184,7 @@ Polytope::~Polytope() { Interval Polytope::fast_bound(const FacetBase &base) const { if (base.isNull()) return Interval::zero(); + if (state[EMPTY]) return Interval(); Index gdim = base.gtDim(); Interval res; if (base.row[gdim]>0.0) { @@ -210,7 +218,7 @@ Interval Polytope::fast_bound(const FacetBase &base) const { } } /* check the vertices if _buildF2V is up-to-date */ - if (_DDbuildF2V && _buildF2V_updated) { + if (state[F2VFORM]) { const std::forward_list &vertices=_DDbuildF2V->get_vertices(); for (const DDvertex &vt : vertices) { IntervalVector vtB = _DDbuildF2V->compute_vertex(vt.vertex); @@ -221,11 +229,40 @@ Interval Polytope::fast_bound(const FacetBase &base) const { return res; } +double Polytope::bound_row_F2V(const Row &r) const { + this->build_DDbuildF2V(); + double a = -oo; + if (state[EMPTY]) return a; + const std::forward_list &vertices=_DDbuildF2V->get_vertices(); + for (const DDvertex &vt : vertices) { + IntervalVector vtB = _DDbuildF2V->compute_vertex(vt.vertex); + Interval b = r.dot(vtB); + a = std::max(a,b.ub()); + } + return a; +} + +double Polytope::bound_row_clp(const Row &r) const { + this->build_clpForm(); + _clpForm->setObjective(r); + LPclp::lp_result_stat ret = _clpForm->solve(); + if (ret[EMPTY]) return -oo; + return _clpForm->getValobj().ub(); +} + +double Polytope::bound_row(const Row &r) const { + assert(r.size()==_dim); + if (state[EMPTY]) return -oo; + if (!state[F2VFORM] && state[CLPFORM]) { + return this->bound_row_clp(r); + } + return this->bound_row_F2V(r); +} + void Polytope::build_DDbuildF2V() const { - if (_buildF2V_updated) return; - if (_empty) { + if (state[F2VFORM]) return; + if (state[EMPTY]) { _DDbuildF2V=nullptr; - _buildF2V_updated=true; return; } /* TODO : insert box progressively ? */ @@ -233,41 +270,39 @@ void Polytope::build_DDbuildF2V() const { for (CollectFacets::mapCIterator it = _facets->get_map().begin(); it!=_facets->get_map().end(); ++it) { if ((*it).second.eqcst) continue; -// std::cout << " add facet " << *it << "\n"; _DDbuildF2V->add_facet(it); -// std::cout << " result " << *_DDbuildF2V << "\n"; if (_DDbuildF2V->is_empty()) { - _empty=true; - _box.set_empty(); - _DDbuildF2V=nullptr; - break; + this->set_empty_private(); + return; } + state[NOTEMPTY]=true; } - _buildF2V_updated=true; + state[F2VFORM]=true; } void Polytope::build_clpForm() const { - if (_clpForm_updated) return; - if (_empty) { + if (state[CLPFORM]) return; + if (state[EMPTY]) { _clpForm=nullptr; - _clpForm_updated=true; return; } _clpForm = std::make_unique(_dim,_facets,_box); - _clpForm_updated=true; + state[CLPFORM]=true; } void Polytope::minimize_constraints_clp(const Interval &tolerance) const { - if (_empty) return; + assert_release(!state[MINIMIZED]); this->build_clpForm(); - int ret = _clpForm->minimize_polytope(tolerance, true); + int ret = _clpForm->minimize_polytope(tolerance, false, !state[BOXUPDATED]); if (ret==-1) { - _empty=true; - _box.set_empty(); - _minimized=true; + this->set_empty_private(); return; } - _box &= _clpForm->get_bbox(); + state[NOTEMPTY]=true; + if (!state[BOXUPDATED]) { + _box &= _clpForm->get_bbox(); + state[BOXUPDATED]=true; + } bool changed=false; for (Index i=0;i<_facets->nbfcts();i++) { if (_clpForm->isRedundant(i)) { @@ -276,35 +311,150 @@ void Polytope::minimize_constraints_clp(const Interval &tolerance) const { } } if (changed) { - _facets->renumber(); - _clpForm_updated=_buildF2V_updated=_buildV2F_updated=false; + std::vector corresp = _facets->renumber(); + if (state[F2VFORM]) { + _DDbuildF2V->update_renumber(corresp); + } + state[CLPFORM]=false; state[V2FFORM]=false; _clpForm=nullptr; } - _minimized=true; + state[MINIMIZED]=true; } +void Polytope::minimize_constraints_F2V() const { + assert_release(!state[MINIMIZED]); + this->build_DDbuildF2V(); + if (state[EMPTY]) return; + std::set redundant = _DDbuildF2V->redundantFacets(); + _box &= _DDbuildF2V->build_bbox(); + state[BOXUPDATED]=true; + bool changed=false; + for (const Index &a : redundant) { + if (a<=0) continue; + _facets->removeFacetById(a); + changed=true; + } + if (changed) { + std::vector corresp = _facets->renumber(); + _DDbuildF2V->update_renumber(corresp); + state[CLPFORM]=false; state[V2FFORM]=false; + _clpForm=nullptr; + } + state[MINIMIZED]=true; +} -#if 0 -const IntervalVector& Polytope::update_box() { - if (_dim==-1 || _empty) return this->_box; - if (!_clpForm) return this->_box; - Row obj = Row::zero(_dim); - for (Index i=0;i<_dim;i++) { - obj[i]=1.0; - _clpForm->setObjective(obj); - LPclp::lp_result_stat ret = _clpForm->solve(false,4); - if (ret[LPclp::EMPTY]) { _empty=true; _box.set_empty(); return _box; } - if (ret[LPclp::BOUNDED]) _box[i] = min(_box[i],_clpForm->getValobj()); - obj[i]=-1.0; - _clpForm->setObjective(obj); - ret = _clpForm->solve(false,4); - if (ret[LPclp::EMPTY]) { _empty=true; _box.set_empty(); return _box; } - if (ret[LPclp::BOUNDED]) _box[i] = max(_box[i],-_clpForm->getValobj()); - } - _clpForm->set_bbox(_box); - return this->_box; +void Polytope::minimize_constraints() const { + if (state[MINIMIZED]) return; + if (state[EMPTY]) return; + /* default : F2V */ + if (!state[F2VFORM] && state[CLPFORM]) { + this->minimize_constraints_clp(); + return; + } + this->minimize_constraints_F2V(); } +void Polytope::update_box_clp() const { + assert_release(!state[BOXUPDATED]); + this->build_clpForm(); + int ret = _clpForm->minimize_box(); + if (ret==-1) { + this->set_empty_private(); + return; + } + _box &= _clpForm->get_bbox(); + state[NOTEMPTY]=true; + state[BOXUPDATED]=true; +} + +void Polytope::update_box_F2V() const { + assert_release(!state[BOXUPDATED]); + this->build_DDbuildF2V(); + if (state[EMPTY]) return; + _box &= _DDbuildF2V->build_bbox(); + state[BOXUPDATED]=true; +} + +void Polytope::update_box() const { + if (state[BOXUPDATED]) return; + if (state[EMPTY]) return; + /* default : F2V */ + if (!state[F2VFORM] && state[CLPFORM]) { + this->update_box_clp(); + return; + } + this->update_box_F2V(); +} + +bool Polytope::check_empty_clp() const { + assert_release(!state[NOTEMPTY] && !state[EMPTY]); + this->build_clpForm(); + int ret = _clpForm->check_emptiness(); + if (ret==-1) { + this->set_empty_private(); + return true; + } + state[NOTEMPTY]=true; + return false; +} + +bool Polytope::check_empty_F2V() const { + assert_release(!state[NOTEMPTY] && !state[EMPTY]); + this->build_DDbuildF2V(); + return state[EMPTY]; +} + +bool Polytope::check_empty() const { + if (state[NOTEMPTY]) return false; + if (state[EMPTY]) return true; + /* default : F2V */ + if (!state[F2VFORM] && state[CLPFORM]) { + return this->check_empty_clp(); + } + return this->check_empty_F2V(); +} + +BoolInterval Polytope::contains(const IntervalVector& p) const { + assert_release(p.size()==_dim); + if (p.is_empty()) return BoolInterval::TRUE; + if (state[EMPTY]) return BoolInterval::FALSE; + if (!p.is_subset(_box)) return BoolInterval::FALSE; + BoolInterval r = BoolInterval::TRUE; + for (const auto &fct : _facets->get_map()) { + Facet_::polytope_inclrel pincl = + Facet_::relation_Box(fct,p); + if (pincl[Facet_::NOTINCLUDE]) return BoolInterval::FALSE; + if (pincl[Facet_::MAYINCLUDE]) r = BoolInterval::UNKNOWN; + } + return r; +} + +bool Polytope::box_is_included(const IntervalVector& x) const { + return this->box().is_subset(x); +} + +bool Polytope::is_included(const Polytope& P, bool checkF2V, bool checkCLP) + const { + const IntervalVector &b2 = P.box(true); + const IntervalVector &b1 = this->box(true); + if (!b1.is_subset(b2)) return false; + for (const auto &fctP : P._facets->get_map()) { + if (this->fast_bound(fctP.first).ub() <= fctP.second.rhs) continue; + if (checkF2V) { + double l1 = this->bound_row_F2V(fctP.first.row); + if (l1<=fctP.second.rhs) continue; + } + if (checkCLP) { + double l1 = this->bound_row_clp(fctP.first.row); + if (l1<=fctP.second.rhs) continue; + } + return false; + } + return true; +} + +#if 0 + polytope_inclrel Polytope::relation_Box(const IntervalVector& p) const { assert_release(_dim>=0); if (p.is_empty()) return (1<::quiet_NaN()); } -double Polytope::distance_cst(const Facet &fc) const { +double Polytope::;istance_cst(const Facet &fc) const { assert_release(_dim==fc.row.size()); if (_empty) return -oo; if (!_clpForm) return (fc.row.dot(_box)-fc.rhs).ub(); @@ -370,23 +520,22 @@ double Polytope::distance_cst(const Facet &fc) const { return (_clpForm->getValobj()-fc.rhs).ub(); } -bool Polytope::box_is_included(const IntervalVector& x) const { - return _box.is_subset(x); -} - +#endif void Polytope::clear() { - assert_release(_dim>=0); - _empty=false; + assert_release(!state[INVALID]); + state=pol_state_init; _box = IntervalVector::Zero(_dim); - this->build_clpForm_from_box(); + _facets=nullptr; + _clpForm=nullptr; + _DDbuildF2V=nullptr; + _DDbuildV2F=nullptr; } -#endif bool Polytope::add_constraint(const std::pair& facet, double tolerance) { - if (_empty) return false; + if (state[EMPTY]) return false; FacetBase base = FacetBase(facet.first); if (base.isNull()) { if (facet.second>=tolerance) return false; @@ -400,41 +549,66 @@ bool Polytope::add_constraint(const std::pair& facet, if (facet.first[gdim]>0.0) { _box[gdim] &= Interval(-oo,val.ub()); if (_box[gdim].is_empty()) { - this->set_empty(); + this->set_empty_private(); return true; } + if (state[CLPFORM]) { + this->_clpForm->set_bbox(_box); + } + if (state[F2VFORM]) { + int ret = _DDbuildF2V->add_bound_var(gdim,true,val.ub()); + if (ret==-1) { + this->set_empty_private(); + return true; + } + if (ret==0) return false; + } } else { _box[gdim] &= Interval(-val.ub(),+oo); if (_box[gdim].is_empty()) { - this->set_empty(); + this->set_empty_private(); return true; } + if (state[CLPFORM]) { + this->_clpForm->set_bbox(_box); + } + if (state[F2VFORM]) { + int ret = _DDbuildF2V->add_bound_var(gdim,false,-val.ub()); + if (ret==-1) { + this->set_empty_private(); + return true; + } + if (ret==0) return false; + } } - if (_clpForm_updated) { - this->_clpForm->set_bbox(_box); - } - _buildF2V_updated = _buildV2F_updated = false; - /* TODO : update _DDbuildF2V */ - _minimized=false; + state[V2FFORM]=false; + state[MINIMIZED]=false; + state[NOTEMPTY]=false; return true; } auto res= _facets->insert_facet(facet.first, facet.second, false, CollectFacets::MIN_RHS); + if (res.first==-1) { this->set_empty_private(); return true; } if (res.first==0) return false; - if (_clpForm_updated) { + if (state[CLPFORM]) { this->_clpForm->updateConstraint(res.first); } - if (_buildF2V_updated) { - this->_DDbuildF2V->add_facet((*_facets)[res.first-1]); + if (state[F2VFORM]) { + int ret = this->_DDbuildF2V->add_facet((*_facets)[res.first-1]); + if (ret==-1) { + this->set_empty_private(); + return true; + } + if (ret==0) return false; } - _buildV2F_updated = false; - _minimized=false; + state[V2FFORM]=state[MINIMIZED]=state[BOXUPDATED]=false; + state[NOTEMPTY]=false; return true; } bool Polytope::add_constraint(const IntervalRow &cst, double rhs, double tolerance) { - if (_empty) return false; + if (state[EMPTY]) return false; Row cstmid = cst.mid(); IntervalRow rem = cst-cstmid; Interval d = rhs+rem.dot(_box); @@ -444,7 +618,7 @@ bool Polytope::add_constraint(const IntervalRow &cst, double rhs, std::pair Polytope::add_constraint_band(const IntervalRow &cst, const Interval &rhs, double tolerance) { - if (_empty) return { false, false }; + if (state[EMPTY]) return { false, false }; Row cstmid = cst.mid(); IntervalRow rem = cst-cstmid; Interval d = rhs+rem.dot(_box); @@ -456,19 +630,78 @@ std::pair Polytope::add_constraint_band(const IntervalRow &cst, return { rlb, rub }; } -void Polytope::set_empty() const { - _empty=true; - _box.set_empty(); -} - -void Polytope::set_empty() { - _empty=true; - _box.set_empty(); +int Polytope::meet_with_box(const IntervalVector &b) { + assert(!state[INVALID]); + if (_box.is_subset(b)) return 0; + if (_box.is_disjoint(b)) { this->set_empty(); return -1; } + _box &= b; + if (state[CLPFORM]) { + this->_clpForm->set_bbox(_box); + } + if (state[F2VFORM]) { + if (!b.is_degenerated()) { + int ret = this->_DDbuildF2V->add_constraint_box(b); + if (ret==-1) { + this->set_empty(); + return -1; + } + if (ret==0) return 0; + } else { /* we do not keep the representation */ + state[F2VFORM]=false; + _DDbuildV2F=nullptr; + } + } + state[V2FFORM]=state[MINIMIZED]=state[BOXUPDATED]=false; + state[NOTEMPTY]=false; + return 1; +} + +Polytope &Polytope::operator&=(const IntervalVector &b) { + this->meet_with_box(b); + return (*this); +} + +int Polytope::meet_with_polytope(const Polytope &P) { + assert_release(!P.state[INVALID]); + assert_release(!state[INVALID]); + if (state[EMPTY]) return -1; + if (P.state[EMPTY]) { this->set_empty(); return -1; } + if (P._facets->nbfcts()==0) { return this->meet_with_box(P._box); } + int ret=0; + if (!_box.is_subset(P._box)) { + _box &= P._box; + ret=1; + if (_box.is_empty()) { this->set_empty(); return -1; } + } + int ret2= this->_facets->merge_CollectFacets(*(P._facets)); + if (ret2==-1) { this->set_empty(); return -1; } + if (ret2==0 && ret==0) return 0; + state[V2FFORM]=state[MINIMIZED]=state[BOXUPDATED]=false; + state[NOTEMPTY]=false; + state[F2VFORM]=state[CLPFORM]=false; + return 1; +} + +Polytope &Polytope::operator&=(const Polytope &P) { + this->meet_with_polytope(P); + return (*this); +} + +std::ostream& operator<<(std::ostream& os, + const Polytope &P) { + if (P.state[Polytope::EMPTY]) { + os << "Polytope(empty dim " << P._dim << ")" << std::endl; + return os; + } + os << "Polytope(bbox " << P._box << ") : " << std::endl; + os << *P._facets; + os << "EndPolytope" << std::endl; + return os; } std::vector Polytope::compute_vertices() const { this->build_DDbuildF2V(); - if (_empty) return std::vector(); + if (state[EMPTY]) return std::vector(); std::vector ret; for (const DDvertex &vtx : _DDbuildF2V->get_vertices()) { ret.push_back(_DDbuildF2V->compute_vertex(vtx.vertex)); @@ -478,7 +711,7 @@ std::vector Polytope::compute_vertices() const { std::vector> Polytope::compute_3Dfacets() const { this->build_DDbuildF2V(); - if (_empty) return std::vector>(); + if (state[EMPTY]) return std::vector>(); return build_3Dfacets(*_DDbuildF2V); } diff --git a/src/extensions/polytope/codac2_Polytope.h b/src/extensions/polytope/codac2_Polytope.h index d363ad08d..05dc70a5f 100644 --- a/src/extensions/polytope/codac2_Polytope.h +++ b/src/extensions/polytope/codac2_Polytope.h @@ -151,7 +151,7 @@ namespace codac2 { /** * \brief is empty */ - bool is_empty() const; + bool is_empty(bool check=true) const; /** * \brief has a flat dimension @@ -159,7 +159,9 @@ namespace codac2 { bool is_flat() const; /** \brief ``component'' : interval of the bounding box - * (only const !!!) */ + * (only const !!!). Update the bbox if needed. + * \return enclosing of the component + */ const Interval& operator[](Index i) const; /** @@ -178,6 +180,14 @@ namespace codac2 { * \return the interval I */ Interval fast_bound(const FacetBase &base) const; + /** + * \brief bound a constraint, using either clp or DD + * \param r the row + * \return the max + */ + double bound_row(const Row &r) const; + +#if 0 /** \brief ``distance'' from the satisfaction of a constraint * ie the inflation of the rhs needed to satisfy the constraint * \param fc the constraint @@ -190,6 +200,7 @@ namespace codac2 { * \param p the box * \return polytope_inclrel checking inclusion and intersection */ Facet_::polytope_inclrel relation_Box(const IntervalVector& p) const; +#endif /** * \brief Checks whether the polytope contains a given point, or @@ -200,6 +211,16 @@ namespace codac2 { */ BoolInterval contains(const IntervalVector& p) const; + /** + * \brief Checks inclusion in another polytope + * \param P the polytope + * \param checkF2V checks using DD + * \param checkCLP checks using CLP + * \return true if inclusion is guaranteed + */ + bool is_included(const Polytope &P, + bool checkF2V=true, bool checkCLP=true) const; + /** \brief intersects a box * \param x the box (IntervalVector) * \return if the polytope intersects the box @@ -212,6 +233,11 @@ namespace codac2 { */ BoolInterval intersects(const Polytope &p) const; + /** minimize the constraints, removing (possibly) redundant + * constraints. + */ + void minimize_constraints() const; + /************* Box access *************************/ /** @@ -221,22 +247,22 @@ namespace codac2 { * * \return The current bounding box */ - const IntervalVector &box() const; + const IntervalVector &box(bool tight=true) const; - /** - * \brief compute and update the bounding box - * (using LPs on the polytope) - * - * \return the updated hull box - */ - const IntervalVector &update_box() const; - /** * \brief test if bounding box is included - * (mainly useful if current bounding box is tight) + * \return true if the polytope is included in x */ bool box_is_included(const IntervalVector& x) const; + /** + * \brief tests if the polytope is bisectable + * (i.e. its bounding box is bisectable). + * do not update the box + * \return true if the bbox is bisectable + */ + bool is_bisectable() const; + /************* Modification **********************/ /** modification creates a new polytope **/ @@ -244,7 +270,7 @@ namespace codac2 { /** \brief set to empty */ void set_empty(); - /** \brief set to (singleton) 0 */ + /** \brief set to (singleton) 0^dim */ void clear(); /** \brief add a inequality (pair row X <= rhs) @@ -274,6 +300,26 @@ namespace codac2 { std::pair add_constraint_band(const IntervalRow &cst, const Interval &rhs, double tolerance=0.0); + /** \brief intersect with a box. + * \param b the box + * \return 0 if nothing done, 1 changed made, -1 results is empty */ + int meet_with_box(const IntervalVector &b); + + /** \brief intersect with a box. + * \param b the box + * \return *this */ + Polytope &operator&=(const IntervalVector &b); + + /** \brief intersect with a polytope. + * \param P the polytope + * \return 0 if nothing done, 1 changed made, -1 results is empty */ + int meet_with_polytope(const Polytope &P); + + /** \brief intersect with a polytope. + * \param P the polytope + * \return *this */ + Polytope &operator&=(const Polytope &P); + /** \brief inflation by a cube * this <- this + [-rad,rad]^d * \param rad radius of the box @@ -304,6 +350,15 @@ namespace codac2 { /*********** Printing and other ``global access'' *********/ + /** + * \brief output the bbox and the set of facets + * \param os the output stream + * \param P the polytope + * \return the stream + */ + friend std::ostream& operator<<(std::ostream& os, + const Polytope &P); + /** * \brief Computes a set of vertices enclosing the polytope * @@ -318,9 +373,29 @@ namespace codac2 { */ std::vector> compute_3Dfacets() const; + + /** \brief state of the polytope */ + enum POLSTATE { + EMPTY, /* is empty */ + NOTEMPTY, /* is NOT empty */ + MINIMIZED, /* minimized : redundant constraints removed */ + BOXUPDATED, /* box is minimal */ + CLPFORM, /* has an up-to-date clp form */ + F2VFORM, /* has an up-to-date F2V form */ + V2FFORM, /* has an up-to-date V2F form */ + INVALID, /* not initialised */ + SIZE_POLSTATE + }; + using pol_state = std::bitset; + static constexpr pol_state pol_state_empty = + 1< _facets; /* "native" formulation , may be shared by other formulations */ @@ -329,27 +404,53 @@ namespace codac2 { /* DDbuildF2V formulation, if used */ mutable std::unique_ptr _DDbuildV2F; /* DDbuildV2F formulation, generally not used */ - mutable bool _minimized=true; - mutable bool _clpForm_updated=false; - mutable bool _buildF2V_updated=false; - mutable bool _buildV2F_updated=false; + mutable pol_state state = pol_state_init; void minimize_constraints_clp(const Interval &tolerance=Interval(0.0)) const; + void minimize_constraints_F2V() const; + void update_box_clp() const; + void update_box_F2V() const; + void update_box() const; + bool check_empty_clp() const; + bool check_empty_F2V() const; + bool check_empty() const; + double bound_row_clp(const Row &r) const; + double bound_row_F2V(const Row &r) const; void build_DDbuildF2V() const; void build_clpForm() const; - void set_empty() const; + void set_empty_private() const; }; -inline const IntervalVector &Polytope::box() const { return _box; } +inline const IntervalVector &Polytope::box(bool tight) const { + if (tight) this->update_box(); + return _box; +} inline Index Polytope::dim() const { return _dim; } inline Index Polytope::size() const { return _dim; } -inline bool Polytope::is_empty() const { return _empty; } -inline bool Polytope::is_flat() const { return _empty || +inline bool Polytope::is_empty(bool check) const { + if (!check) return state[EMPTY]; + return this->check_empty(); } +inline bool Polytope::is_flat() const { return state[EMPTY] || _box.is_degenerated() || (_facets && _facets->nbeqfcts()>0); } inline Index Polytope::nbFacets() const { - if (_empty) return -1; + if (state[EMPTY]) return -1; if (!_facets) return (2*_dim); return (2*_dim + _facets->nbfcts()); } +inline void Polytope::set_empty_private() const { + state = pol_state_empty; + _box.set_empty(); + _clpForm=nullptr; + _DDbuildF2V=nullptr; + _DDbuildV2F=nullptr; + _facets=nullptr; +} + + +inline void Polytope::set_empty() { + this->set_empty_private(); +} + + } diff --git a/src/extensions/polytope/codac2_Polytope_dd.cpp b/src/extensions/polytope/codac2_Polytope_dd.cpp index c607a6e05..3cd64ca85 100644 --- a/src/extensions/polytope/codac2_Polytope_dd.cpp +++ b/src/extensions/polytope/codac2_Polytope_dd.cpp @@ -27,83 +27,12 @@ #include "codac2_Facet.h" #include "codac2_Polytope_dd.h" #include "codac2_IntvFullPivLU.h" +#include "codac2_PosetMax.h" #undef DEBUG_CODAC2_DD namespace codac2 { -/*************************/ -/****** PairMaxSet *******/ -/*************************/ -template -struct PairMaxSets { - T a,b; - std::vector elems; - - PairMaxSets() {} - - PairMaxSets(T a, T b, const std::vector e1, const std::vector e2) : a(a), b(b) { - Index i=0,j=0; - while(i<(Index)e1.size() && j<(Index)e2.size()) { - if (e1[i]==e2[j]) { - elems.push_back(e1[i]); - i++; j++; - } else if (e1[i] e1, const std::vector e2, const Index rejectF) : a(a), b(b) { - Index i=0,j=0; - while(i<(Index)e1.size() && j<(Index)e2.size()) { - if (e1[i]==e2[j]) { - if (e1[i]!=rejectF) - elems.push_back(e1[i]); - i++; j++; - } else if (e1[i]a,p2.a); - std::swap(this->b,p2.b); - this->elems.swap(p2.elems); - } - - int comp(const PairMaxSets &pm) const { - /* -1 if elems included in pm.elems; 0 if ==, - +1 elems includes pm.elems, +2 if not comparable */ - const std::vector &e1=elems; - const std::vector &e2=pm.elems; - std::vector::size_type i=0,j=0; - bool leq=(e1.size()<=e2.size()), geq=(e1.size()>=e2.size()); - bool same_length = (e1.size()==e2.size()); - /* leq=F,geq=T => +1 - leq=F,geq=F => 2 - leq=T,geq=F => -1 - leq=T,geq=T => 0 */ - while(ie2[j]) { - if (!leq || same_length) return 2; - geq=false; j++; - } else { i++; j++; } - } - if (iadd_constraint_box(bbox); } -void DDbuildF2V::add_bound_var(Index c, bool mx, double rhs) { - if (empty) return; - if (rhs==+oo) return; +int DDbuildF2V::add_bound_var(Index c, bool mx, double rhs) { + if (empty) return -1; + if (mx && rhs==+oo) return 0; + if (!mx && rhs==-oo) return 0; IntervalRow facet = IntervalRow::Zero(this->fdim+1); facet[0] = -rhs; if (flat) { @@ -190,18 +120,24 @@ void DDbuildF2V::add_bound_var(Index c, bool mx, double rhs) { } if (!mx) facet=-facet; Index idFacet = -c-1-(mx ? 0 : this->dim); - this->add_facet(idFacet,facet); + return this->add_facet(idFacet,facet); } -void DDbuildF2V::add_constraint_box(const IntervalVector &box) { +int DDbuildF2V::add_constraint_box(const IntervalVector &box) { + int ret=0; for (Index i=0;iadd_bound_var(i,true,box[i].ub()); + int ret1=this->add_bound_var(i,true,box[i].ub()); + if (ret1==-1) return -1; + if (ret1==1) ret=1; } for (Index i=0;iadd_bound_var(i,false,box[i].lb()); + int ret1=this->add_bound_var(i,false,box[i].lb()); + if (ret1==-1) return -1; + if (ret1==1) ret=1; } + return ret; } std::forward_list::iterator DDbuildF2V::addDDvertex(const IntervalVector &v) { @@ -401,7 +337,8 @@ int DDbuildF2V::add_facet(Index idFacet, const IntervalRow &facet) { std::forward_list::iterator itVertex = vertices.begin(); std::priority_queue::iterator, std::vector::iterator>, - CmpDD> stack; + CmpDD> stack; /* note : absolutely not clear that we need + a priority queue */ Index refFacet=-1; while (itVertex!=vertices.end()) { // bool inBoundary; @@ -495,7 +432,8 @@ int DDbuildF2V::add_facet(Index idFacet, const IntervalRow &facet) { #ifdef DEBUG_CODAC2_DD std::cout << " fin traitement liens : " << adjacentVertices.size() << "\n"; #endif - std::vector::iterator>> maxset; + PosetMaxSet::iterator, + std::forward_list::iterator>> maxset; for (Index i=0;i<=(Index)adjacentVertices.size()-1;i++) { #ifdef DEBUG_CODAC2_DD std::cout << " sommet : " << adjacentVertices[i].first->Id << " : "; @@ -505,51 +443,29 @@ int DDbuildF2V::add_facet(Index idFacet, const IntervalRow &facet) { std::cout << std::endl; #endif for (Index j=i+1;j<(Index)adjacentVertices.size();j++) { - PairMaxSets::iterator> - actuel(adjacentVertices[i].first, - adjacentVertices[j].first, + PosetElem::iterator, + std::forward_list::iterator>> + actuel(std::pair(adjacentVertices[i].first, + adjacentVertices[j].first), adjacentVertices[i].second, adjacentVertices[j].second,refFacet); if (actuel.elems.size()Id << "\n"; #endif - for (const PairMaxSets::iterator> + for (const PosetElem::iterator, + std::forward_list::iterator>> &pm : maxset) { #ifdef DEBUG_CODAC2_DD if (pm.elems.size()Id << " " << pm.b->Id << " " << pm.elems.size() << std::endl; + std::cout << " maxset strange : " << pm.a.first->Id << " " << pm.a.second->Id << " " << pm.elems.size() << std::endl; } - std::cout << pm.a->Id << " " << pm.b->Id << "\n"; + std::cout << pm.a.first->Id << " " << pm.a.second->Id << "\n"; #endif - [[maybe_unused]] bool res = this->addDDlink(pm.a,pm.b); + [[maybe_unused]] bool res = this->addDDlink(pm.a.first,pm.a.second); #ifdef DEBUG_CODAC2_DD std::cout << (res ? " added " : " already here ") << "\n"; #endif @@ -584,41 +500,86 @@ int DDbuildF2V::add_facet(CollectFacets::mapCIterator itFacet) { return this->add_facet(idFacet,facet); } -/* TODO : make a specific computation */ -#if 0 -bool DDbuildF2V::vertex_compatible(const std::vector &fcts, - const IntervalVector &v) { - IntervalMatrix M = IntervalMatrix::zero(fcts.size(), v.size()); - for (unsigned long int i=0;i DDbuildF2V::redundantFacets() { + std::map> mapping; + std::set resultat; /* set of existing facets */ + for (Index i = -2*dim;i<0;i++) { + resultat.insert(i); + } + for (const auto &u : facetsptr->get_map()) { + resultat.insert(u.second.Id); + } + for (const DDvertex &vt : vertices) { + /* vt.Id is in decreasing order , therefore we consider + the negation of vt.Id to be in increasing order */ + Index id = vt.Id; + for (Index ft : vt.fcts) { + std::vector &elem = mapping[ft]; + elem.push_back(-id); /* in decreasing order */ + } + } + PosetMaxSet facetElems; + for (std::pair> &p : mapping) { +// std::cout << "insert " << p.first << ":" << reffacets[p.first] << " " << p.second.size() << " : "; +// for (Index q : p.second) std::cout << q << "\n"; + facetElems.addElement(p.first,p.second); /* swap p.second */ + } + /* to get the result of facetElems, we store booleans for each facets */ + std::vector redundant(reffacets.size(),true); + for (const PosetElem &elm : facetElems) { + redundant[elm.a]=false; + resultat.erase(reffacets[elm.a]); + /* note : this approach (fill resultat then remove + irredundant facets) is ok when one facet may "appear" + several times */ } - M.row(fcts.size())=v; - IntvFullPivLU decLU(M); - return decLU.rank.lb()+eqfacets.size()==v.size(); + /* renumbering reffacets */ + std::vector new_reffacets; + Index nb_irredundant=0; + for (unsigned int i=0;isecond.removeLnk(Id); +/* update reffacets following a renumbering */ +void DDbuildF2V::update_renumber(const std::vector &ret) { + for (Index &a : reffacets) { + if (a<=0) continue; + if (ret[a-1]==-1) { + a=0; + } else { + a=ret[a-1]; } } - this->vertices.erase(Id); } -IntervalVector DDbuildF2V::recompute_vertex(const IntervalVector &vect) const { - IntervalVector v=vect; - for (Index i = eqfacets.size()-1; i>=0; i--) { - const EqFacet &ef = eqfacets[i]; - v[ef.dim]=ef.eqcst.dot(v)+ef.valcst; +/* build the bbox of all vertices */ +IntervalVector DDbuildF2V::build_bbox() const { + IntervalVector b = IntervalVector::empty(dim); + for (const DDvertex &a : vertices) { + b |= this->compute_vertex(a.vertex); } - return v; + return b; } -#endif + +/* normalisation for printing of vertice... good idea ? */ IntervalVector normalise(const IntervalVector &v) { IntervalVector res(v); if (res[0].lb()<=0.0) return res; @@ -918,7 +879,8 @@ int DDbuildV2F::add_vertex(Index idVertex, const IntervalVector& vertex) { this->removeFacet(it,false); } /*construction of new links for the eq facets */ - std::vector::iterator>> maxset; + PosetMaxSet::iterator, + std::forward_list::iterator>> maxset; for (std::forward_list::iterator it = facets.begin(); it!=facets.end();++it) { DDfacet &d = (*it); @@ -927,40 +889,19 @@ int DDbuildV2F::add_vertex(Index idVertex, const IntervalVector& vertex) { it2!=facets.end(); ++it2) { DDfacet &d2 = (*it2); if (d2.status==DDfacet::FACETON || d2.status==DDfacet::FACETNEW) { - PairMaxSets actuel(it,it2,d.vtx,d2.vtx); - bool placed=false; - unsigned long int k=0, l=0; - while (k::iterator, + std::forward_list::iterator>> + actuel(std::pair(it,it2),d.vtx,d2.vtx); + maxset.addElement(std::move(actuel)); } } } } - for (const PairMaxSets::iterator> &pm : maxset) { + for (const PosetElem::iterator, + std::forward_list::iterator>> &pm : maxset) { // std::cout << " maxset : " << pm.a << " " << pm.b << std::endl; - pm.a->addLnk(pm.b); - pm.b->addLnk(pm.a); + pm.a.first->addLnk(pm.a.second); + pm.a.second->addLnk(pm.a.first); } facets.remove_if([](const DDfacet &v) { return v.status==DDfacet::FACETREM; }); diff --git a/src/extensions/polytope/codac2_Polytope_dd.h b/src/extensions/polytope/codac2_Polytope_dd.h index 8ed970a40..1c6ed5a44 100644 --- a/src/extensions/polytope/codac2_Polytope_dd.h +++ b/src/extensions/polytope/codac2_Polytope_dd.h @@ -193,14 +193,13 @@ class DDbuildF2V { std::shared_ptr facetsptr, bool include_box=true); -// void add_constraint_box(const IntervalVector &box); - /* add an inequality facet in the build */ + /* return 1 : changed, 0 : not changed, -1 : empty */ int add_facet(Index id); int add_facet(CollectFacets::mapCIterator itFacet); - void add_constraint_box(const IntervalVector &box); - void add_bound_var(Index c, bool mx, double rhs); + int add_constraint_box(const IntervalVector &box); + int add_bound_var(Index c, bool mx, double rhs); IntervalVector compute_vertex(const IntervalVector &vect) const; Index get_dim() const; @@ -209,6 +208,16 @@ class DDbuildF2V { bool is_empty() const; const IntervalMatrix &get_M_EQ() const; + /* return a set of the Id of (detected as) redundant facets , + and remove these facets of the representation */ + std::set redundantFacets(); + + /* update reffacets following a renumbering */ + void update_renumber(const std::vector &ref); + + /* return the bounding box of all vertices */ + IntervalVector build_bbox() const; + const std::vector &get_lines() const; const std::forward_list &get_vertices() const; const std::vector &get_reffacets() const; @@ -226,43 +235,6 @@ class DDbuildF2V { DDvertex &p2, double rhs, Index idFacet); */ /* void removeVertex(Index Id, bool removeLnks); */ -#if 0 - /* x_dim = eqcst x + rhs */ - struct EqFacet { - Index dim; - Row eqcst; - Interval valcst; - - EqFacet (Index dim, const Row &eqcst, const Interval &valcst) : - dim(dim), eqcst(eqcst), valcst(valcst) { }; - - /* computation of the value */ - void computeVal(IntervalVector &x) const { - x[dim] = this->eqcst.dot(x) + valcst; - } - - /* adapt box */ - void adapt_box(IntervalVector &bbox) const; - - /* adaptation of an equality constraint cst x = rhs */ - /* with alpha = cst[dim], - we have (cst' + alpha*eqcst).mid x' = rhs - alpha*valcst + - [-1,1] * (cst' + alpha*eqcst).rad * box */ - void adapt_eqconstraint(const IntervalVector &bbox, - Row &cst, Interval &rhs) const; - - /* adaptation of an inequality constraint cst x <= rhs */ - /* with alpha = cst[dim], - we have (cst' + alpha*eqcst) x' <== (rhs - alpha*valcst).ub */ - void adapt_ineqconstraint(IntervalRow &cst, double &rhs) const; - - /* construction of a new eqfacet from an equality constraint - cst x = rhs */ - static EqFacet build_EqFacet(const IntervalVector &bbox, - const Row &cst, Interval &rhs); - }; -#endif - Index dim; Index fdim; /* dimension - nb independant equalities constraints */ IntervalVector bbox; diff --git a/src/extensions/polytope/codac2_PosetMax.h b/src/extensions/polytope/codac2_PosetMax.h new file mode 100644 index 000000000..9d3c874a2 --- /dev/null +++ b/src/extensions/polytope/codac2_PosetMax.h @@ -0,0 +1,145 @@ +/** + * \file codac2_PosetMax.h Managing a set of maximal elements in a poset + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2025 + * \license This program is distributed under the terms of + * the GNU Lesser General Public License (LGPL). + */ + +#pragma once + +#include +#include +#include +#include + +#include "codac2_Index.h" + +namespace codac2 { + /* the poset is an (ordered) vector of Index */ + /* used in .h because of its templated version */ + +/*** elements ****************************************************/ +/*** we may store more that set of elems to speed up computation */ +template +struct PosetElem { + T a; + std::vector elems; + + PosetElem() {} + + PosetElem(const T &a) : a(a) { + } + /* building with one list. e1 is assumed to be ordered */ + PosetElem(const T &a, const std::vector &e1) : a(a), elems(e1) { + } + + /* intersection of two lists */ + PosetElem(const T &a, const std::vector &e1, const std::vector &e2) : a(a) { + Index i=0,j=0; + while(i<(Index)e1.size() && j<(Index)e2.size()) { + if (e1[i]==e2[j]) { + elems.push_back(e1[i]); + i++; j++; + } else if (e1[i] &e1, const std::vector &e2, const Index rejectF) : a(a) { + Index i=0,j=0; + while(i<(Index)e1.size() && j<(Index)e2.size()) { + if (e1[i]==e2[j]) { + if (e1[i]!=rejectF) + elems.push_back(e1[i]); + i++; j++; + } else if (e1[i]a,p2.a); + this->elems.swap(p2.elems); + } + + /* comparison */ + int comp(const PosetElem &pm) const { + /* -1 if elems included in pm.elems; 0 if ==, + +1 elems includes pm.elems, +2 if not comparable */ + const std::vector &e1=elems; + const std::vector &e2=pm.elems; + std::vector::size_type i=0,j=0; + bool leq=(e1.size()<=e2.size()), geq=(e1.size()>=e2.size()); + bool same_length = (e1.size()==e2.size()); + /* leq=F,geq=T => +1 + leq=F,geq=F => 2 + leq=T,geq=F => -1 + leq=T,geq=T => 0 */ + while(ie2[j]) { + if (!leq || same_length) return 2; + geq=false; j++; + } else { i++; j++; } + } + if (i +struct PosetMaxSet : std::vector> { + + bool addElement(const T &a, std::vector &elems) { + PosetElem e(a); + std::swap(e.elems,elems); + return this->addElement(std::move(e)); + } + + /* add an element if maximal, removing lesser elements. + returns true if the element is placed. Destroy the argument */ + bool addElement(PosetElem &&elem) { + bool ismax=true; + unsigned long int k=0,l=this->size(); + while (ksize()) { + this->push_back(elem); + } else { + (*this)[l] = elem; + this->resize(l+1); + } + } else if (l!=this->size()) { /* should not happen */ + this->resize(l); + } + return ismax; + } +}; + +} From 4a5d908c300be17176a78f333a20be09ec07f776 Mon Sep 17 00:00:00 2001 From: damien-masse Date: Fri, 21 Nov 2025 08:54:47 +0100 Subject: [PATCH 14/26] Separation between polytope-clp and polytope. --- CMakeLists.txt | 15 +- .../polytope_examples/main-3Dgraphics.cpp | 36 +- examples/polytope_examples/main-3Dhull.cpp | 1 - examples/polytope_examples/main-F2V.cpp | 1 - examples/polytope_examples/main-V2F.cpp | 1 - src/CMakeLists.txt | 4 +- src/core/CMakeLists.txt | 8 + .../domains}/polytope/codac2_Facet.cpp | 66 ++- .../domains}/polytope/codac2_Facet.h | 71 ++- .../domains}/polytope/codac2_Polytope.cpp | 435 +++++++++++++++++- .../domains}/polytope/codac2_Polytope.h | 163 +++++-- .../domains}/polytope/codac2_Polytope_dd.cpp | 0 .../domains}/polytope/codac2_Polytope_dd.h | 0 .../polytope/codac2_Polytope_util.cpp | 0 .../domains}/polytope/codac2_Polytope_util.h | 0 .../domains}/polytope/codac2_PosetMax.h | 0 src/extensions/clp/CMakeLists.txt | 67 +++ .../{polytope => clp}/FindClp.cmake | 0 .../{polytope => clp}/codac-polytope.h | 0 src/extensions/clp/codac2_Polytope_clp.cpp | 0 src/extensions/clp/codac2_Polytope_clp.h | 0 .../{polytope => }/clp/codac2_clp.cpp | 3 + .../{polytope => }/clp/codac2_clp.h | 8 +- src/extensions/polytope/CMakeLists.txt | 73 --- tests/CMakeLists.txt | 8 +- .../{polytope => clp}/codac2_tests_clp.cpp | 0 26 files changed, 790 insertions(+), 170 deletions(-) rename src/{extensions => core/domains}/polytope/codac2_Facet.cpp (89%) rename src/{extensions => core/domains}/polytope/codac2_Facet.h (87%) rename src/{extensions => core/domains}/polytope/codac2_Polytope.cpp (61%) rename src/{extensions => core/domains}/polytope/codac2_Polytope.h (73%) rename src/{extensions => core/domains}/polytope/codac2_Polytope_dd.cpp (100%) rename src/{extensions => core/domains}/polytope/codac2_Polytope_dd.h (100%) rename src/{extensions => core/domains}/polytope/codac2_Polytope_util.cpp (100%) rename src/{extensions => core/domains}/polytope/codac2_Polytope_util.h (100%) rename src/{extensions => core/domains}/polytope/codac2_PosetMax.h (100%) create mode 100644 src/extensions/clp/CMakeLists.txt rename src/extensions/{polytope => clp}/FindClp.cmake (100%) rename src/extensions/{polytope => clp}/codac-polytope.h (100%) create mode 100644 src/extensions/clp/codac2_Polytope_clp.cpp create mode 100644 src/extensions/clp/codac2_Polytope_clp.h rename src/extensions/{polytope => }/clp/codac2_clp.cpp (99%) rename src/extensions/{polytope => }/clp/codac2_clp.h (98%) delete mode 100644 src/extensions/polytope/CMakeLists.txt rename tests/extensions/{polytope => clp}/codac2_tests_clp.cpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index df8788b1e..2a94f313f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -159,22 +159,15 @@ # Looking for CLP (if needed) ################################################################################ -option(WITH_POLYTOPE "Build with polytopes using CLP" OFF) +option(WITH_CLP "Use CLP for LP in polytopes" OFF) - if(WITH_POLYTOPE) - include(./src/extensions/polytope/FindClp.cmake) + if(WITH_CLP) + add_compile_definitions(WITH_CLP) + include(./src/extensions/polytope-clp/FindClp.cmake) message(STATUS "Found CLP version ${CLP_VERSION}") message(STATUS "Found CLP inclusion ${CLP_INCLUDE_DIRS}") endif() -# option(WITH_POLYTOPE "Build with polytopes using CDD library" ON) -# -# if(WITH_POLYTOPE) -# include(./src/extensions/cdd-polytope/FindCdd.cmake) -# message(STATUS "Found CDD version ${CDD_VERSION}") -# message(STATUS "Found CDD inclusion ${CDD_INCLUDE_DIRS}") -# endif() -# ################################################################################ # Compile sources ################################################################################ diff --git a/examples/polytope_examples/main-3Dgraphics.cpp b/examples/polytope_examples/main-3Dgraphics.cpp index f31f49586..c4f497192 100644 --- a/examples/polytope_examples/main-3Dgraphics.cpp +++ b/examples/polytope_examples/main-3Dgraphics.cpp @@ -2,7 +2,6 @@ // Construct different polytopes and draw them #include -#include #include #include @@ -20,6 +19,8 @@ void draw_polytope(Figure3D &fig, const Polytope &P, const StyleProperties &st) int main(int argc, char *argv[]) { +// std::cout << std::scientific << std::setprecision(20); + Figure3D fig("3D polytopes"); /* box */ @@ -39,22 +40,41 @@ int main(int argc, char *argv[]) { -0.3,0.0,0.4 }, { 0.0, -0.6, 0.8 }})); std::cout << "Parallelotop : " << paral << "\n"; - draw_polytope(fig,paral,StyleProperties(Color::dark_blue(),"parallelepiped")); + draw_polytope(fig,paral,StyleProperties(Color::dark_blue(0.6),"parallelepiped")); /* zonotope */ Polytope zono(Zonotope({-4,1,1}, {{ 0.5,0.6,0.0,0.4,0.0,1.1 }, { -0.3,0.0,0.4,-0.5,0.3,-0.3 }, { -0.2,0.0,0.3,-0.4,-0.2,-0.2 }})); std::cout << "Zonotope : " << zono << "\n"; - draw_polytope(fig,zono,StyleProperties(Color::dark_yellow(),"zonotope")); + draw_polytope(fig,zono,StyleProperties(Color::dark_yellow(0.6),"zonotope")); + + /* intersection hyperplane */ + Polytope iplan = zono.meet_with_hyperplane(2,1.5); + std::cout << "Intersection with hyperplan (iplan) : " << iplan << "\n"; + iplan.minimize_constraints(); + std::cout << "iplan (minimized) : " << iplan << "\n"; + draw_polytope(fig,iplan,StyleProperties(Color::dark_purple(),"iplan")); + /* intersection */ - zono.meet_with_polytope(paral); + Polytope zono2 = zono; + zono2.meet_with_polytope(paral); + + std::cout << "Intersection : " << zono2 << "\n"; + draw_polytope(fig,zono2,StyleProperties(Color::dark_gray(),"intersection")); + zono2.minimize_constraints(); + std::cout << "Intersection (minimized) : " << zono2 << "\n"; + + /* union */ + Polytope uni = Polytope::union_of_polytopes({ box1, box2, paral, zono }); + std::cout << "Union : " << uni << "\n"; + draw_polytope(fig,uni,StyleProperties(Color::dark_orange(0.3),"union")); - std::cout << "Intersection : " << zono << "\n"; - draw_polytope(fig,zono,StyleProperties(Color::dark_gray(),"intersection")); - zono.minimize_constraints(); - std::cout << "Intersection (minimized) : " << zono << "\n"; + /* homothety */ + uni.homothety(IntervalVector({ 0,0,-3 }), 2.0); + std::cout << "Homot : " << uni << "\n"; + draw_polytope(fig,uni,StyleProperties(Color::dark_red(0.3),"homot")); return 0; } diff --git a/examples/polytope_examples/main-3Dhull.cpp b/examples/polytope_examples/main-3Dhull.cpp index 1f4196718..782f8e682 100644 --- a/examples/polytope_examples/main-3Dhull.cpp +++ b/examples/polytope_examples/main-3Dhull.cpp @@ -2,7 +2,6 @@ // Generate random points in [-100,100] and draw the convex hull #include -#include #include #include #include diff --git a/examples/polytope_examples/main-F2V.cpp b/examples/polytope_examples/main-F2V.cpp index 51da18032..d99a11e81 100644 --- a/examples/polytope_examples/main-F2V.cpp +++ b/examples/polytope_examples/main-F2V.cpp @@ -2,7 +2,6 @@ // Transformation facets to vertex from a .ine file #include -#include #include #include #include diff --git a/examples/polytope_examples/main-V2F.cpp b/examples/polytope_examples/main-V2F.cpp index bff2a6161..04d9e3f13 100644 --- a/examples/polytope_examples/main-V2F.cpp +++ b/examples/polytope_examples/main-V2F.cpp @@ -2,7 +2,6 @@ // Transformation vertices -> facets from a .ext file #include -#include #include #include #include diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c98bbd3d7..7c00036f0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,8 +14,8 @@ add_subdirectory(extensions/capd) endif() - if(WITH_POLYTOPE) - add_subdirectory(extensions/polytope/) + if(WITH_CLP) + add_subdirectory(extensions/clp) endif() #if(WITH_PYTHON) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 9503da436..0bc9fe004 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -75,6 +75,14 @@ ${CMAKE_CURRENT_SOURCE_DIR}/domains/zonotope/codac2_Parallelepiped.cpp ${CMAKE_CURRENT_SOURCE_DIR}/domains/zonotope/codac2_Zonotope.h ${CMAKE_CURRENT_SOURCE_DIR}/domains/zonotope/codac2_Zonotope.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/domains/polytope/codac2_Facet.h + ${CMAKE_CURRENT_SOURCE_DIR}/domains/polytope/codac2_Facet.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/domains/polytope/codac2_Polytope_util.h + ${CMAKE_CURRENT_SOURCE_DIR}/domains/polytope/codac2_Polytope_util.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/domains/polytope/codac2_Polytope_dd.h + ${CMAKE_CURRENT_SOURCE_DIR}/domains/polytope/codac2_Polytope_dd.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/domains/polytope/codac2_Polytope.h + ${CMAKE_CURRENT_SOURCE_DIR}/domains/polytope/codac2_Polytope.cpp ${CMAKE_CURRENT_SOURCE_DIR}/domains/tube/codac2_Slice.h ${CMAKE_CURRENT_SOURCE_DIR}/domains/tube/codac2_SliceBase.cpp ${CMAKE_CURRENT_SOURCE_DIR}/domains/tube/codac2_SliceBase.h diff --git a/src/extensions/polytope/codac2_Facet.cpp b/src/core/domains/polytope/codac2_Facet.cpp similarity index 89% rename from src/extensions/polytope/codac2_Facet.cpp rename to src/core/domains/polytope/codac2_Facet.cpp index bb5cc91c6..484fc6e20 100644 --- a/src/extensions/polytope/codac2_Facet.cpp +++ b/src/core/domains/polytope/codac2_Facet.cpp @@ -108,7 +108,7 @@ Interval bound_linear_form(const Facet &f, } /* end of namespace Facet_ */ std::ostream& operator<<(std::ostream& os, const Facet& f) { - os << f.second.Id << " : " << f.first.row << (f.second.eqcst ? "=" : "<=" ) << f.second.rhs; + os << f.second.Id << "(" << f.first.bdim << "," << f.first.vdim << ") : " << f.first.row << (f.second.eqcst ? "=" : "<=" ) << f.second.rhs; return os; } @@ -132,7 +132,7 @@ CollectFacets::CollectFacets(const Matrix &mat, const Vector &rhsvect, for (Index i=0;i a =this->insert_facet(mat.row(i),rhsvect[i],false); if (!a.second) - throw std::domain_error("CollectFacets with duplicate rows"); + throw std::domain_error("CollectFacets construction with duplicate rows"); } for (Index i: eqSet) { _eqFacets.push_back(i); @@ -240,9 +240,11 @@ std::pair if (old_eqcst) remove_in_eqFacets(result_insert.position->second.Id-1); _allFacets[result_insert.position->second.Id-1] = endFacet(); + nb_removed_facets++; return { result_insert.position, false }; } else { - _allFacets[_eqFacets[id]] = this->_map.end(); + _allFacets[_eqFacets[id]] = endFacet(); + nb_removed_facets++; _eqFacets[id] = _eqFacets.back(); _eqFacets.pop_back(); return { this->_map.end(), false }; @@ -270,11 +272,13 @@ std::pair if (old_eqcst) remove_in_eqFacets(result_insert.position->second.Id-1); _allFacets[result_insert.position->second.Id-1] = endFacet(); + nb_removed_facets++; result_insert.position->second.Id=id; _allFacets[id-1] = result_insert.position; return { result_insert.position, false }; } else { - _allFacets[id-1] = this->_map.end(); + _allFacets[id-1] = endFacet(); + nb_removed_facets++; return { this->_map.end(), false }; } } @@ -331,11 +335,13 @@ CollectFacets::mapIterator if (old_eqcst) remove_in_eqFacets(result_insert.position->second.Id-1); _allFacets[result_insert.position->second.Id-1] = endFacet(); + nb_removed_facets++; result_insert.position->second.Id=aId+1; _allFacets[aId] = result_insert.position; ret=result_insert.position; } else { _allFacets[aId] = endFacet(); + nb_removed_facets++; ret=endFacet(); } } else { @@ -353,11 +359,11 @@ bool CollectFacets::removeFacetById(Index id) { this->remove_in_eqFacets(id-1); } _map.erase(_allFacets[id-1]); - _allFacets[id-1]=_map.end(); + _allFacets[id-1]=endFacet(); + nb_removed_facets++; return true; } - IntervalVector CollectFacets::extractBox() { assert_release(this->getDim()!=-1); /* check emptiness with null row */ @@ -367,35 +373,48 @@ IntervalVector CollectFacets::extractBox() { FacetRhs &rhs = it->second; if (rhs.rhs<0.0 || (rhs.rhs>0.0 && rhs.eqcst)) return IntervalVector::constant(this->getDim(),Interval::empty()); + _allFacets[it->second.Id-1]= endFacet(); + nb_removed_facets++; + if (rhs.eqcst) remove_in_eqFacets(it->second.Id-1); _map.erase(it); } /* check bounds if IV */ IntervalVector ret = IntervalVector(this->getDim()); for (Index i=0;igetDim();i++) { - double lbound=-oo, ubound=+oo; - row[i]=1.0; - it = _map.find(FacetBase(row)); - if (it!=_map.end()) { - FacetRhs &rhs = it->second; - ubound = rhs.rhs; - if (rhs.eqcst) lbound=rhs.rhs; - _map.erase(it); + Interval &bound = ret[i]; + std::pair pIt + = this->range_between_bases(FacetBase::base_range(this->getDim(),i, + false)); + while (pIt.first!=pIt.second && !bound.is_empty()) { + FacetRhs &rhs = pIt.first->second; + Interval val(pIt.first->first.row[i]); + if (rhs.eqcst) bound &= rhs.rhs/val; + else bound = min(bound,rhs.rhs/val); + _allFacets[pIt.first->second.Id-1]=endFacet(); + nb_removed_facets++; + if (rhs.eqcst) remove_in_eqFacets(pIt.first->second.Id-1); + pIt.first = _map.erase(pIt.first); /* pIt points to the next element */ } - row[i]=-1.0; - it = _map.find(FacetBase(row)); - if (it!=_map.end()) { - FacetRhs &rhs = it->second; - lbound = std::max(lbound,-rhs.rhs); - if (rhs.eqcst) ubound= std::min(ubound,-rhs.rhs); - _map.erase(it); + pIt = this->range_between_bases(FacetBase::base_range(this->getDim(),i, + true)); + while (pIt.first!=pIt.second && !bound.is_empty()) { + FacetRhs &rhs = pIt.first->second; + Interval val(pIt.first->first.row[i]); + if (rhs.eqcst) bound &= rhs.rhs/val; + else bound = max(bound,rhs.rhs/val); + _allFacets[pIt.first->second.Id-1]=endFacet(); + nb_removed_facets++; + if (rhs.eqcst) remove_in_eqFacets(pIt.first->second.Id-1); + pIt.first = _map.erase(pIt.first); /* pIt points to the next element */ } - ret[i] = Interval(lbound,ubound); + if (bound.is_empty()) { ret.set_empty(); break; } } return ret; } std::vector CollectFacets::renumber() { - /* renumber the constraints */ + /* renumber the constraints, only if there are removed facets */ + if (nb_removed_facets==0) return std::vector(); std::vector ret(_allFacets.size(),-1); Index sineq=0, seq=0; _allFacets.resize(_map.size()); @@ -413,6 +432,7 @@ std::vector CollectFacets::renumber() { } if (seq<(Index)_eqFacets.size()) _eqFacets.resize(seq); + nb_removed_facets=0; return ret; } diff --git a/src/extensions/polytope/codac2_Facet.h b/src/core/domains/polytope/codac2_Facet.h similarity index 87% rename from src/extensions/polytope/codac2_Facet.h rename to src/core/domains/polytope/codac2_Facet.h index 5b482059c..64583017f 100644 --- a/src/extensions/polytope/codac2_Facet.h +++ b/src/core/domains/polytope/codac2_Facet.h @@ -54,6 +54,17 @@ struct FacetBase { } FacetBase(FacetBase &&fc) : bdim(fc.bdim), vdim(fc.vdim), row(fc.row) { } + + /* create a "min" and "max" FacetBase for constraints of the form + a_i x_i <= b_i (or - a_i x_i <= b_i if neg is true */ + static std::pair + base_range(Index dim, Index i, bool neg) { + Row r = Row::zero(dim); + Index d = (i+(neg?dim:0))*(2*dim); + std::cout << i << " " << dim << " " << d << "\n"; + return std::make_pair + (FacetBase(d,-1.0,r),FacetBase(d,1.0,r)); + } inline void swap(FacetBase &fb) { this->row.swap(fb.row); @@ -92,6 +103,9 @@ struct FacetBase { } private: + + FacetBase(Index bdim, double vdim, const Row &row) : + bdim(bdim), vdim(vdim), row(row) { } void compute_key() { if (row.size()==0) return; @@ -110,7 +124,7 @@ struct FacetBase { if (val<0.0) bdim=row.size()+i; else bdim=i; } else if (val_i>val2abs) { val2abs=val_i; - if (val_i<0.0) b2dim=row.size()+i; else b2dim=i; + if (row[i]<0.0) b2dim=row.size()+i; else b2dim=i; } } if (valabs==0.0) return; /* bdim=-1 */ @@ -119,7 +133,7 @@ struct FacetBase { bdim = bdim * (2*row.size()+1) + 2*row.size() - b2dim; else bdim = bdim * (2*row.size()+1) - b2dim; - vdim = valabs/val2abs; + vdim = val2abs/valabs; } }; @@ -245,8 +259,16 @@ class CollectFacets { CollectFacets(const Matrix &mat, const Vector &rhsvect, const std::vector &eqSet); - /** create a copy of the CollectFacets */ + /** @brief create a copy of the CollectFacets, updating _allFacets + * @param cf the CollectFacets + */ CollectFacets(const CollectFacets& cf); + + /** move the elements of CollectFacets. Perform a renumbering if + * nb_removed_facets > 0 + * @param cf the CollectFacets + */ + CollectFacets(CollectFacets&& cf); /** return the dimension of the facets */ Index getDim() const; @@ -312,6 +334,13 @@ class CollectFacets { /** get the end iterator of the facet * @return an iterator on the facet */ mapCIterator endFacet() const; + /** get the first iterator + * @return an iterator on the facet */ + mapIterator beginFacet(); + /** get the first iterator + * @return an iterator on the facet */ + mapCIterator beginFacet() const; + /** change a eqfacet, keeping its Id (unless the new row creates * a duplicate) @@ -378,7 +407,7 @@ class CollectFacets { * remove some contraints, may put endFacet() for some Id */ IntervalVector extractBox(); - /** \brief renumber the set, removing spurious Id. + /** \brief renumber the set, if _allFacets have removed facets. * do not modify the iterators, but modify the indices of the * facets and equality facets. * \return the matching old Id => new Id */ @@ -417,23 +446,41 @@ class CollectFacets { mapType _map; std::vector _allFacets; std::vector _eqFacets; /* index in _allFacets */ + int nb_removed_facets=0; /* number of removed facets in _allFacets */ /* look for and remove a value in eqFacet */ void remove_in_eqFacets(Index id); /* finish an insertion process */ std::pair result_insertion(std::pair res, double rhs, bool eqcst, ACTION_DUPLICATE act); + /* return a range of iterators between two bases */ + std::pair range_between_bases + (const std::pair &bases); + }; inline CollectFacets::CollectFacets(const CollectFacets& cf) : dim(cf.getDim()), _map(cf._map), _allFacets(cf._allFacets.size(),_map.end()), - _eqFacets(cf._eqFacets) + _eqFacets(cf._eqFacets), nb_removed_facets(cf.nb_removed_facets) { /* redirect the facets */ for (mapIterator it = _map.begin(); it!=_map.end(); ++it) { _allFacets[it->second.Id-1]=it; } + /* change the end iterators */ + if (nb_removed_facets==0) return; + for (auto &v : _allFacets) { + if (v == cf._map.end()) v = _map.end(); + } +} + +inline CollectFacets::CollectFacets(CollectFacets&& cf) : + dim(cf.getDim()), _map(std::move(cf._map)), + _allFacets(std::move(cf._allFacets)), + _eqFacets(cf._eqFacets), nb_removed_facets(cf.nb_removed_facets) +{ + if (nb_removed_facets>0) this->renumber(); } inline Index CollectFacets::getDim() const { @@ -465,5 +512,19 @@ inline CollectFacets::mapIterator CollectFacets::endFacet() { inline CollectFacets::mapCIterator CollectFacets::endFacet() const { return this->_map.cend(); } +inline CollectFacets::mapIterator CollectFacets::beginFacet() { + return this->_map.begin(); +} +inline CollectFacets::mapCIterator CollectFacets::beginFacet() const { + return this->_map.cbegin(); +} + + +inline std::pair + CollectFacets::range_between_bases + (const std::pair &bases) { + return std::pair + (_map.lower_bound(bases.first),_map.lower_bound(bases.second)); +} } diff --git a/src/extensions/polytope/codac2_Polytope.cpp b/src/core/domains/polytope/codac2_Polytope.cpp similarity index 61% rename from src/extensions/polytope/codac2_Polytope.cpp rename to src/core/domains/polytope/codac2_Polytope.cpp index e3c133a84..637588429 100644 --- a/src/extensions/polytope/codac2_Polytope.cpp +++ b/src/core/domains/polytope/codac2_Polytope.cpp @@ -27,9 +27,11 @@ #include "codac2_inversion.h" #include "codac2_Parallelepiped.h" #include "codac2_Zonotope.h" -#include "codac2_clp.h" #include "codac2_Polytope.h" #include "codac2_Facet.h" +#ifdef WITH_CLP +#include "../../../extensions/polytope/clp/codac2_clp.h" +#endif using namespace codac2; @@ -69,6 +71,22 @@ Polytope::Polytope(const Polytope &P) : _dim(P._dim), _box(P._box), { } +Polytope &Polytope::operator=(const Polytope &P) { + assert(!P.state[INVALID]); + this->_dim = P._dim; + this->_box = P._box; + this->_facets = P.state[EMPTY] ? std::make_shared(_dim) : + std::make_shared(*(P._facets)); +#ifdef WITH_CLP + this->_clpForm=nullptr; +#endif + this->_DDbuildF2V=nullptr; + this->_DDbuildV2F=nullptr; + this->state = P.state & (pol_state_empty | + pol_state(1< &vertices) : Polytope() { @@ -84,6 +102,26 @@ Polytope::Polytope(const std::vector &vertices) : _box = _facets->extractBox(); _facets->encompass_vertices(vertices,_box,true); state = pol_state_init; + _facets->renumber(); + _DDbuildV2F.release(); /* encompass may have added constraints */ +} + +Polytope::Polytope(const std::vector &vertices) : + Polytope() +{ + if (vertices.empty()) return; + _dim=vertices[0].size(); + /* build the V2F form */ + _DDbuildV2F = std::make_unique(1,vertices[0].mid()); + for (Index i=1;i<(Index)vertices.size();i++) { + _DDbuildV2F->add_vertex(i+1,vertices[i].mid()); + } + /* get the CollectFacets */ + _facets=_DDbuildV2F->getFacets(); + _box = _facets->extractBox(); + _facets->encompass_vertices(vertices,_box,true); + _facets->renumber(); + state = pol_state_init; _DDbuildV2F.release(); /* encompass may have added constraints */ } @@ -101,10 +139,8 @@ Polytope::Polytope(const std::vector &vertices, for (Index i=0;i<(Index)vertices.size();i++) { _box |= vertices[i]; } - _facets=_DDbuildV2F->getFacets(); _facets->encompass_vertices(vertices,_box,true); - _box = _facets->extractBox(); - state = pol_state_init; + state = pol_state_init; } @@ -115,7 +151,7 @@ Polytope::Polytope(const IntervalVector &box, this->add_constraint(cst); } state = 0; - if (minimize) this->minimize_constraints_clp(); + if (minimize) this->minimize_constraints(); } @@ -163,6 +199,21 @@ Polytope::Polytope(const Zonotope &zon) : state = pol_state_init; } +Polytope::Polytope(CollectFacets &&facets) : _dim(facets.getDim()), state(0) { + _box = facets.extractBox(); + _facets = std::make_shared(std::move(facets)); + /* include renumbering if needed */ + state = 0; +} + +Polytope::Polytope(IntervalVector &&box, + CollectFacets &&facets) : _dim(facets.getDim()), + _box(std::move(box)), state(0) { + _box &= facets.extractBox(); + _facets = std::make_shared(std::move(facets)); +} + + Polytope Polytope::from_ineFile(const char *filename) { std::shared_ptr fcts = std::make_shared(read_ineFile(filename)); @@ -242,6 +293,7 @@ double Polytope::bound_row_F2V(const Row &r) const { return a; } +#ifdef WITH_CLP double Polytope::bound_row_clp(const Row &r) const { this->build_clpForm(); _clpForm->setObjective(r); @@ -249,13 +301,16 @@ double Polytope::bound_row_clp(const Row &r) const { if (ret[EMPTY]) return -oo; return _clpForm->getValobj().ub(); } +#endif double Polytope::bound_row(const Row &r) const { assert(r.size()==_dim); if (state[EMPTY]) return -oo; +#ifdef WITH_CLP if (!state[F2VFORM] && state[CLPFORM]) { return this->bound_row_clp(r); } +#endif return this->bound_row_F2V(r); } @@ -280,6 +335,7 @@ void Polytope::build_DDbuildF2V() const { state[F2VFORM]=true; } +#ifdef WITH_CLP void Polytope::build_clpForm() const { if (state[CLPFORM]) return; if (state[EMPTY]) { @@ -320,6 +376,7 @@ void Polytope::minimize_constraints_clp(const Interval &tolerance) const { } state[MINIMIZED]=true; } +#endif void Polytope::minimize_constraints_F2V() const { assert_release(!state[MINIMIZED]); @@ -337,8 +394,11 @@ void Polytope::minimize_constraints_F2V() const { if (changed) { std::vector corresp = _facets->renumber(); _DDbuildF2V->update_renumber(corresp); - state[CLPFORM]=false; state[V2FFORM]=false; +#ifdef WITH_CLP + state[CLPFORM]=false; _clpForm=nullptr; +#endif + state[V2FFORM]=false; } state[MINIMIZED]=true; } @@ -347,13 +407,16 @@ void Polytope::minimize_constraints() const { if (state[MINIMIZED]) return; if (state[EMPTY]) return; /* default : F2V */ +#ifdef WITH_CLP if (!state[F2VFORM] && state[CLPFORM]) { this->minimize_constraints_clp(); return; } +#endif this->minimize_constraints_F2V(); } +#ifdef WITH_CLP void Polytope::update_box_clp() const { assert_release(!state[BOXUPDATED]); this->build_clpForm(); @@ -366,6 +429,7 @@ void Polytope::update_box_clp() const { state[NOTEMPTY]=true; state[BOXUPDATED]=true; } +#endif void Polytope::update_box_F2V() const { assert_release(!state[BOXUPDATED]); @@ -379,13 +443,16 @@ void Polytope::update_box() const { if (state[BOXUPDATED]) return; if (state[EMPTY]) return; /* default : F2V */ +#ifdef WITH_CLP if (!state[F2VFORM] && state[CLPFORM]) { this->update_box_clp(); return; } +#endif this->update_box_F2V(); } +#ifdef WITH_CLP bool Polytope::check_empty_clp() const { assert_release(!state[NOTEMPTY] && !state[EMPTY]); this->build_clpForm(); @@ -397,6 +464,7 @@ bool Polytope::check_empty_clp() const { state[NOTEMPTY]=true; return false; } +#endif bool Polytope::check_empty_F2V() const { assert_release(!state[NOTEMPTY] && !state[EMPTY]); @@ -408,9 +476,11 @@ bool Polytope::check_empty() const { if (state[NOTEMPTY]) return false; if (state[EMPTY]) return true; /* default : F2V */ +#ifdef WITH_CLP if (!state[F2VFORM] && state[CLPFORM]) { return this->check_empty_clp(); } +#endif return this->check_empty_F2V(); } @@ -429,11 +499,12 @@ BoolInterval Polytope::contains(const IntervalVector& p) const { return r; } -bool Polytope::box_is_included(const IntervalVector& x) const { +bool Polytope::box_is_subset(const IntervalVector& x) const { return this->box().is_subset(x); } -bool Polytope::is_included(const Polytope& P, bool checkF2V, bool checkCLP) +bool Polytope::is_subset(const Polytope& P, bool checkF2V, + [[maybe_unused]] bool checkCLP) const { const IntervalVector &b2 = P.box(true); const IntervalVector &b1 = this->box(true); @@ -444,10 +515,12 @@ bool Polytope::is_included(const Polytope& P, bool checkF2V, bool checkCLP) double l1 = this->bound_row_F2V(fctP.first.row); if (l1<=fctP.second.rhs) continue; } +#ifdef WITH_CLP if (checkCLP) { double l1 = this->bound_row_clp(fctP.first.row); if (l1<=fctP.second.rhs) continue; } +#endif return false; } return true; @@ -527,7 +600,9 @@ void Polytope::clear() { state=pol_state_init; _box = IntervalVector::Zero(_dim); _facets=nullptr; +#ifdef WITH_CLP _clpForm=nullptr; +#endif _DDbuildF2V=nullptr; _DDbuildV2F=nullptr; } @@ -552,9 +627,11 @@ bool Polytope::add_constraint(const std::pair& facet, this->set_empty_private(); return true; } +#ifdef WITH_CLP if (state[CLPFORM]) { this->_clpForm->set_bbox(_box); } +#endif if (state[F2VFORM]) { int ret = _DDbuildF2V->add_bound_var(gdim,true,val.ub()); if (ret==-1) { @@ -569,9 +646,11 @@ bool Polytope::add_constraint(const std::pair& facet, this->set_empty_private(); return true; } +#ifdef WITH_CLP if (state[CLPFORM]) { this->_clpForm->set_bbox(_box); } +#endif if (state[F2VFORM]) { int ret = _DDbuildF2V->add_bound_var(gdim,false,-val.ub()); if (ret==-1) { @@ -590,9 +669,11 @@ bool Polytope::add_constraint(const std::pair& facet, false, CollectFacets::MIN_RHS); if (res.first==-1) { this->set_empty_private(); return true; } if (res.first==0) return false; +#ifdef WITH_CLP if (state[CLPFORM]) { this->_clpForm->updateConstraint(res.first); } +#endif if (state[F2VFORM]) { int ret = this->_DDbuildF2V->add_facet((*_facets)[res.first-1]); if (ret==-1) { @@ -630,14 +711,63 @@ std::pair Polytope::add_constraint_band(const IntervalRow &cst, return { rlb, rub }; } +int Polytope::add_equality(const std::pair& facet) { + if (state[EMPTY]) return -1; + FacetBase base(facet.first); + if (base.isNull()) { + if (facet.second==0.0) return 0; + this->set_empty(); return -1; + } + if (base.isCoord()) { + Index gdim = base.gtDim(); + Interval val = Interval(facet.second)/facet.first[gdim]; + _box[gdim] &= val; + if (_box[gdim].is_empty()) { + this->set_empty_private(); + return -1; + } +#ifdef WITH_CLP + if (state[CLPFORM]) { + this->_clpForm->set_bbox(_box); + } +#endif + state[F2VFORM]=false; + state[V2FFORM]=false; + state[MINIMIZED]=false; + state[NOTEMPTY]=false; + return 1; + } + FacetBase negBase(base); + negBase.negate_row(); + Interval actub = this->fast_bound(base); + Interval actlb = this->fast_bound(negBase); + if (actub.ub()set_empty(); return -1; } + if (actlb.ub()<-facet.second) { this->set_empty(); return -1; } + auto res= _facets->insert_facet(facet.first, facet.second, + true, CollectFacets::MIN_RHS); + if (res.first==-1) { this->set_empty_private(); return -1; } + if (res.first==0) return 0; +#ifdef WITH_CLP + if (state[CLPFORM]) { + this->_clpForm->updateConstraint(res.first); + } +#endif + state[F2VFORM]=false; + state[V2FFORM]=state[MINIMIZED]=state[BOXUPDATED]=false; + state[NOTEMPTY]=false; + return 1; +} + int Polytope::meet_with_box(const IntervalVector &b) { assert(!state[INVALID]); if (_box.is_subset(b)) return 0; if (_box.is_disjoint(b)) { this->set_empty(); return -1; } _box &= b; +#ifdef WITH_CLP if (state[CLPFORM]) { this->_clpForm->set_bbox(_box); } +#endif if (state[F2VFORM]) { if (!b.is_degenerated()) { int ret = this->_DDbuildF2V->add_constraint_box(b); @@ -660,6 +790,34 @@ Polytope &Polytope::operator&=(const IntervalVector &b) { this->meet_with_box(b); return (*this); } + +Polytope Polytope::meet_with_hyperplane(Index dm, double x) const { + assert(!state[INVALID]); + if (!_box[dm].contains(x)) { return Polytope(this->_dim,true); } + IntervalVector nbox(_box); + nbox[dm]=x; + CollectFacets cf(_dim); + for (auto &facet : _facets->get_map()) { + Row r(facet.first.row); + Interval rhs = facet.second.rhs - nbox[dm]*r[dm]; + r[dm]=0.0; + if (!facet.second.eqcst || rhs.is_degenerated()) { + std::pair ret = + cf.insert_facet(std::move(r),rhs.ub(),facet.second.eqcst, + CollectFacets::MIN_RHS); + if (ret.first==-1) return Polytope(this->_dim, true); + } else { + std::pair ret = + cf.insert_facet(r,rhs.ub(),false, + CollectFacets::MIN_RHS); + if (ret.first==-1) return Polytope(this->_dim, true); + ret = cf.insert_facet(-r,-rhs.lb(),false, + CollectFacets::MIN_RHS); + if (ret.first==-1) return Polytope(this->_dim, true); + } + } + return Polytope(std::move(nbox),std::move(cf)); +} int Polytope::meet_with_polytope(const Polytope &P) { assert_release(!P.state[INVALID]); @@ -678,14 +836,273 @@ int Polytope::meet_with_polytope(const Polytope &P) { if (ret2==0 && ret==0) return 0; state[V2FFORM]=state[MINIMIZED]=state[BOXUPDATED]=false; state[NOTEMPTY]=false; - state[F2VFORM]=state[CLPFORM]=false; + state[F2VFORM]=false; +#ifdef WITH_CLP + state[CLPFORM]=false; +#endif return 1; } +Polytope &Polytope::homothety(const IntervalVector &c, double delta) { + assert_release(!state[INVALID]); + assert(delta>0); + Interval deltaI (delta); /* for interval computation */ + if (state[EMPTY]) return (*this); + _box = (1.0-deltaI) * c + delta * _box; +#ifdef WITH_CLP + if (state[CLPFORM]) { + this->_clpForm->set_bbox(_box); + } +#endif + for (CollectFacets::mapIterator it = _facets->beginFacet() ; + it != _facets->endFacet(); ++it) { + if (it->second.eqcst) continue; + Interval nrhs = deltaI*it->second.rhs + + (1.0-deltaI)*it->first.row.dot(c); + _facets->change_ineqFacet_rhs(it,nrhs.ub()); + } + for (Index i=_facets->nbeqfcts()-1;i>=0;i--) { + CollectFacets::mapIterator it = _facets->get_eqFacet(i); + Interval nrhs = deltaI*it->second.rhs + + (1.0-deltaI)*it->first.row.dot(c); + _facets->change_ineqFacet_rhs(it,nrhs.ub()); + if (!nrhs.is_degenerated()) { + _facets->dissociate_eqFacet(i,nrhs.lb()); + } + } + state[V2FFORM]=false; + state[MINIMIZED]=false; + state[F2VFORM]=false; +#ifdef WITH_CLP + state[CLPFORM]=false; /* TODO : possible if c is punctual */ +#endif + return (*this); +} + +Polytope Polytope::reverse_affine_transform(const IntervalMatrix &M, + const IntervalVector &P, const IntervalVector &bbox) const { + assert(!state[INVALID]); + if (state[EMPTY]) return Polytope(_dim,true); + CollectFacets cf(_dim); + /* first the box */ + for (Index i=0;i<_dim;i++) { + IntervalRow rI = M.row(i); + Interval rhs = _box[i] - rI.dot(P); + Row r = rI.mid(); + rI = rI - r; + rhs -= rI.dot(bbox); + if (rhs.is_degenerated()) { + if (rhs.ub()==+oo) continue; + std::pair ret = + cf.insert_facet(std::move(r),rhs.ub(),true, + CollectFacets::MIN_RHS); + if (ret.first==-1) return Polytope(this->_dim, true); + } else { + if (rhs.ub()!=+oo) { + std::pair ret = + cf.insert_facet(r,rhs.ub(),false, + CollectFacets::MIN_RHS); + if (ret.first==-1) return Polytope(this->_dim, true); + } + if (rhs.lb()!=-oo) { + std::pair ret = + cf.insert_facet(-r,-rhs.lb(),false, + CollectFacets::MIN_RHS); + if (ret.first==-1) return Polytope(this->_dim, true); + } + } + } + for (auto &facet : _facets->get_map()) { + IntervalRow rI = facet.first.row * M; + Interval rhs = facet.second.rhs - rI.dot(P); + Row r = rI.mid(); + rI = rI - r; + rhs -= rI.dot(bbox); + if (!facet.second.eqcst || rhs.is_degenerated()) { + if (rhs.ub()==+oo) continue; + std::pair ret = + cf.insert_facet(std::move(r),rhs.ub(),facet.second.eqcst, + CollectFacets::MIN_RHS); + if (ret.first==-1) return Polytope(this->_dim, true); + } else { + if (rhs.ub()!=+oo) { + std::pair ret = + cf.insert_facet(r,rhs.ub(),false, + CollectFacets::MIN_RHS); + if (ret.first==-1) return Polytope(this->_dim, true); + } + if (rhs.lb()!=-oo) { + std::pair ret = + cf.insert_facet(-r,-rhs.lb(),false, + CollectFacets::MIN_RHS); + if (ret.first==-1) return Polytope(this->_dim, true); + } + } + } + return Polytope(IntervalVector(bbox),std::move(cf)); +} + +Polytope Polytope::bijective_affine_transform(const IntervalMatrix &M, + const IntervalMatrix &Minv, const IntervalVector &P) const { + IntervalVector M2 = M*_box+P; + return this->reverse_affine_transform(Minv,Minv*P,M2); +} + Polytope &Polytope::operator&=(const Polytope &P) { this->meet_with_polytope(P); return (*this); } + +Polytope Polytope::union_of_polytopes(std::initializer_list lst) { + if (lst.size()==0) return Polytope(); + if (lst.size()==1) return Polytope(*(lst.begin())); + Index dim = lst.begin()->dim(); + IntervalVector box = IntervalVector::constant(dim,Interval::empty()); + std::vector lvert; + for (auto &P : lst) { + std::vector tmp = + P.compute_vertices(); + lvert.insert(lvert.end(),tmp.begin(),tmp.end()); + box |= P.box(); + } + if (lvert.size()==0) { + return Polytope(dim,true); + } + Polytope ret= Polytope(lvert); + return (ret &= box); +} + +int Polytope::join_with_polytope(const Polytope &P) { + assert(!state[INVALID]); + assert(!P.state[INVALID]); + if (P.state[EMPTY]) return 0; + if (state[EMPTY]) { *this=P; return 1; } + *this=union_of_polytopes( { *this, P } ); + return 1; +} + +Polytope &Polytope::operator|=(const Polytope &P) { + assert(!state[INVALID]); + assert(!P.state[INVALID]); + if (P.state[EMPTY]) return *this; + if (state[EMPTY]) { *this=P; return *this; } + *this=union_of_polytopes( { *this, P } ); + return *this; +} + +Polytope &Polytope::inflate(double rad) { + assert(!state[INVALID]); + return (this->inflate(IntervalVector::constant(_dim,Interval(-rad,rad)))); +} + +Polytope &Polytope::inflate_ball(double rad) { + assert(!state[INVALID]); + if (state[EMPTY]) return *this; + if (rad<=0.0) return *this; + _box.inflate(rad); + /* first, we consider the inequality facets */ + for (CollectFacets::mapIterator it = _facets->beginFacet() ; + it != _facets->endFacet(); ++it) { + if (it->second.eqcst) continue; + double nrm = (it->first.row).norm()*rad; + double nrhs = it->second.rhs+nrm; + _facets->change_ineqFacet_rhs(it,nrhs); + } + /* now, we consider the equality facets */ + /* decreasing order because we "destroy" the eqFacet array */ + for (Index i=_facets->nbeqfcts()-1;i>=0;i--) { + CollectFacets::mapIterator it = _facets->get_eqFacet(i); + double nrm = (it->first.row).norm()*rad; + Interval rhs2 = it->second.rhs+Interval(-nrm,nrm); + _facets->change_ineqFacet_rhs(it,rhs2.ub()); + _facets->dissociate_eqFacet(i,rhs2.lb()); + } + /* TODO : update */ + state[V2FFORM]=false; + state[F2VFORM]=false; + _DDbuildF2V=nullptr; + _DDbuildV2F=nullptr; +#ifdef WITH_CLP + state[CLPFORM]=false; + _clpForm=nullptr; +#endif + return *this; +} + +Polytope &Polytope::inflate(const IntervalVector& box) { + assert(!state[INVALID]); + if (state[EMPTY]) return *this; + if (box.is_empty()) { this->set_empty(); return (*this); } + _box += box; + /* first, we consider the inequality facets */ + for (CollectFacets::mapIterator it = _facets->beginFacet() ; + it != _facets->endFacet(); ++it) { + if (it->second.eqcst) continue; + double nrhs = (it->second.rhs+box.dot(it->first.row)).ub(); + _facets->change_ineqFacet_rhs(it,nrhs); + } + /* now, we consider the equality facets */ + /* decreasing order because we "destroy" the eqFacet array + however, some facets are kept as equalities if there is no + expansion */ + for (Index i=_facets->nbeqfcts()-1;i>=0;i--) { + CollectFacets::mapIterator it = _facets->get_eqFacet(i); + Interval rhs2 = it->second.rhs+box.dot(it->first.row); + _facets->change_ineqFacet_rhs(it,rhs2.ub()); + if (!rhs2.is_degenerated()) + _facets->dissociate_eqFacet(i,rhs2.lb()); + } + /* TODO : update */ + state[V2FFORM]=false; + state[F2VFORM]=false; + _DDbuildF2V=nullptr; + _DDbuildV2F=nullptr; +#ifdef WITH_CLP + _clpForm=nullptr; + state[CLPFORM]=false; +#endif + return (*this); +} + + +Polytope &Polytope::unflat(Index dm, double rad) { + assert(!state[INVALID]); + if (state[EMPTY]) return *this; + if (rad<=0.0) { return *this; } + _box[dm].inflate(rad); + /* first, we consider the inequality facets */ + Interval radI(-rad,rad); /* for interval computations */ + for (CollectFacets::mapIterator it = _facets->beginFacet() ; + it != _facets->endFacet(); ++it) { + if (it->second.eqcst) continue; + if (it->first.row[dm]==0.0) continue; + double nrhs = (it->second.rhs+radI*it->first.row[dm]).ub(); + _facets->change_ineqFacet_rhs(it,nrhs); + } + /* now, we consider the equality facets */ + /* decreasing order because we "destroy" the eqFacet array + however, some facets are kept as equalities if there is no + expansion */ + for (Index i=_facets->nbeqfcts()-1;i>=0;i--) { + CollectFacets::mapIterator it = _facets->get_eqFacet(i); + if (it->first.row[dm]==0.0) continue; + Interval rhs2 = it->second.rhs+radI*it->first.row[dm]; + _facets->change_ineqFacet_rhs(it,rhs2.ub()); + if (!rhs2.is_degenerated()) + _facets->dissociate_eqFacet(i,rhs2.lb()); + } + /* TODO : update */ + state[V2FFORM]=false; + state[F2VFORM]=false; + _DDbuildF2V=nullptr; + _DDbuildV2F=nullptr; +#ifdef WITH_CLP + state[CLPFORM]=false; + _clpForm=nullptr; +#endif + return (*this); +} + std::ostream& operator<<(std::ostream& os, const Polytope &P) { diff --git a/src/extensions/polytope/codac2_Polytope.h b/src/core/domains/polytope/codac2_Polytope.h similarity index 73% rename from src/extensions/polytope/codac2_Polytope.h rename to src/core/domains/polytope/codac2_Polytope.h index 05dc70a5f..36a8d09fb 100644 --- a/src/extensions/polytope/codac2_Polytope.h +++ b/src/core/domains/polytope/codac2_Polytope.h @@ -18,6 +18,7 @@ #include #include #include +#include #include "codac2_Index.h" #include "codac2_Matrix.h" @@ -27,10 +28,12 @@ #include "codac2_IntervalVector.h" #include "codac2_IntervalMatrix.h" #include "codac2_BoolInterval.h" -#include "codac2_clp.h" #include "codac2_Polytope_util.h" #include "codac2_Facet.h" #include "codac2_Polytope_dd.h" +#ifdef WITH_CLP +#include "../../../extensions/polytope/codac2_clp.h" +#endif namespace codac2 { @@ -69,10 +72,16 @@ namespace codac2 { /** * \brief Constructs a polytope from a set of vertices - * for now, just take the bounding box of the vertices + * \param the vertices */ Polytope(const std::vector &vertices); + /** + * \brief Constructs a polytope from a set of vertices as intervalboxes + * \param the vertices + */ + Polytope(const std::vector &vertices); + /** * \brief Constructs a polytope from a set of vertices (intervalVector) * and a set of linear forms, described as a CollectFacets @@ -94,9 +103,27 @@ namespace codac2 { */ Polytope(const Zonotope &zon); + /** + * \brief Constructs a polytope from a box and a CollectFacets which + * will be moved in the polytope. Perform a "box extraction" and a + * renumbering on the collection. + * \param box the box + * \param facets the collection of facets + */ + Polytope(IntervalVector &&box, CollectFacets &&facets); + + /** + * \brief Constructs a polytope from a CollectFacets which + * will be moved in the polytope. Perform a "box extraction" and a + * renumbering on the collection. + * \param facets the collection of facets + */ + Polytope(CollectFacets &&facets); + /** * \brief Constructs a polytope from a bounding box and - * a set of constraints + * a set of constraints (inequalities) + * */ Polytope(const IntervalVector &box, const std::vector> &facets, @@ -111,11 +138,27 @@ namespace codac2 { * \param filename the file */ static Polytope from_extFile(const char *filename); + /** \brief convex union of polytopes + * use vertices to join the polytopes, as such the result may be + * too large + * \param lst a non-empty initializer_list of valid polytopes of + * same dimension + * \return the polytope */ + static Polytope union_of_polytopes(std::initializer_list lst); + /** * \brief Destructor */ ~Polytope(); + /** + * \brief copy assignment operator + * \param P copy + */ + Polytope &operator=(const Polytope &P); + + + /***************** ACCESS **************************/ /** * \brief Get the dimension of the space @@ -218,7 +261,7 @@ namespace codac2 { * \param checkCLP checks using CLP * \return true if inclusion is guaranteed */ - bool is_included(const Polytope &P, + bool is_subset(const Polytope &P, bool checkF2V=true, bool checkCLP=true) const; /** \brief intersects a box @@ -253,7 +296,7 @@ namespace codac2 { * \brief test if bounding box is included * \return true if the polytope is included in x */ - bool box_is_included(const IntervalVector& x) const; + bool box_is_subset(const IntervalVector& x) const; /** * \brief tests if the polytope is bisectable @@ -265,7 +308,7 @@ namespace codac2 { /************* Modification **********************/ - /** modification creates a new polytope **/ + /* keeping the current polytope */ /** \brief set to empty */ void set_empty(); @@ -293,13 +336,20 @@ namespace codac2 { /** \brief two inequalities with intervalVector (cst X in rhs) * using the bounding box - * do basic checks, but do not minimize the system + * do basic checks, but do not minimize the system. Also, + * should not be used for equalities (cst and rhs punctual) * \param cst the row constraint *  \param x the rhs * \return pair of booleans (one for each constraints possibly added */ std::pair add_constraint_band(const IntervalRow &cst, const Interval &rhs, double tolerance=0.0); + /** \brief add a equality (pair row X = rhs) + * do basic checks, but do not minimize the system + * \param facet the constraint + * \return -1 if empty, 0 no change (not probable), 1 change */ + int add_equality(const std::pair& facet); + /** \brief intersect with a box. * \param b the box * \return 0 if nothing done, 1 changed made, -1 results is empty */ @@ -320,34 +370,85 @@ namespace codac2 { * \return *this */ Polytope &operator&=(const Polytope &P); - /** \brief inflation by a cube + /** \brief union with a polytope + * \param P the polytope + * \return 0 if nothing done, 1 changed made, -1 results is empty */ + int join_with_polytope(const Polytope &P); + + /** \brief union with a polytope + * \param P the polytope + * \return *this */ + Polytope &operator|=(const Polytope &P); + + /** \brief inflation by a cube, keeping the shape (not optimal) * this <- this + [-rad,rad]^d * \param rad radius of the box - * \return the + * \return *this (keep minimized, except rounding) + */ + Polytope &inflate(double rad); + + /** \brief inflation with a box, keeping the shape (not optimal) + * this <- this + box + * \param box the box + * \return *this (keep minimized, except rounding) */ - Polytope inflate(double rad); + Polytope &inflate(const IntervalVector& box); - /** \brief inflation by a ball while keeping the set of facets - * this <- this + B_d(rad) + /** \brief inflation by a ball, keeping the shape (not optimal) + * this <- this + B_d(rad) (note: vector norm is computed on double) * \param rad radius of the ball - * \return *this + * \return *this (keep minimized, except rounding) */ - Polytope inflate_ball(double rad); + Polytope &inflate_ball(double rad); - /** \brief expansion of a dimension (flat or not) + /** \brief expansion of a dimension (not optimal) + * equivalent to inflation by a box 0,...,[-rad,rad],...0 * \param dm index of the dimension * \param rad radius - * \return *this */ - Polytope unflat(Index dm, double rad); + * \return *this (keep minimized, except rounding) */ + Polytope &unflat(Index dm, double rad); - /** \brief centered homothety - * x <- [c] + delta*([x]-[c]) ( (1-delta)[c] + delta*[x] ) + /** \brief centered homothety (optimal if c is punctual) + * x <- [c] + delta*(x-[c]) ( or (1-delta)[c] + delta*x ) + * note : delta must be > 0 * \param c ``center'' * \param delta expansion - * \return this + * \return *this */ - Polytope homothety(IntervalVector c, double delta); - + Polytope &homothety(const IntervalVector &c, double delta); + + /* creating a new polytope */ + + /** \brief return a polytope intersected with an hyperplan + * in a specific coordinate (rebuild the constraints without + * any dependence on this coordinate in a new polytope) + * \param dm index of the coordinate + * \param x the value of the coordinate + * \return the polytope */ + Polytope meet_with_hyperplane(Index dm, double x) const; + + /** \brief reverse affine transformation + * compute { x' in Box s.t. ([M] x' + [P]) in the initial polytope } + * The bounding box is needed to handle interval in [M]. + * \param M non-empty matrix + * \param P non-empty vector + * \param bbox bouding-box of the new polytope + * \return a new polytope */ + Polytope reverse_affine_transform(const IntervalMatrix &M, + const IntervalVector &P, const IntervalVector &bbox) const; + + /** \brief linear bijective affine transformation + * compute { [M] x + [P] s.t. x in the initial polytope } + * with MInv encloses the inverse of M + * M is used only to approximate the bounding box of the result, + * Minv is used to compute the new constraints + * \param M matrix + * \param MInv its inverse + * \param P non-empty vector + * \return a new polytope */ + Polytope bijective_affine_transform(const IntervalMatrix &M, + const IntervalMatrix &Minv, + const IntervalVector &P) const; /*********** Printing and other ``global access'' *********/ /** @@ -380,7 +481,9 @@ namespace codac2 { NOTEMPTY, /* is NOT empty */ MINIMIZED, /* minimized : redundant constraints removed */ BOXUPDATED, /* box is minimal */ +#ifdef WITH_CLP CLPFORM, /* has an up-to-date clp form */ +#endif F2VFORM, /* has an up-to-date F2V form */ V2FFORM, /* has an up-to-date V2F form */ INVALID, /* not initialised */ @@ -399,25 +502,29 @@ namespace codac2 { mutable IntervalVector _box; /* bounding box */ mutable std::shared_ptr _facets; /* "native" formulation , may be shared by other formulations */ +#ifdef WITH_CLP mutable std::unique_ptr _clpForm; /* LPclp formulation, if used */ +#endif mutable std::unique_ptr _DDbuildF2V; /* DDbuildF2V formulation, if used */ mutable std::unique_ptr _DDbuildV2F; /* DDbuildV2F formulation, generally not used */ mutable pol_state state = pol_state_init; - void minimize_constraints_clp(const Interval &tolerance=Interval(0.0)) const; void minimize_constraints_F2V() const; - void update_box_clp() const; void update_box_F2V() const; void update_box() const; - bool check_empty_clp() const; bool check_empty_F2V() const; bool check_empty() const; - double bound_row_clp(const Row &r) const; double bound_row_F2V(const Row &r) const; void build_DDbuildF2V() const; - void build_clpForm() const; void set_empty_private() const; +#ifdef WITH_CLP + void minimize_constraints_clp(const Interval &tolerance=Interval(0.0)) const; + void update_box_clp() const; + bool check_empty_clp() const; + double bound_row_clp(const Row &r) const; + void build_clpForm() const; +#endif }; @@ -441,7 +548,9 @@ inline Index Polytope::nbFacets() const { inline void Polytope::set_empty_private() const { state = pol_state_empty; _box.set_empty(); +#ifdef WITH_CLP _clpForm=nullptr; +#endif _DDbuildF2V=nullptr; _DDbuildV2F=nullptr; _facets=nullptr; diff --git a/src/extensions/polytope/codac2_Polytope_dd.cpp b/src/core/domains/polytope/codac2_Polytope_dd.cpp similarity index 100% rename from src/extensions/polytope/codac2_Polytope_dd.cpp rename to src/core/domains/polytope/codac2_Polytope_dd.cpp diff --git a/src/extensions/polytope/codac2_Polytope_dd.h b/src/core/domains/polytope/codac2_Polytope_dd.h similarity index 100% rename from src/extensions/polytope/codac2_Polytope_dd.h rename to src/core/domains/polytope/codac2_Polytope_dd.h diff --git a/src/extensions/polytope/codac2_Polytope_util.cpp b/src/core/domains/polytope/codac2_Polytope_util.cpp similarity index 100% rename from src/extensions/polytope/codac2_Polytope_util.cpp rename to src/core/domains/polytope/codac2_Polytope_util.cpp diff --git a/src/extensions/polytope/codac2_Polytope_util.h b/src/core/domains/polytope/codac2_Polytope_util.h similarity index 100% rename from src/extensions/polytope/codac2_Polytope_util.h rename to src/core/domains/polytope/codac2_Polytope_util.h diff --git a/src/extensions/polytope/codac2_PosetMax.h b/src/core/domains/polytope/codac2_PosetMax.h similarity index 100% rename from src/extensions/polytope/codac2_PosetMax.h rename to src/core/domains/polytope/codac2_PosetMax.h diff --git a/src/extensions/clp/CMakeLists.txt b/src/extensions/clp/CMakeLists.txt new file mode 100644 index 000000000..6b47b2b5c --- /dev/null +++ b/src/extensions/clp/CMakeLists.txt @@ -0,0 +1,67 @@ +# ================================================================== +# Codac - cmake configuration file +# ================================================================== + +list(APPEND CODAC_CLP_SRC + + ${CMAKE_CURRENT_SOURCE_DIR}/codac2_clp.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codac2_clp.h + ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Polytope_clp.h + ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Polytope_clp.cpp +) + +################################################################################ +# Create the target for libcodac-clp +################################################################################ + + #if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + #endif() + + add_library(${PROJECT_NAME}-clp ${CODAC_CLP_SRC}) + target_include_directories(${PROJECT_NAME}-clp PUBLIC + ${CLP_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/ + ${CMAKE_CURRENT_SOURCE_DIR}/clp + ) + target_link_libraries(${PROJECT_NAME}-clp PUBLIC ${PROJECT_NAME}-core Ibex::ibex Eigen3::Eigen ${CLP_LIBRARIES}) + + + ################################################################################ + # For the generation of the PKG file + ################################################################################ + + set(CODAC_PKG_CONFIG_CFLAGS "${CODAC_PKG_CONFIG_CFLAGS} -I\${includedir}/${PROJECT_NAME}-clp" PARENT_SCOPE) + set(CODAC_PKG_CONFIG_LIBS "${CODAC_PKG_CONFIG_LIBS} -l${PROJECT_NAME}-clp" PARENT_SCOPE) + + + ################################################################################ + # Installation of libcodac-clp files + ################################################################################ + + # Getting header files from sources + + foreach(srcfile ${CODAC_CLP_SRC}) + if(srcfile MATCHES "\\.h$" OR srcfile MATCHES "\\.hpp$") + list(APPEND CODAC_CLP_HDR ${srcfile}) + file(COPY ${srcfile} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../../include) + endif() + endforeach() + + # Generating the file codac-clp.h + + set(CODAC_CLP_MAIN_HEADER ${CMAKE_CURRENT_BINARY_DIR}/codac-clp.h) + file(WRITE ${CODAC_CLP_MAIN_HEADER} "/* This file is generated by CMake */\n\n") + file(APPEND ${CODAC_CLP_MAIN_HEADER} "#pragma once\n\n") + foreach(header_path ${CODAC_CLP_HDR}) + get_filename_component(header_name ${header_path} NAME) + file(APPEND ${CODAC_CLP_MAIN_HEADER} "#include <${header_name}>\n") + endforeach() + file(COPY ${CODAC_CLP_MAIN_HEADER} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../../include) + + # Install files in system directories + + install(TARGETS ${PROJECT_NAME}-clp DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(FILES ${CODAC_CLP_HDR} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-clp) + install(FILES ${CODAC_CLP_MAIN_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-clp) diff --git a/src/extensions/polytope/FindClp.cmake b/src/extensions/clp/FindClp.cmake similarity index 100% rename from src/extensions/polytope/FindClp.cmake rename to src/extensions/clp/FindClp.cmake diff --git a/src/extensions/polytope/codac-polytope.h b/src/extensions/clp/codac-polytope.h similarity index 100% rename from src/extensions/polytope/codac-polytope.h rename to src/extensions/clp/codac-polytope.h diff --git a/src/extensions/clp/codac2_Polytope_clp.cpp b/src/extensions/clp/codac2_Polytope_clp.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/src/extensions/clp/codac2_Polytope_clp.h b/src/extensions/clp/codac2_Polytope_clp.h new file mode 100644 index 000000000..e69de29bb diff --git a/src/extensions/polytope/clp/codac2_clp.cpp b/src/extensions/clp/codac2_clp.cpp similarity index 99% rename from src/extensions/polytope/clp/codac2_clp.cpp rename to src/extensions/clp/codac2_clp.cpp index 4790518d0..87ad805f9 100644 --- a/src/extensions/polytope/clp/codac2_clp.cpp +++ b/src/extensions/clp/codac2_clp.cpp @@ -105,6 +105,8 @@ LPclp::LPclp(Index dim) : { } +#if 0 +/* no copy constructor it shares the facet pointer */ LPclp::LPclp(const LPclp &P) : nbRows(P.nbRows), nbCols(P.nbCols), Afacets(P.Afacets), objvect(P.objvect), cststat(P.cststat), @@ -115,6 +117,7 @@ LPclp::LPclp(const LPclp &P) : tolerance(P.tolerance) { } +#endif LPclp::~LPclp() { delete(model); diff --git a/src/extensions/polytope/clp/codac2_clp.h b/src/extensions/clp/codac2_clp.h similarity index 98% rename from src/extensions/polytope/clp/codac2_clp.h rename to src/extensions/clp/codac2_clp.h index 5012ddf22..7b624ed24 100644 --- a/src/extensions/polytope/clp/codac2_clp.h +++ b/src/extensions/clp/codac2_clp.h @@ -95,12 +95,10 @@ class LPclp { LPclp (const Matrix &mat, const Vector &rhsvect, const std::vector &eqSet=std::vector()); - /** \brief Copy constructor - * Do not build the model, even if the original model is built - * also do not copy all results related to the last computation : - * status, row_basis, valObj, primalSol, primalRay, dualSol, dualRay + /** \brief Copy constructor is deleted, to avoir sharing the facets. + * use the constructor if needed. */ - LPclp (const LPclp& P); + LPclp (const LPclp& P) = delete; /** \brief Empty constructor * \param dim number of variables diff --git a/src/extensions/polytope/CMakeLists.txt b/src/extensions/polytope/CMakeLists.txt deleted file mode 100644 index 86984a140..000000000 --- a/src/extensions/polytope/CMakeLists.txt +++ /dev/null @@ -1,73 +0,0 @@ -# ================================================================== -# Codac - cmake configuration file -# ================================================================== - -list(APPEND CODAC_POLYTOPE_SRC - - ${CMAKE_CURRENT_SOURCE_DIR}/clp/codac2_clp.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/clp/codac2_clp.h - ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Facet.h - ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Facet.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Polytope_dd.h - ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Polytope_dd.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Polytope.h - ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Polytope.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Polytope_util.h - ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Polytope_util.cpp -) - -################################################################################ -# Create the target for libcodac-polytope -################################################################################ - - #if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 20) - set(CMAKE_CXX_STANDARD_REQUIRED ON) - #endif() - - add_library(${PROJECT_NAME}-polytope ${CODAC_POLYTOPE_SRC}) - target_include_directories(${PROJECT_NAME}-polytope PUBLIC - ${CLP_INCLUDE_DIRS} - ${CMAKE_CURRENT_SOURCE_DIR}/ - ${CMAKE_CURRENT_SOURCE_DIR}/clp - ) - target_link_libraries(${PROJECT_NAME}-polytope PUBLIC ${PROJECT_NAME}-core Ibex::ibex Eigen3::Eigen ${CLP_LIBRARIES}) - - - ################################################################################ - # For the generation of the PKG file - ################################################################################ - - set(CODAC_PKG_CONFIG_CFLAGS "${CODAC_PKG_CONFIG_CFLAGS} -I\${includedir}/${PROJECT_NAME}-polytope" PARENT_SCOPE) - set(CODAC_PKG_CONFIG_LIBS "${CODAC_PKG_CONFIG_LIBS} -l${PROJECT_NAME}-polytope" PARENT_SCOPE) - - - ################################################################################ - # Installation of libcodac-polytope files - ################################################################################ - - # Getting header files from sources - - foreach(srcfile ${CODAC_POLYTOPE_SRC}) - if(srcfile MATCHES "\\.h$" OR srcfile MATCHES "\\.hpp$") - list(APPEND CODAC_POLYTOPE_HDR ${srcfile}) - file(COPY ${srcfile} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../../include) - endif() - endforeach() - - # Generating the file codac-polytope.h - - set(CODAC_POLYTOPE_MAIN_HEADER ${CMAKE_CURRENT_BINARY_DIR}/codac-polytope.h) - file(WRITE ${CODAC_POLYTOPE_MAIN_HEADER} "/* This file is generated by CMake */\n\n") - file(APPEND ${CODAC_POLYTOPE_MAIN_HEADER} "#pragma once\n\n") - foreach(header_path ${CODAC_POLYTOPE_HDR}) - get_filename_component(header_name ${header_path} NAME) - file(APPEND ${CODAC_POLYTOPE_MAIN_HEADER} "#include <${header_name}>\n") - endforeach() - file(COPY ${CODAC_POLYTOPE_MAIN_HEADER} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../../include) - - # Install files in system directories - - install(TARGETS ${PROJECT_NAME}-polytope DESTINATION ${CMAKE_INSTALL_LIBDIR}) - install(FILES ${CODAC_POLYTOPE_HDR} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-polytope) - install(FILES ${CODAC_POLYTOPE_MAIN_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-polytope) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 67044bd7f..a7d12a431 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -125,12 +125,12 @@ if (WITH_CAPD) set(CODAC_LIBRARIES ${CODAC_LIBRARIES} ${PROJECT_NAME}-capd capd::capd) endif() -# POLYTOPE test -if (WITH_POLYTOPE) +# CLP test +if (WITH_CLP) list(APPEND SRC_TESTS - extensions/polytope/codac2_tests_clp + extensions/clp/codac2_tests_clp ) - set(CODAC_LIBRARIES ${CODAC_LIBRARIES} ${PROJECT_NAME}-polytope ${CLP_LIBRARIES}) + set(CODAC_LIBRARIES ${CODAC_LIBRARIES} ${PROJECT_NAME}-clp ${CLP_LIBRARIES}) endif() # IBEX test diff --git a/tests/extensions/polytope/codac2_tests_clp.cpp b/tests/extensions/clp/codac2_tests_clp.cpp similarity index 100% rename from tests/extensions/polytope/codac2_tests_clp.cpp rename to tests/extensions/clp/codac2_tests_clp.cpp From ca54be1417815bf2e6b817f97e17f2978662cbe0 Mon Sep 17 00:00:00 2001 From: damien-masse Date: Fri, 21 Nov 2025 21:35:21 +0100 Subject: [PATCH 15/26] Python bindings (partial), python tests. Manual (preliminary version). --- doc/manual/manual/geometry/index.rst | 3 +- doc/manual/manual/geometry/polytope.rst | 33 ++++ python/src/core/CMakeLists.txt | 2 + python/src/core/codac2_py_core.cpp | 6 +- .../domains/polytope/codac2_py_Polytope.cpp | 158 ++++++++++++++++++ python/src/core/matrices/codac2_py_Row.cpp | 100 +++++++++++ src/core/domains/polytope/codac2_Facet.h | 12 +- src/core/domains/polytope/codac2_Polytope.cpp | 109 +++--------- src/core/domains/polytope/codac2_Polytope.h | 32 +++- .../domains/polytope/codac2_Polytope_dd.h | 52 ++---- .../domains/polytope/codac2_Polytope_util.h | 13 +- src/core/domains/polytope/codac2_PosetMax.h | 51 +++++- src/extensions/clp/codac-polytope.h | 5 - src/extensions/clp/codac2_Polytope_clp.cpp | 115 +++++++++++++ src/extensions/clp/codac2_clp.h | 9 +- tests/CMakeLists.txt | 1 + .../polytope/codac2_tests_Polytope.cpp | 99 +++++++++++ .../domains/polytope/codac2_tests_Polytope.py | 88 ++++++++++ 18 files changed, 726 insertions(+), 162 deletions(-) create mode 100644 doc/manual/manual/geometry/polytope.rst create mode 100644 python/src/core/domains/polytope/codac2_py_Polytope.cpp create mode 100644 python/src/core/matrices/codac2_py_Row.cpp delete mode 100644 src/extensions/clp/codac-polytope.h create mode 100644 tests/core/domains/polytope/codac2_tests_Polytope.cpp create mode 100644 tests/core/domains/polytope/codac2_tests_Polytope.py diff --git a/doc/manual/manual/geometry/index.rst b/doc/manual/manual/geometry/index.rst index 8e998195c..eb328afbc 100644 --- a/doc/manual/manual/geometry/index.rst +++ b/doc/manual/manual/geometry/index.rst @@ -3,7 +3,7 @@ Geometry ======== -Codac provides a set of utility functions for basic 2d geometric calculations. +Codac provides a set of utility functions for basic geometric calculations. Several basic geometric types are available under the form of classes representing enclosures of these types. For instance, a 2d point if represented by an ``IntervalVector`` enclosing it, a segment between two 2d points is implemented by the ``Segment`` class, the endpoints of which are ``IntervalVector`` objects. Furthermore, ``Polygon`` and ``ConvexPolygon`` are also available. Operations between these structure will reliably meet the uncertainties associated with the enclosure of points (vertices), as well as floating-point calculations. @@ -16,6 +16,7 @@ Because computations are based on interval arithmetic, all these functions provi segment.rst polygon.rst zonotope.rst + polytope.rst Related interval enumerations ----------------------------- diff --git a/doc/manual/manual/geometry/polytope.rst b/doc/manual/manual/geometry/polytope.rst new file mode 100644 index 000000000..84ba93c5c --- /dev/null +++ b/doc/manual/manual/geometry/polytope.rst @@ -0,0 +1,33 @@ +.. _sec-zonotope: + +Polytope +======== + + Main author: `Damien Massé `_ + +The Polytope class represent convex polytopes (or, more generally, +convex polyhedra). +Polytopes are internally represented as intersections of linear constraints +(and a bounding box), in a class name CollectFacets. +A double-description algorithm is used to compute generators for operations +requiring them (union, projections...). Due to the imprecise nature of +floating-point computations, the algorithm is designed such that the +convex hull of the "generators" encloses the polytope. However, no guarantee +is given that each vertice of the polytope is associated to a generator. +As a result, +going back to the hyperplane representation may return a larger polytope. + +Polytope +-------- + +.. doxygenclass:: codac2::Polytope + :project: codac + :members: + +CollectFacets +------------- + +.. doxygenclass:: codac2::CollectFacets + :project: codac + :members: + diff --git a/python/src/core/CMakeLists.txt b/python/src/core/CMakeLists.txt index e3f0c330c..940784a60 100644 --- a/python/src/core/CMakeLists.txt +++ b/python/src/core/CMakeLists.txt @@ -52,6 +52,7 @@ domains/paving/codac2_py_Subpaving.cpp domains/zonotope/codac2_py_Parallelepiped.cpp domains/zonotope/codac2_py_Zonotope.cpp + domains/polytope/codac2_py_Polytope.cpp domains/tube/codac2_py_Slice.h domains/tube/codac2_py_SlicedTube.h domains/tube/codac2_py_TDomain.cpp @@ -82,6 +83,7 @@ matrices/codac2_py_MatrixBase.h matrices/codac2_py_MatrixBlock.h matrices/codac2_py_Vector.cpp + matrices/codac2_py_Row.cpp matrices/codac2_py_VectorBase.h operators/codac2_py_operators.cpp diff --git a/python/src/core/codac2_py_core.cpp b/python/src/core/codac2_py_core.cpp index d97769485..d15c21e1e 100644 --- a/python/src/core/codac2_py_core.cpp +++ b/python/src/core/codac2_py_core.cpp @@ -72,6 +72,7 @@ void export_PavingNode(py::module& m); void export_Subpaving(py::module& m); void export_Zonotope(py::module& m); void export_Parallelepiped(py::module& m); +void export_Polytope(py::module& m); void export_TDomain(py::module& m); void export_TimePropag(py::module& m); void export_TSlice(py::module& m); @@ -199,7 +200,8 @@ PYBIND11_MODULE(_core, m) // matrices export_cart_prod(m); - py::class_ exported_row_class(m, "Row", DOC_TO_BE_DEFINED); +// py::class_ exported_row_class(m, "Row", DOC_TO_BE_DEFINED); + export_Row(m); auto py_V = export_Vector(m); auto py_M = export_Matrix(m); auto py_B = export_EigenBlock(m, "MatrixBlock"); @@ -244,6 +246,8 @@ PYBIND11_MODULE(_core, m) export_Zonotope(m); export_Parallelepiped(m); + export_Polytope(m); + // function py::enum_(m, "EvalMode") .value("NATURAL", EvalMode::NATURAL) diff --git a/python/src/core/domains/polytope/codac2_py_Polytope.cpp b/python/src/core/domains/polytope/codac2_py_Polytope.cpp new file mode 100644 index 000000000..3c581e9be --- /dev/null +++ b/python/src/core/domains/polytope/codac2_py_Polytope.cpp @@ -0,0 +1,158 @@ +/** + * Codac binding (core) + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2024 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include +#include +#include +#include +#include "codac2_py_Polytope_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py): +#include "codac2_py_cast.h" + +using namespace std; +using namespace codac2; +namespace py = pybind11; +using namespace pybind11::literals; + +void export_Polytope(py::module& m) +{ + py::class_ + exported(m, "Polytope", POLYTOPE_MAIN); + exported + + .def(py::init<>(), + POLYTOPE_POLYTOPE) + + .def(py::init(), + POLYTOPE_POLYTOPE_INDEX,"dim"_a) + + .def(py::init(), + POLYTOPE_POLYTOPE_INDEX_BOOL, + "dim"_a,"empty"_a) + + .def(py::init(), + POLYTOPE_POLYTOPE_CONST_INTERVALVECTOR_REF, + "box"_a) + + .def(py::init(), + POLYTOPE_POLYTOPE_CONST_POLYTOPE_REF, + "P"_a) + + .def(py::init &>(), + POLYTOPE_POLYTOPE_CONST_VECTOR_VECTOR_REF, + "vertices"_a) + + .def(py::init &>(), + POLYTOPE_POLYTOPE_CONST_VECTOR_INTERVALVECTOR_REF, + "vertices"_a) + + .def(py::init &>(), + POLYTOPE_POLYTOPE_CONST_VECTOR_INTERVALVECTOR_REF, + "vertices"_a) + + .def(py::init(), + POLYTOPE_POLYTOPE_CONST_PARALLELEPIPED_REF, + "par"_a) + + .def(py::init(), + POLYTOPE_POLYTOPE_CONST_ZONOTOPE_REF, + "zon"_a) + + .def(py::init> &, bool>(), + POLYTOPE_POLYTOPE_CONST_INTERVALVECTOR_REF_CONST_VECTOR_PAIR_ROWDOUBLE_REF_BOOL, + "box"_a,"facets"_a,"minimize"_a) + + .def("assign",[](Polytope &Q, const Polytope &P) { Q=P; return Q; }, + POLYTOPE_REF_POLYTOPE_OPERATORAFF_CONST_POLYTOPE_REF, "P"_a) + + .def("dim",&Polytope::dim, + INDEX_POLYTOPE_DIM_CONST) + + .def("dim",&Polytope::size, + INDEX_POLYTOPE_SIZE_CONST) + + .def("nbFacets",&Polytope::nbFacets, + INDEX_POLYTOPE_NBFACETS_CONST) + +// .def("nbEqFacets",&Polytope::nbEqFacets, +// INDEX_POLYTOPE_NBEQFACETS_CONST) + + .def("is_empty",[](const Polytope &P) { return P.is_empty(true); }, + BOOL_POLYTOPE_IS_EMPTY_BOOL_CONST) + + .def("is_flat",&Polytope::is_flat, + BOOL_POLYTOPE_IS_FLAT_CONST) + + .def("bound_row",&Polytope::bound_row, + BOOL_POLYTOPE_IS_FLAT_CONST, "r"_a) + + .def("contains",&Polytope::contains, + BOOLINTERVAL_POLYTOPE_CONTAINS_CONST_INTERVALVECTOR_REF_CONST, "p"_a) + + .def("is_subset",[](const Polytope &Q, const Polytope &P) + { return Q.is_subset(P,true,true); }, + BOOL_POLYTOPE_IS_SUBSET_CONST_POLYTOPE_REF_BOOL_BOOL_CONST, "P"_a) + + .def("box",[](const Polytope &Q) + { return Q.box(true); }, + CONST_INTERVALVECTOR_REF_POLYTOPE_BOX_BOOL_CONST) + + .def("meet_with_polytope",&Polytope::meet_with_polytope, + INT_POLYTOPE_MEET_WITH_POLYTOPE_CONST_POLYTOPE_REF,"P"_a) + + .def("inflate",[](Polytope &Q, double rad) { return Q.inflate(rad); }, + POLYTOPE_REF_POLYTOPE_INFLATE_DOUBLE,"rad"_a) + + .def("inflate",[](Polytope &Q, IntervalVector &box) + { return Q.inflate(box); }, + POLYTOPE_REF_POLYTOPE_INFLATE_CONST_INTERVALVECTOR_REF, "box"_a) + + .def("inflate_ball",[](Polytope &Q, double rad) + { return Q.inflate_ball(rad); }, + POLYTOPE_REF_POLYTOPE_INFLATE_BALL_DOUBLE,"rad"_a) + + .def("unflat",&Polytope::unflat, + POLYTOPE_REF_POLYTOPE_UNFLAT_INDEX_DOUBLE,"dm"_a,"rad"_a) + + .def("homothety",&Polytope::homothety, + POLYTOPE_REF_POLYTOPE_HOMOTHETY_CONST_INTERVALVECTOR_REF_DOUBLE, + "c"_a,"delta"_a) + + .def("reverse_affine_transform",&Polytope::reverse_affine_transform, + POLYTOPE_POLYTOPE_REVERSE_AFFINE_TRANSFORM_CONST_INTERVALMATRIX_REF_CONST_INTERVALVECTOR_REF_CONST_INTERVALVECTOR_REF_CONST, + "M"_a,"P"_a,"bbox"_a) + + .def("bijective_affine_transform",&Polytope::bijective_affine_transform, + POLYTOPE_POLYTOPE_BIJECTIVE_AFFINE_TRANSFORM_CONST_INTERVALMATRIX_REF_CONST_INTERVALMATRIX_REF_CONST_INTERVALVECTOR_REF_CONST, + "M"_a,"Minv"_a,"P"_a) + + .def("compute_vertices",&Polytope::compute_vertices, + VECTOR_INTERVALVECTOR_POLYTOPE_COMPUTE_VERTICES_CONST) + + .def("compute_3Dfacets",&Polytope::compute_3Dfacets, + VECTOR_VECTOR_VECTOR_POLYTOPE_COMPUTE_3DFACETS_CONST) + + .def_static("from_ineFile",&Polytope::from_ineFile, + STATIC_POLYTOPE_POLYTOPE_FROM_INEFILE_CONST_CHAR_PTR,"filename"_a) + + .def_static("from_extFile",&Polytope::from_extFile, + STATIC_POLYTOPE_POLYTOPE_FROM_EXTFILE_CONST_CHAR_PTR,"filename"_a) + + .def_static("union_of_polytopes", [](const std::vector &lst) + { return Polytope::union_of_polytopes(lst); }, + STATIC_POLYTOPE_POLYTOPE_UNION_OF_POLYTOPES_CONST_VECTOR_POLYTOPE_REF, + "lst"_a) + + .def("__repr__", [](const Polytope& P) { + std::ostringstream stream; + stream << P; + return string(stream.str()); + }, + OSTREAM_REF_OPERATOROUT_OSTREAM_REF_CONST_POLYTOPE_REF) + ; +} diff --git a/python/src/core/matrices/codac2_py_Row.cpp b/python/src/core/matrices/codac2_py_Row.cpp new file mode 100644 index 000000000..481a01206 --- /dev/null +++ b/python/src/core/matrices/codac2_py_Row.cpp @@ -0,0 +1,100 @@ +/** + * Row binding, from Vector bindings + * ---------------------------------------------------------------------------- + * \date 2024 + * \author Simon Rohou, Damien Massé + * \copyright Copyright 2024 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include +#include +#include +#include +#include +#include + +#include "codac2_py_doc.h" +#include "codac2_py_Matrix_addons_Base_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py) +//#include "codac2_py_Matrix_addons_IntervalMatrix_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py) +#include "codac2_py_Matrix_addons_IntervalMatrixBase_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py) +#include "codac2_py_Matrix_addons_IntervalVector_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py) +//#include "codac2_py_Matrix_addons_Matrix_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py) +#include "codac2_py_Matrix_addons_MatrixBase_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py) +#include "codac2_py_Matrix_addons_Vector_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py) +#include "codac2_py_Matrix_addons_VectorBase_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py) +#include "codac2_py_MatrixBase_addons_Base_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py) +//#include "codac2_py_MatrixBase_addons_IntervalMatrix_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py) +#include "codac2_py_MatrixBase_addons_IntervalMatrixBase_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py) +#include "codac2_py_MatrixBase_addons_IntervalVector_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py) +//#include "codac2_py_MatrixBase_addons_Matrix_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py) +//#include "codac2_py_MatrixBase_addons_MatrixBase_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py) +#include "codac2_py_MatrixBase_addons_Vector_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py) +#include "codac2_py_MatrixBase_addons_VectorBase_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py) +#include "codac2_py_matrices_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py) +#include "codac2_py_Row_docs.h" + +#include "codac2_py_VectorBase.h" + +using namespace std; +using namespace codac2; +namespace py = pybind11; +using namespace pybind11::literals; + +py::class_ export_Row(py::module& m) +{ + py::class_ exported_row_class(m, "Row", DOC_TO_BE_DEFINED); + export_MatrixBase(m, exported_row_class); + export_VectorBase(m, exported_row_class); + + exported_row_class + + .def(py::init( + [](Index_type n) + { + matlab::test_integer(n); + return std::make_unique((int)n); + }), + DOC_TO_BE_DEFINED, + "n"_a) + + .def(py::init(), + "x"_a) + + .def(py::init( // this constructor must be the last one to be declared + [](const std::vector& v_) + { + auto v = std::make_unique(v_.size()); + for(size_t i = 0 ; i < v_.size() ; i++) + (*v)[i] = v_[i]; + return v; + }), + MATRIX_ADDONS_VECTOR_MATRIX_INITIALIZER_LIST_DOUBLE, + "v"_a) + + .def("min_coeff_index", [](const Row& x) + { + return matlab::output_index(x.min_coeff_index()); + }, + MATRIXBASE_ADDONS_VECTOR_INDEX_MIN_COEFF_INDEX_CONST) + + .def("max_coeff_index", [](const Row& x) + { + return matlab::output_index(x.max_coeff_index()); + }, + MATRIXBASE_ADDONS_VECTOR_INDEX_MAX_COEFF_INDEX_CONST) + + .def("__repr__", [](const Row& x) + { + std::ostringstream s; + s << x; + return string(s.str()); + }, + DOC_TO_BE_DEFINED) + + ; + + py::implicitly_convertible(); + + return exported_row_class; +} diff --git a/src/core/domains/polytope/codac2_Facet.h b/src/core/domains/polytope/codac2_Facet.h index 64583017f..740edbfb1 100644 --- a/src/core/domains/polytope/codac2_Facet.h +++ b/src/core/domains/polytope/codac2_Facet.h @@ -61,7 +61,6 @@ struct FacetBase { base_range(Index dim, Index i, bool neg) { Row r = Row::zero(dim); Index d = (i+(neg?dim:0))*(2*dim); - std::cout << i << " " << dim << " " << d << "\n"; return std::make_pair (FacetBase(d,-1.0,r),FacetBase(d,1.0,r)); } @@ -254,8 +253,8 @@ class CollectFacets { /** generate the set of facets from a matrix and a vector, i.e. * mat X <= rhs (eqSet states which rows of mat are equalities) * @param mat the matrix - * @rhsvect the vector of rhs - * @eqSet the set of equalities */ + * @param rhsvect the vector of rhs + * @param eqSet the set of equalities */ CollectFacets(const Matrix &mat, const Vector &rhsvect, const std::vector &eqSet); @@ -355,9 +354,9 @@ class CollectFacets { double nrhs, ACTION_DUPLICATE act=KEEP_RHS); /** change a facet, keeping its Id (unless the new row creates * a duplicate) - * @param Id (the Id of the facet starting from 1) - * @param nrow (the new row of the facet) - * @param nrhs (the new right-hand side) + * @param id the id of the facet starting from 1 + * @param nrow the new row of the facet + * @param nrhs the new right-hand side * @param act action if duplicate * @return an iterator on the new facet, true if the modified facet * has been inserted, false if there was a duplicate. In this @@ -378,6 +377,7 @@ class CollectFacets { if nbound = -oo or +oo, do not modify/add the new facet @param eqId the equality Id of the equality facet @param nbound the new bound + @param act action if duplicate @return depends on the construction nbound=-oo => current facet iterator -oo new facet iterator (for nbound), or endFacet if diff --git a/src/core/domains/polytope/codac2_Polytope.cpp b/src/core/domains/polytope/codac2_Polytope.cpp index 637588429..ebc3df80d 100644 --- a/src/core/domains/polytope/codac2_Polytope.cpp +++ b/src/core/domains/polytope/codac2_Polytope.cpp @@ -30,7 +30,7 @@ #include "codac2_Polytope.h" #include "codac2_Facet.h" #ifdef WITH_CLP -#include "../../../extensions/polytope/clp/codac2_clp.h" +#include "../../../extensions/clp/codac2_clp.h" #endif using namespace codac2; @@ -241,7 +241,7 @@ Interval Polytope::fast_bound(const FacetBase &base) const { if (base.row[gdim]>0.0) { res= Interval(_box[gdim].ub())*base.row[gdim]; } else { - res= Interval(-_box[gdim].lb())*base.row[gdim]; + res= Interval(_box[gdim].lb())*base.row[gdim]; } if (!base.isCoord()) { Row bcopy = base.row; @@ -293,16 +293,6 @@ double Polytope::bound_row_F2V(const Row &r) const { return a; } -#ifdef WITH_CLP -double Polytope::bound_row_clp(const Row &r) const { - this->build_clpForm(); - _clpForm->setObjective(r); - LPclp::lp_result_stat ret = _clpForm->solve(); - if (ret[EMPTY]) return -oo; - return _clpForm->getValobj().ub(); -} -#endif - double Polytope::bound_row(const Row &r) const { assert(r.size()==_dim); if (state[EMPTY]) return -oo; @@ -335,49 +325,6 @@ void Polytope::build_DDbuildF2V() const { state[F2VFORM]=true; } -#ifdef WITH_CLP -void Polytope::build_clpForm() const { - if (state[CLPFORM]) return; - if (state[EMPTY]) { - _clpForm=nullptr; - return; - } - _clpForm = std::make_unique(_dim,_facets,_box); - state[CLPFORM]=true; -} - -void Polytope::minimize_constraints_clp(const Interval &tolerance) const { - assert_release(!state[MINIMIZED]); - this->build_clpForm(); - int ret = _clpForm->minimize_polytope(tolerance, false, !state[BOXUPDATED]); - if (ret==-1) { - this->set_empty_private(); - return; - } - state[NOTEMPTY]=true; - if (!state[BOXUPDATED]) { - _box &= _clpForm->get_bbox(); - state[BOXUPDATED]=true; - } - bool changed=false; - for (Index i=0;i<_facets->nbfcts();i++) { - if (_clpForm->isRedundant(i)) { - _facets->removeFacetById(i+1); - changed=true; - } - } - if (changed) { - std::vector corresp = _facets->renumber(); - if (state[F2VFORM]) { - _DDbuildF2V->update_renumber(corresp); - } - state[CLPFORM]=false; state[V2FFORM]=false; - _clpForm=nullptr; - } - state[MINIMIZED]=true; -} -#endif - void Polytope::minimize_constraints_F2V() const { assert_release(!state[MINIMIZED]); this->build_DDbuildF2V(); @@ -416,21 +363,6 @@ void Polytope::minimize_constraints() const { this->minimize_constraints_F2V(); } -#ifdef WITH_CLP -void Polytope::update_box_clp() const { - assert_release(!state[BOXUPDATED]); - this->build_clpForm(); - int ret = _clpForm->minimize_box(); - if (ret==-1) { - this->set_empty_private(); - return; - } - _box &= _clpForm->get_bbox(); - state[NOTEMPTY]=true; - state[BOXUPDATED]=true; -} -#endif - void Polytope::update_box_F2V() const { assert_release(!state[BOXUPDATED]); this->build_DDbuildF2V(); @@ -452,20 +384,6 @@ void Polytope::update_box() const { this->update_box_F2V(); } -#ifdef WITH_CLP -bool Polytope::check_empty_clp() const { - assert_release(!state[NOTEMPTY] && !state[EMPTY]); - this->build_clpForm(); - int ret = _clpForm->check_emptiness(); - if (ret==-1) { - this->set_empty_private(); - return true; - } - state[NOTEMPTY]=true; - return false; -} -#endif - bool Polytope::check_empty_F2V() const { assert_release(!state[NOTEMPTY] && !state[EMPTY]); this->build_DDbuildF2V(); @@ -887,7 +805,7 @@ Polytope Polytope::reverse_affine_transform(const IntervalMatrix &M, /* first the box */ for (Index i=0;i<_dim;i++) { IntervalRow rI = M.row(i); - Interval rhs = _box[i] - rI.dot(P); + Interval rhs = _box[i] - P[i]; Row r = rI.mid(); rI = rI - r; rhs -= rI.dot(bbox); @@ -914,7 +832,7 @@ Polytope Polytope::reverse_affine_transform(const IntervalMatrix &M, } for (auto &facet : _facets->get_map()) { IntervalRow rI = facet.first.row * M; - Interval rhs = facet.second.rhs - rI.dot(P); + Interval rhs = facet.second.rhs - facet.first.row.dot(P); Row r = rI.mid(); rI = rI - r; rhs -= rI.dot(bbox); @@ -972,6 +890,25 @@ Polytope Polytope::union_of_polytopes(std::initializer_list lst) { return (ret &= box); } +Polytope Polytope::union_of_polytopes(const std::vector &lst) { + if (lst.size()==0) return Polytope(); + if (lst.size()==1) return Polytope(*(lst.begin())); + Index dim = lst.begin()->dim(); + IntervalVector box = IntervalVector::constant(dim,Interval::empty()); + std::vector lvert; + for (auto &P : lst) { + std::vector tmp = + P.compute_vertices(); + lvert.insert(lvert.end(),tmp.begin(),tmp.end()); + box |= P.box(); + } + if (lvert.size()==0) { + return Polytope(dim,true); + } + Polytope ret= Polytope(lvert); + return (ret &= box); +} + int Polytope::join_with_polytope(const Polytope &P) { assert(!state[INVALID]); assert(!P.state[INVALID]); diff --git a/src/core/domains/polytope/codac2_Polytope.h b/src/core/domains/polytope/codac2_Polytope.h index 36a8d09fb..c2336fd49 100644 --- a/src/core/domains/polytope/codac2_Polytope.h +++ b/src/core/domains/polytope/codac2_Polytope.h @@ -31,8 +31,10 @@ #include "codac2_Polytope_util.h" #include "codac2_Facet.h" #include "codac2_Polytope_dd.h" +#include "codac2_Parallelepiped.h" +#include "codac2_Zonotope.h" #ifdef WITH_CLP -#include "../../../extensions/polytope/codac2_clp.h" +#include "../../../extensions/clp/codac2_clp.h" #endif @@ -72,13 +74,13 @@ namespace codac2 { /** * \brief Constructs a polytope from a set of vertices - * \param the vertices + * \param vertices the vertices */ Polytope(const std::vector &vertices); /** * \brief Constructs a polytope from a set of vertices as intervalboxes - * \param the vertices + * \param vertices the vertices */ Polytope(const std::vector &vertices); @@ -86,7 +88,7 @@ namespace codac2 { * \brief Constructs a polytope from a set of vertices (intervalVector) * and a set of linear forms, described as a CollectFacets * \param vertices the vertices - * \param facetforms the facets description (the CollectFacets) + * \param facetsform the facets description (the CollectFacets) */ Polytope(const std::vector &vertices, const CollectFacets &facetsform); @@ -146,6 +148,14 @@ namespace codac2 { * \return the polytope */ static Polytope union_of_polytopes(std::initializer_list lst); + /** \brief convex union of polytopes + * use vertices to join the polytopes, as such the result may be + * too large + * \param lst a non-empty initializer_list of valid polytopes of + * same dimension + * \return the polytope */ + static Polytope union_of_polytopes(const std::vector &lst); + /** * \brief Destructor */ @@ -219,7 +229,7 @@ namespace codac2 { * return an interval I : row X is guaranteed to be less than I.ub() * and I.lb gives an indication of the precision of the computation * (i.e. if diam(I) is low, a constraint close to row was used - * \param fbase the constraint, expressed as a FacetBase + * \param base the constraint, expressed as a FacetBase * \return the interval I */ Interval fast_bound(const FacetBase &base) const; @@ -271,7 +281,7 @@ namespace codac2 { BoolInterval intersects(const IntervalVector& x) const; /** \brief intersects a polytope - * \param x the polytope + * \param p the polytope * \return if the polytope intersects the box */ BoolInterval intersects(const Polytope &p) const; @@ -319,6 +329,7 @@ namespace codac2 { /** \brief add a inequality (pair row X <= rhs) * do basic checks, but do not minimize the system * \param facet the constraint + * \param tolerance tolerance for redundancy checking (CLP only) * \return false if the (basic) checks showed the constraint to * be redundant (or inside tolerance), true if added */ bool add_constraint(const std::pair& facet, @@ -328,7 +339,8 @@ namespace codac2 { * using the bounding box * do basic checks, but do not minimize the system * \param cst the row constraint - *  \param x the rhs + *  \param rhs the rhs + * \param tolerance tolerance for redundancy checking (CLP only) * \return false if the (basic) checks showed the constraint to * be redundant (or inside tolerance), true if added */ bool add_constraint(const IntervalRow &cst, double rhs, @@ -339,7 +351,8 @@ namespace codac2 { * do basic checks, but do not minimize the system. Also, * should not be used for equalities (cst and rhs punctual) * \param cst the row constraint - *  \param x the rhs + *  \param rhs the rhs + * \param tolerance tolerance for redundancy checking (CLP only) * \return pair of booleans (one for each constraints possibly added */ std::pair add_constraint_band(const IntervalRow &cst, const Interval &rhs, double tolerance=0.0); @@ -443,7 +456,7 @@ namespace codac2 { * M is used only to approximate the bounding box of the result, * Minv is used to compute the new constraints * \param M matrix - * \param MInv its inverse + * \param Minv its inverse * \param P non-empty vector * \return a new polytope */ Polytope bijective_affine_transform(const IntervalMatrix &M, @@ -561,5 +574,6 @@ inline void Polytope::set_empty() { this->set_empty_private(); } +std::ostream& operator<<(std::ostream& os, const Polytope &P); } diff --git a/src/core/domains/polytope/codac2_Polytope_dd.h b/src/core/domains/polytope/codac2_Polytope_dd.h index 1c6ed5a44..29636dbf7 100644 --- a/src/core/domains/polytope/codac2_Polytope_dd.h +++ b/src/core/domains/polytope/codac2_Polytope_dd.h @@ -1,5 +1,5 @@ /** - * \file codac2_Polytope_dd.h implementation of the dd algorithm + * \file codac2_Polytope_dd.h implementation of the dd algorithms * ---------------------------------------------------------------------------- * \date 2025 * \author Damien Massé @@ -33,9 +33,10 @@ namespace codac2 { -#if 0 -struct DDlink; /* link between two vertices */ -#endif +/** \brief multiply a vector by a power of 2 (without rounding) + * such that 2^(-10) <= norm(vec) <= 2^10 + * \param vec the vector (modified) + * \return the multiplying factor */ inline double reduce_vector_without_rounding(IntervalVector &vec) { int fx; if (frexp(vec[0].mag(),&fx)==0.0) fx=INT_MIN; @@ -53,6 +54,11 @@ inline double reduce_vector_without_rounding(IntervalVector &vec) { } return mult; } + +/** \brief multiply a row by a power of 2 (without rounding) + * such that 2^(-10) <= norm(vec) <= 2^10 + * \param vec the row (modified) + * \return the multiplying factor */ inline double reduce_row_without_rounding(Row &vec) { int fx; if (frexp(vec[0],&fx)==0.0) fx=INT_MIN; @@ -72,6 +78,7 @@ inline double reduce_row_without_rounding(Row &vec) { } +/** \brief structure for vertices in BuildF2V */ struct DDvertex { Index Id; IntervalVector vertex; @@ -153,38 +160,8 @@ struct DDvertex { }; -#if 0 -struct DDlink { - std::forward_list::iterator V1; - std::forward_list::iterator V2; -// std::unordered_set> fcts; - - DDlink(const std::forward_list::iterator &V1, - const std::forward_list::iterator &V2 - /*, const std::vector &fcts*/) : V1(V1), V2(V2) { -/* this->fcts.insert(fcts); */ - } - - void changeVertex(const std::forward_list::iterator &Vold, - const std::forward_list::iterator &Vnew) { - if (this->V1==Vold) { this->V1=Vnew; return; } - this->V2=Vnew; - } - - const std::forward_list::iterator &otherVertex - (const std::forward_list::iterator &V) const { - if (V==V1) return V2; - return V1; - } - -#if 0 - void addFct(const std::vector &fcts) { - this->fcts.insert(fcts); - } -#endif -}; -#endif - +/** \brief Structure to build an enclosing set of IntervalVector + * from a set of facets */ class DDbuildF2V { public: /* create the build with a collection of facet, but @@ -275,6 +252,7 @@ inline const std::vector &DDbuildF2V::get_reffacets() const { return this->reffacets; } +/** \brief structure for facets in BuildF2V */ struct DDfacet { CollectFacets::mapIterator facetIt; double lambda; @@ -336,6 +314,8 @@ struct DDfacet { }; +/** \brief construction of an (approximate) set of facets from a + * a set of vertices */ class DDbuildV2F { public: DDbuildV2F(Index idVertex, const Vector &vertex); diff --git a/src/core/domains/polytope/codac2_Polytope_util.h b/src/core/domains/polytope/codac2_Polytope_util.h index 1beb04d08..5a4519b77 100644 --- a/src/core/domains/polytope/codac2_Polytope_util.h +++ b/src/core/domains/polytope/codac2_Polytope_util.h @@ -35,20 +35,25 @@ namespace codac2 { /** read a .ine file and return a list of facets * this is a very crude function, which assumes the format of the - * file is correct. Equalities are not treated for now. - * @param filename name of the file */ + * file is correct. Linearities are not treated for now. + * @param filename name of the file + * @return the CollectFacets structure containing the facets */ CollectFacets read_ineFile(const char *filename); /** read a .ext file and return a list of vertices * this is a very crude function, which assumes the format of the * file is correct. Rays are not treated for now. - * @param filename name of the file */ + * @param filename name of the file + * @return a set of vertices */ std::vector read_extFile(const char *filename); /** construct the list of facets of a 3D-polyhedron from the buildF2V * structure * use the mid of each vertex. For rays/line use an arbitrary bound - * @param build the buildF2V structure */ + * @param build the buildF2V structure + * @param bound the bound for rays/line (not correctly handled) + * @return the set of set of vertices + */ std::vector> build_3Dfacets(const DDbuildF2V &build, double bound=50.0); } diff --git a/src/core/domains/polytope/codac2_PosetMax.h b/src/core/domains/polytope/codac2_PosetMax.h index 9d3c874a2..c2f443777 100644 --- a/src/core/domains/polytope/codac2_PosetMax.h +++ b/src/core/domains/polytope/codac2_PosetMax.h @@ -23,20 +23,32 @@ namespace codac2 { /*** elements ****************************************************/ /*** we may store more that set of elems to speed up computation */ + +/** \brief Pair of an element of type T and an (ordered) vector of + * Index, along with the partial (subset) order on the vector */ template struct PosetElem { T a; std::vector elems; + /** \brief empty element */ PosetElem() {} + /** \brief Element with empty list + * \param a key of type T */ PosetElem(const T &a) : a(a) { } - /* building with one list. e1 is assumed to be ordered */ + + /** \brief Construct an element (a,e1) + * \param a key + * \param e1 set of indices (supposed to be ordered) */ PosetElem(const T &a, const std::vector &e1) : a(a), elems(e1) { } - /* intersection of two lists */ + /** \brief build an element (a, e1 inter e2) + * \param a key + * \param e1 ordered set + * \param e2 ordered set */ PosetElem(const T &a, const std::vector &e1, const std::vector &e2) : a(a) { Index i=0,j=0; while(i<(Index)e1.size() && j<(Index)e2.size()) { @@ -48,7 +60,12 @@ struct PosetElem { } } - /* intersection of two lists, minus one element */ + /** \brief build an element (a, e1 inter e2 minus rejectF) + * \param a key + * \param e1 ordered set + * \param e2 ordered set + * \param rejectF element to remove from e1 inter e2 + */ PosetElem(const T &a, const std::vector &e1, const std::vector &e2, const Index rejectF) : a(a) { Index i=0,j=0; while(i<(Index)e1.size() && j<(Index)e2.size()) { @@ -61,12 +78,21 @@ struct PosetElem { } } + /** \brief swap two elements + * \param p2 the element to be swapped with (*this) + */ void swap(PosetElem &p2) { std::swap(this->a,p2.a); this->elems.swap(p2.elems); } - /* comparison */ + /** \brief comparison between the set of indices + * \param pm the element to be compared + * \return -1 if (*this).elems is included in pm.elems + * 0 if both sets are equal + * 1 if (*this).elems is a superset of pm.elems + * 2 if both sets are not comparable + */ int comp(const PosetElem &pm) const { /* -1 if elems included in pm.elems; 0 if ==, +1 elems includes pm.elems, +2 if not comparable */ @@ -100,18 +126,25 @@ struct PosetElem { } }; -/* vector of maximum elements */ +/** \brief stucture to handle sets of maximal elements */ template struct PosetMaxSet : std::vector> { + /** \brief build and add a new element + * \param a key + * \param elems the set of indices (ordered) + * \return true if the element is added, false otherwise + */ bool addElement(const T &a, std::vector &elems) { PosetElem e(a); std::swap(e.elems,elems); return this->addElement(std::move(e)); } - /* add an element if maximal, removing lesser elements. - returns true if the element is placed. Destroy the argument */ + /** \brief move in a new element (if maximal) + * \param elem the element + * \return true if the element is added, false otherwise + */ bool addElement(PosetElem &&elem) { bool ismax=true; unsigned long int k=0,l=this->size(); @@ -130,9 +163,9 @@ struct PosetMaxSet : std::vector> { } if (ismax) { if (l==this->size()) { - this->push_back(elem); + this->push_back(std::move(elem)); } else { - (*this)[l] = elem; + (*this)[l] = std::move(elem); this->resize(l+1); } } else if (l!=this->size()) { /* should not happen */ diff --git a/src/extensions/clp/codac-polytope.h b/src/extensions/clp/codac-polytope.h deleted file mode 100644 index fb91465ef..000000000 --- a/src/extensions/clp/codac-polytope.h +++ /dev/null @@ -1,5 +0,0 @@ -/* This file is generated by CMake */ - -#pragma once - -#include diff --git a/src/extensions/clp/codac2_Polytope_clp.cpp b/src/extensions/clp/codac2_Polytope_clp.cpp index e69de29bb..bd0eee9ef 100644 --- a/src/extensions/clp/codac2_Polytope_clp.cpp +++ b/src/extensions/clp/codac2_Polytope_clp.cpp @@ -0,0 +1,115 @@ +/** + * \file codac2_clp_Polytope.cpp (Polytope methods which use CLP) + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2025 + * \license This program is distributed under the terms of + * the GNU Lesser General Public License (LGPL). + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "codac2_Index.h" +#include "codac2_Matrix.h" +#include "codac2_Vector.h" +#include "codac2_Row.h" +#include "codac2_IntervalRow.h" +#include "codac2_IntervalVector.h" +#include "codac2_IntervalMatrix.h" +#include "codac2_inversion.h" +#include "codac2_Parallelepiped.h" +#include "codac2_Zonotope.h" +#include "codac2_Polytope.h" +#include "codac2_Facet.h" +#include "codac2_clp.h" + +using namespace codac2; + + +namespace codac2 { + +double Polytope::bound_row_clp(const Row &r) const { + this->build_clpForm(); + _clpForm->setObjective(r); + LPclp::lp_result_stat ret = _clpForm->solve(); + if (ret[EMPTY]) return -oo; + return _clpForm->getValobj().ub(); +} + +void Polytope::build_clpForm() const { + if (state[CLPFORM]) return; + if (state[EMPTY]) { + _clpForm=nullptr; + return; + } + _clpForm = std::make_unique(_dim,_facets,_box); + state[CLPFORM]=true; +} + +void Polytope::minimize_constraints_clp(const Interval &tolerance) const { + assert_release(!state[MINIMIZED]); + this->build_clpForm(); + int ret = _clpForm->minimize_polytope(tolerance, false, !state[BOXUPDATED]); + if (ret==-1) { + this->set_empty_private(); + return; + } + state[NOTEMPTY]=true; + if (!state[BOXUPDATED]) { + _box &= _clpForm->get_bbox(); + state[BOXUPDATED]=true; + } + bool changed=false; + for (Index i=0;i<_facets->nbfcts();i++) { + if (_clpForm->isRedundant(i)) { + _facets->removeFacetById(i+1); + changed=true; + } + } + if (changed) { + std::vector corresp = _facets->renumber(); + if (state[F2VFORM]) { + _DDbuildF2V->update_renumber(corresp); + } + state[CLPFORM]=false; state[V2FFORM]=false; + _clpForm=nullptr; + } + state[MINIMIZED]=true; +} + +void Polytope::update_box_clp() const { + assert_release(!state[BOXUPDATED]); + this->build_clpForm(); + int ret = _clpForm->minimize_box(); + if (ret==-1) { + this->set_empty_private(); + return; + } + _box &= _clpForm->get_bbox(); + state[NOTEMPTY]=true; + state[BOXUPDATED]=true; +} + +bool Polytope::check_empty_clp() const { + assert_release(!state[NOTEMPTY] && !state[EMPTY]); + this->build_clpForm(); + int ret = _clpForm->check_emptiness(); + if (ret==-1) { + this->set_empty_private(); + return true; + } + state[NOTEMPTY]=true; + return false; +} + + +} + diff --git a/src/extensions/clp/codac2_clp.h b/src/extensions/clp/codac2_clp.h index 7b624ed24..9677e7be3 100644 --- a/src/extensions/clp/codac2_clp.h +++ b/src/extensions/clp/codac2_clp.h @@ -108,12 +108,12 @@ class LPclp { /** \brief destructor */ ~LPclp(); - /** \brief set bbox - * \param bbox the new bbox + /** \brief set the bounding box + * \param box the new box */ void set_bbox(const IntervalVector &box); - /** \brief get bbox (after minimization) + /** \brief get bounding box (after minimization) * \return the bbox */ IntervalVector get_bbox(); @@ -135,8 +135,7 @@ class LPclp { * \return id if ok, or -1 if failure */ Index updateConstraint(Index id); /** \brief Set the objvective - * \param objvect the new objective - * \param rhs its RHS */ + * \param objvect the new objective */ void setObjective(const Row &objvect); /** \brief Set the ''activity'' of a constraint * inactive constraints <=> rhs put to oo diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a7d12a431..70b53e078 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -51,6 +51,7 @@ list(APPEND SRC_TESTS # listing files without extension core/domains/interval/codac2_tests_IntervalVector core/domains/zonotope/codac2_tests_Parallelepiped core/domains/zonotope/codac2_tests_Parallelepiped_eval + core/domains/polytope/codac2_tests_Polytope core/domains/tube/codac2_tests_TDomain core/domains/tube/codac2_tests_Slice core/domains/tube/codac2_tests_Slice_polygon diff --git a/tests/core/domains/polytope/codac2_tests_Polytope.cpp b/tests/core/domains/polytope/codac2_tests_Polytope.cpp new file mode 100644 index 000000000..0dfef94f2 --- /dev/null +++ b/tests/core/domains/polytope/codac2_tests_Polytope.cpp @@ -0,0 +1,99 @@ +/** + * Codac tests + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2025 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include +#include +#include + +using namespace std; +using namespace codac2; + +TEST_CASE("Polytope") +{ + /* polytope with vertices + (0,0,0) , (2,0,1) , (1,0.1,0) , (0,-0.1,1), (1,2,0.5) + inequalities + -x + 20y + 82z <= 80 x - 20y - 2z <= 0 + -20.5x + 10y +z <= 0 -x + 10y - 38z <= 0 + 3.9x + y - 3.8z <= 4 x -10y - 2z <= 0 + + "redundant" inequality + -2.1 x + 2y + 4.2z <= 4 */ + std::vector> facets + { { {-1,20,82}, 80 }, + { {-20.5,10,1} ,0 }, + { {3.9,1,-3.8} ,4 }, + { {1,-20,-2} ,0 }, + { {-1,10,-38} ,0 }, + { {1,-10,-2} ,0 } }; + + SECTION("Definition, box, contains") + { + Polytope p(IntervalVector({{-4,4},{-4,4},{-4,4}}), facets, true); + + CHECK(!p.is_empty()); + CHECK(!p.is_flat()); + CHECK(Approx(p.box(),1e-8) == IntervalVector({{0.,2.},{-0.1,2.},{0.,1.}})); + CHECK(IntervalVector({{0.,2.},{-0.1,2.},{0.,1.}}).is_subset(p.box())); + CHECK(Approx(p.bound_row(Row({-2.1,2,4.2})),1e-8)==4.0); + CHECK(Approx(p.bound_row(Row({1.0,1.0,1.0})),1e-8)==3.5); + CHECK(p.contains(IntervalVector({1.0,1.0,0.5}))==BoolInterval::TRUE); + CHECK(p.contains(IntervalVector({1.0,1.0,61.0/82.0}))==BoolInterval::UNKNOWN); + CHECK(p.contains(IntervalVector({1.0,1.0,1.0}))==BoolInterval::FALSE); + } + + SECTION("Subset") + { + Polytope p(IntervalVector({{-4,4},{-4,4},{-4,4}}), facets, true); + + Polytope q(IntervalVector({{0.2,0.4},{0.0,0.3},{0.3,0.5}})); + CHECK(q.is_subset(p)); + Polytope r(3,true); /* empty polyhedron */ + CHECK(r.is_empty()); + CHECK(r.is_subset(q)); + } + + SECTION("Transformation") + { + Polytope p(IntervalVector({{-4,4},{-4,4},{-4,4}}), facets, true); + Polytope q = p; + q.inflate(0.5); + CHECK(p.is_subset(q)); + Polytope r = p; + r.inflate_ball(0.5); + CHECK(r.is_subset(q)); + Polytope s = p; + s.unflat(2,0.5); + CHECK(s.is_subset(r)); + Polytope t = p; + t.homothety(IntervalVector({0,0,-0.5}),2); + CHECK(Approx(t.bound_row(Row({1.0,1.0,1.0})),1e-8)==7.5); + Polytope u=t; + u.meet_with_polytope(p); + CHECK(u.is_subset(t)); + CHECK(u.is_subset(p)); + Polytope v = Polytope::union_of_polytopes({ p,t }); + CHECK(p.is_subset(v)); + CHECK(t.is_subset(v)); + t.inflate(0.1); + CHECK(!t.is_subset(v)); + /* transformation : rotation of pi/3 degrees (reverse), center (1,0,0) */ + IntervalMatrix M { { cos(PI/3) , sin(PI/3) , 0 }, + { -sin(PI/3), cos(PI/3) , 0 }, + { 0 , 0 , 1 } }; + IntervalVector P { 1-cos(PI/3) , sin(PI/3) , 0 }; + Polytope w = p.reverse_affine_transform(M,P, + IntervalVector({{-4,4},{-4,4},{-4,4}})); + CHECK(Approx(w.box(),1e-5) == + IntervalVector({{1.0-2*sin(PI/3),1.5}, + {-sin(PI/3)-0.1*cos(PI/3),1.}, + {0.,1.}})); + } + +} + diff --git a/tests/core/domains/polytope/codac2_tests_Polytope.py b/tests/core/domains/polytope/codac2_tests_Polytope.py new file mode 100644 index 000000000..05cc8bf2b --- /dev/null +++ b/tests/core/domains/polytope/codac2_tests_Polytope.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python + +# Codac tests +# ---------------------------------------------------------------------------- +# \date 2025 +# \author Damien Massé +# \copyright Copyright 2024 Codac Team +# \license GNU Lesser General Public License (LGPL) + +import unittest +from codac import * +import sys +import math + + +class TestPolytope(unittest.TestCase): + facets = [[Row([-1,20,82]),80], + [Row([-20.5,10,1]),0], + [Row([3.9,1,-3.8]),4], + [Row([1,-20,-2]),0], + [Row([-1,10,-38]),0], + [Row([1,-10,-2]),0]] + +## polytope with vertices +## (0,0,0) , (2,0,1) , (1,0.1,0) , (0,-0.1,1), (1,2,0.5) +## inequalities +## -x + 20y + 82z <= 80 x - 20y - 2z <= 0 +## -20.5x + 10y +z <= 0 -x + 10y - 38z <= 0 +## 3.9x + y - 3.8z <= 4 x -10y - 2z <= 0 +## + "redundant" inequality +## -2.1 x + 2y + 4.2z <= 4 + + def test_polytope(self): + p = Polytope(IntervalVector([[-4,4],[-4,4],[-4,4]]),self.facets,True) + self.assertFalse(p.is_empty()) + self.assertFalse(p.is_flat()) + self.assertTrue(Approx(p.box(),1e-8)==IntervalVector([[0.,2.],[-0.1,2.],[0.,1.]])) + self.assertTrue(Approx(p.bound_row(Row([-2.1,2.0,4.2])),1e-8)==4.0) + self.assertTrue(Approx(p.bound_row(Row([1.0,1.0,1.0])),1e-8)==3.5) + self.assertTrue(p.contains(IntervalVector([1.0,1.0,0.5]))==BoolInterval.TRUE) + self.assertTrue(p.contains(IntervalVector([1.0,1.0,61.0/82.0]))==BoolInterval.TRUE) + self.assertTrue(p.contains(IntervalVector([1.0,1.0,1.0]))==BoolInterval.FALSE) + + def test_polytope_subset(self): + p = Polytope(IntervalVector([[-4,4],[-4,4],[-4,4]]),self.facets,True) + q = Polytope(IntervalVector([[0.2,0.4],[0.0,0.3],[0.3,0.5]])) + self.assertTrue(q.is_subset(p)) + r = Polytope(3,True) # empty polyhedron + self.assertTrue(r.is_empty()) + self.assertTrue(r.is_subset(q)) + + def test_polytope_transformation(self): + p = Polytope(IntervalVector([[-4,4],[-4,4],[-4,4]]),self.facets,True) + q = Polytope(p) # or q = Polytope() q.assign(p) + q.inflate(0.5) + self.assertTrue(p.is_subset(q)) + r = Polytope(p) + r.inflate_ball(0.5) + self.assertTrue(r.is_subset(q)) + self.assertFalse(q.is_subset(r)) + s = Polytope(p) + s.unflat(2,0.5) + self.assertTrue(s.is_subset(r)) + t = Polytope(p) + t.homothety(IntervalVector([0,0,-0.5]),2) + self.assertTrue(Approx(t.bound_row(Row([1.0,1.0,1.0])),1e-8)==7.5) + u = Polytope(t) + u.meet_with_polytope(p) + self.assertTrue(u.is_subset(p)) + self.assertTrue(u.is_subset(t)) + v = Polytope.union_of_polytopes([ p,t ]) + self.assertTrue(p.is_subset(v)) + self.assertTrue(t.is_subset(v)) + t.inflate(0.1) + self.assertFalse(t.is_subset(v)) +## transformation : rotation of pi/3 degrees (reverse), center (1,0,0) + M = IntervalMatrix([[cos(PI/3),sin(PI/3),0],[-sin(PI/3),cos(PI/3),0], + [0,0,1]]) + P = IntervalVector([1-cos(PI/3),sin(PI/3),0]) + w = p.reverse_affine_transform(M,P,IntervalVector([[-4,4],[-4,4],[-4,4]])) + self.assertTrue(Approx(w.box(),1e-8)== + IntervalVector([[1.0-2*math.sin(PI/3),1.5], + [-math.sin(PI/3)-0.1*math.cos(PI/3),1], + [0,1]])) + + +if __name__ == '__main__': + unittest.main() From e9337a1f33e7a9e80f1ba53452c2bf558e509985 Mon Sep 17 00:00:00 2001 From: damien-masse Date: Mon, 24 Nov 2025 17:15:32 +0100 Subject: [PATCH 16/26] Modification for readability, new tests. --- doc/manual/manual/geometry/facets.rst | 25 ++ doc/manual/manual/geometry/polytope.rst | 30 +- doc/manual/manual/geometry/polytope_class.rst | 14 + doc/manual/manual/geometry/src_polytope.cpp | 57 ++++ examples/polytope_examples/CMakeLists.txt | 2 +- .../polytope_examples/main-3Dgraphics.cpp | 4 +- examples/polytope_examples/main-3Dhull.cpp | 4 +- examples/polytope_examples/main-3Dhull.py | 38 +++ examples/polytope_examples/main-3Dtest.cpp | 105 +++++++ examples/polytope_examples/main-F2V.cpp | 2 +- examples/polytope_examples/main-V2F.cpp | 2 +- examples/polytope_examples/main-manual.cpp | 55 ++++ .../domains/polytope/codac2_py_Polytope.cpp | 23 +- src/core/domains/polytope/codac2_Facet.cpp | 20 +- src/core/domains/polytope/codac2_Facet.h | 271 +++++++++++------- src/core/domains/polytope/codac2_Polytope.cpp | 21 +- src/core/domains/polytope/codac2_Polytope.h | 209 ++++++++------ .../domains/polytope/codac2_Polytope_dd.cpp | 6 +- .../domains/polytope/codac2_tests_Polytope.py | 2 +- 19 files changed, 657 insertions(+), 233 deletions(-) create mode 100644 doc/manual/manual/geometry/facets.rst create mode 100644 doc/manual/manual/geometry/polytope_class.rst create mode 100644 doc/manual/manual/geometry/src_polytope.cpp create mode 100644 examples/polytope_examples/main-3Dhull.py create mode 100644 examples/polytope_examples/main-3Dtest.cpp create mode 100644 examples/polytope_examples/main-manual.cpp diff --git a/doc/manual/manual/geometry/facets.rst b/doc/manual/manual/geometry/facets.rst new file mode 100644 index 000000000..77d9b5913 --- /dev/null +++ b/doc/manual/manual/geometry/facets.rst @@ -0,0 +1,25 @@ +.. _sec-polytope: + +Facet classes +============= + + Main author: `Damien Massé `_ + +Facets +------ + +.. doxygenclass:: codac2::FacetBase + :project: codac + :members: + +.. doxygenclass:: codac2::FacetRhs + :project: codac + :members: + +Collection of facets +-------------------- + +.. doxygenclass:: codac2::CollectFacets + :project: codac + :members: + diff --git a/doc/manual/manual/geometry/polytope.rst b/doc/manual/manual/geometry/polytope.rst index 84ba93c5c..76615da50 100644 --- a/doc/manual/manual/geometry/polytope.rst +++ b/doc/manual/manual/geometry/polytope.rst @@ -1,4 +1,4 @@ -.. _sec-zonotope: +.. _sec-polytope: Polytope ======== @@ -17,17 +17,25 @@ is given that each vertice of the polytope is associated to a generator. As a result, going back to the hyperplane representation may return a larger polytope. -Polytope --------- +.. toctree:: + :maxdepth: 1 + + facets.rst + polytope_class.rst + + +Building a polytope +------------------- +Polytopes can be defined from a set of facets (linear inequalities +or equalities), or a set of vertices. -.. doxygenclass:: codac2::Polytope - :project: codac - :members: +.. tabs:: -CollectFacets -------------- + .. group-tab:: C++ -.. doxygenclass:: codac2::CollectFacets - :project: codac - :members: + .. literalinclude:: src_polytope.cpp + :language: c++ + :start-after: [polytope-1-beg] + :end-before: [polytope-1-end] + :dedent: 2 diff --git a/doc/manual/manual/geometry/polytope_class.rst b/doc/manual/manual/geometry/polytope_class.rst new file mode 100644 index 000000000..ac0ea74b5 --- /dev/null +++ b/doc/manual/manual/geometry/polytope_class.rst @@ -0,0 +1,14 @@ +.. _sec-polytope: + +The Polytope class +================== + + Main author: `Damien Massé `_ + +Polytope +-------- + +.. doxygenclass:: codac2::Polytope + :project: codac + :members: + diff --git a/doc/manual/manual/geometry/src_polytope.cpp b/doc/manual/manual/geometry/src_polytope.cpp new file mode 100644 index 000000000..b0e3d57a9 --- /dev/null +++ b/doc/manual/manual/geometry/src_polytope.cpp @@ -0,0 +1,57 @@ +// Author : Damien Massé +// Graphical illustration of the polytope test + +#include +#include +#include + +using namespace std; +using namespace codac2; + +void draw_polytope(Figure3D &fig, const Polytope &P, const StyleProperties &st) { + std::vector> facets3D=P.vertices_3Dfacets(); + Vector center = Vector::zero(3); + Matrix transfo = Matrix::Identity(3,3); + for (const std::vector &vec : facets3D) { + fig.draw_polygon(center,transfo,vec,st); + } +} + +int main(int argc, char *argv[]) +{ +// std::cout << std::scientific << std::setprecision(20); + + Figure3D fig("manual_polytope"); + + fig.draw_axes(); + + // [polytope-1-beg] + // definition using vertices + std::vector vertices + { {0,0,0}, {2,1,0}, {-1,1,0}, {0,1.5,2}, {-1.5,0,-1.5}, {0,3,0} }; + Polytope p1(vertices); // vertices {0,0,0}, {2,1,0}, etc. + // definition using facets + std::vector> facets + { { {1,1,0.75}, 3 }, // x+y+0.75z <= 3 + { {-1,-0.4,0.6} ,0.6 }, // -x-0.4y+0.6z <= 0.6 + { {-1,0.5,0.375} ,1.5 }, // ... + { {-1,0.5,0} ,1.5 }, + { {0.5,-1,0.75} ,0 }, + { {-0.75,-1,0.75} ,0 }, + { {0.5,-1,-0.5} ,0 }, + { {1.0/3.0,1.0/3.0,-1} ,1 } }; + // the first argument is a bounding box, here the whole space + Polytope p2(IntervalVector::Constant(3,Interval()), facets); + // p1 and p2 are almost the same polytope + // [polytope-1-end] + + + draw_polytope(fig,p1,StyleProperties(Color::dark_red(0.8),"p1")); + std::cout << p1 << std::endl; + + std::vector vp2 = p2.vertices(); + for (auto &v : vp2) std::cout << v << std::endl; + draw_polytope(fig,p2,StyleProperties(Color::dark_blue(0.8),"p2")); + + return 0; +} diff --git a/examples/polytope_examples/CMakeLists.txt b/examples/polytope_examples/CMakeLists.txt index e6af2cf64..d0c0ac911 100644 --- a/examples/polytope_examples/CMakeLists.txt +++ b/examples/polytope_examples/CMakeLists.txt @@ -34,7 +34,7 @@ if(FAST_RELEASE) message(STATUS "You are running Codac in fast release mode. (option -DCMAKE_BUILD_TYPE=Release is required)") endif() -set(PROGS main-F2V main-3Dgraphics main-V2F main-3Dhull) +set(PROGS main-F2V main-3Dgraphics main-V2F main-3Dhull main-3Dtest main-manual) foreach(PROG ${PROGS}) add_executable(${PROG} ${PROG}.cpp) diff --git a/examples/polytope_examples/main-3Dgraphics.cpp b/examples/polytope_examples/main-3Dgraphics.cpp index c4f497192..ed48d75b1 100644 --- a/examples/polytope_examples/main-3Dgraphics.cpp +++ b/examples/polytope_examples/main-3Dgraphics.cpp @@ -9,7 +9,7 @@ using namespace std; using namespace codac2; void draw_polytope(Figure3D &fig, const Polytope &P, const StyleProperties &st) { - std::vector> facets3D=P.compute_3Dfacets(); + std::vector> facets3D=P.vertices_3Dfacets(); Vector center = Vector::zero(3); Matrix transfo = Matrix::Identity(3,3); for (const std::vector &vec : facets3D) { @@ -67,7 +67,7 @@ int main(int argc, char *argv[]) std::cout << "Intersection (minimized) : " << zono2 << "\n"; /* union */ - Polytope uni = Polytope::union_of_polytopes({ box1, box2, paral, zono }); + Polytope uni = Polytope::union_of_polytopes({ pbox1, pbox2, paral, zono }); std::cout << "Union : " << uni << "\n"; draw_polytope(fig,uni,StyleProperties(Color::dark_orange(0.3),"union")); diff --git a/examples/polytope_examples/main-3Dhull.cpp b/examples/polytope_examples/main-3Dhull.cpp index 782f8e682..3514e8db5 100644 --- a/examples/polytope_examples/main-3Dhull.cpp +++ b/examples/polytope_examples/main-3Dhull.cpp @@ -1,5 +1,5 @@ // Author : Damien Massé -// Generate random points in [-100,100] and draw the convex hull +// Generate random points on a ball of radius 10 and draw the convex hull #include #include @@ -11,7 +11,7 @@ using namespace std; using namespace codac2; void draw_polytope(Figure3D &fig, const Polytope &P, const StyleProperties &st) { - std::vector> facets3D=P.compute_3Dfacets(); + std::vector> facets3D=P.vertices_3Dfacets(); Vector center = Vector::zero(3); Matrix transfo = Matrix::Identity(3,3); for (const std::vector &vec : facets3D) { diff --git a/examples/polytope_examples/main-3Dhull.py b/examples/polytope_examples/main-3Dhull.py new file mode 100644 index 000000000..f420afdf7 --- /dev/null +++ b/examples/polytope_examples/main-3Dhull.py @@ -0,0 +1,38 @@ +from codac import * +import numpy as np +import random +import math + +def draw_polytope(fig,P,st): + facets3D = P.vertices_3Dfacets() + center = Vector([0,0,0]) + transfo = Matrix.eye(3,3) + for vec in facets3D: + fig.draw_polygon(center,transfo,vec,st) + +radius = 10.0 + +def dis(): + return random.uniform(-1,1) + +def random_point(): + a = dis()*radius + b = dis()*math.sqrt(radius*radius-a*a) + c = math.sqrt(radius*radius-a*a-b*b) + if dis()<0.0: + c = -c + return IntervalVector([[a-0.2,a+0.2],[b-0.2,b+0.2],[c-0.2,c+0.2]]) + +if __name__=="__main__": + fig = Figure3D("3D hull") + nbvertices=20 + vertices= [] + scale = 0.1*Matrix.eye(3,3) + for i in range(0,nbvertices): + point = random_point() + vertices.append(point) + fig.draw_box(point,StyleProperties(Color.black())) + pol = Polytope(vertices) + draw_polytope(fig,pol,StyleProperties(Color.dark_blue(0.5),"hull")) + + del(fig) # flush the file diff --git a/examples/polytope_examples/main-3Dtest.cpp b/examples/polytope_examples/main-3Dtest.cpp new file mode 100644 index 000000000..97fbd4869 --- /dev/null +++ b/examples/polytope_examples/main-3Dtest.cpp @@ -0,0 +1,105 @@ +// Author : Damien Massé +// Graphical illustration of the polytope test + +#include +#include +#include + +using namespace std; +using namespace codac2; + +void draw_polytope(Figure3D &fig, const Polytope &P, const StyleProperties &st) { + std::vector> facets3D=P.vertices_3Dfacets(); + Vector center = Vector::zero(3); + Matrix transfo = Matrix::Identity(3,3); + for (const std::vector &vec : facets3D) { + fig.draw_polygon(center,transfo,vec,st); + } +} + +int main(int argc, char *argv[]) +{ +// std::cout << std::scientific << std::setprecision(20); + + Figure3D fig("test_polytope"); + + fig.draw_axes(); +/* polytope with vertices + (0,0,0) , (2,0,1) , (1,0.1,0) , (0,-0.1,1), (1,2,0.5) + inequalities + -x + 20y + 82z <= 80 x - 20y - 2z <= 0 + -20.5x + 10y +z <= 0 -x + 10y - 38z <= 0 + 3.9x + y - 3.8z <= 4 x -10y - 2z <= 0 */ + + std::vector> facets + { { {-1,20,82}, 80 }, + { {-20.5,10,1} ,0 }, + { {3.9,1,-3.8} ,4 }, + { {1,-20,-2} ,0 }, + { {-1,10,-38} ,0 }, + { {1,-10,-2} ,0 } }; + + Polytope p(IntervalVector({{-4,4},{-4,4},{-4,4}}), facets, true); + draw_polytope(fig,p,StyleProperties(Color::dark_red(0.8), + "initial polytope")); + + std::vector vertices = p.vertices(); + + fig.draw_box(IntervalVector({{0.2,0.4},{0.0,0.3},{0.3,0.5}}), + StyleProperties(Color::black(),"box included")); + + Polytope q = p; + q.inflate(0.5); + for (IntervalVector &vt : vertices) { + fig.draw_box(vt+IntervalVector::constant(3,Interval(-0.5,0.5)), + StyleProperties(Color::orange(0.6), + "box_inflate")); + } + draw_polytope(fig,q,StyleProperties(Color::red(0.4), + "inflate")); + Polytope r = p; + r.inflate_ball(0.5); + for (IntervalVector &vt : vertices) { + fig.draw_sphere(vt.mid(),0.5*Matrix::Identity(3,3), + StyleProperties(Color::orange(0.6), + "ball_inflate")); + } + draw_polytope(fig,r,StyleProperties(Color::purple(0.4), + "inflate_ball")); + + Polytope s = p; + s.unflat(2,0.5); + for (IntervalVector &vt : vertices) { + fig.draw_box(vt+IntervalVector({{-0.01,0.01},{-0.01,0.01},{-0.5,0.5}}), + StyleProperties(Color::black(), + "box_unflat")); + } + draw_polytope(fig,s,StyleProperties(Color::yellow(0.4), + "unflat")); + + Polytope t = p; + t.homothety(IntervalVector({0,0,-0.5}),2); + draw_polytope(fig,t,StyleProperties(Color::dark_blue(0.8), + "homothety")); + + Polytope u = t; + u.meet_with_polytope(p); + draw_polytope(fig,u,StyleProperties(Color::dark_gray(), + "intersection")); + + Polytope v = Polytope::union_of_polytopes({ p,t }); + draw_polytope(fig,v,StyleProperties(Color::cyan(0.4), + "union")); + + IntervalMatrix M { { cos(PI/3) , sin(PI/3) , 0 }, + { -sin(PI/3), cos(PI/3) , 0 }, + { 0 , 0 , 1 } }; + IntervalVector P { 1-cos(PI/3) , sin(PI/3) , 0 }; + Polytope w = p.reverse_affine_transform(M,P, + IntervalVector({{-4,4},{-4,4},{-4,4}})); + + draw_polytope(fig,w,StyleProperties(Color::blue(0.8), + "transformation")); + + return 0; +} diff --git a/examples/polytope_examples/main-F2V.cpp b/examples/polytope_examples/main-F2V.cpp index d99a11e81..b0f4088fc 100644 --- a/examples/polytope_examples/main-F2V.cpp +++ b/examples/polytope_examples/main-F2V.cpp @@ -19,7 +19,7 @@ int main(int argc, char *argv[]) std::cout << pol << std::endl; std::vector - lvect = pol.compute_vertices(); + lvect = pol.vertices(); std::cout << lvect.size() << " vertices" << std::endl; for (IntervalVector a : lvect) std::cout << a << std::endl; diff --git a/examples/polytope_examples/main-V2F.cpp b/examples/polytope_examples/main-V2F.cpp index 04d9e3f13..395ba76f9 100644 --- a/examples/polytope_examples/main-V2F.cpp +++ b/examples/polytope_examples/main-V2F.cpp @@ -20,7 +20,7 @@ int main(int argc, char *argv[]) std::cout << pol << std::endl; - std::vector lst = pol.compute_vertices(); + std::vector lst = pol.vertices(); std::cout << "nb vertices : " << lst.size() << std::endl; for (int i=0;i +#include +#include + +using namespace std; +using namespace codac2; + +void draw_polytope(Figure3D &fig, const Polytope &P, const StyleProperties &st) { + std::vector> facets3D=P.vertices_3Dfacets(); + Vector center = Vector::zero(3); + Matrix transfo = Matrix::Identity(3,3); + for (const std::vector &vec : facets3D) { + fig.draw_polygon(center,transfo,vec,st); + } +} + +int main(int argc, char *argv[]) +{ +// std::cout << std::scientific << std::setprecision(20); + + Figure3D fig("manual_polytope"); + + fig.draw_axes(); + + // definition using vertices + std::vector vertices + { {0,0,0}, {2,1,0}, {-1,1,0}, {0,1.5,2}, {-1.5,0,-1.5}, {0,3,0} }; + Polytope p1(vertices); + // definition using facets + std::vector> facets + { { {1,1,0.75}, 3 }, + { {-1,-0.4,0.6} ,0.6 }, + { {-1,0.5,0.375} ,1.5 }, + { {-1,0.5,0} ,1.5 }, + { {0.5,-1,0.75} ,0 }, + { {-0.75,-1,0.75} ,0 }, + { {0.5,-1,-0.5} ,0 }, + { {1.0/3.0,1.0/3.0,-1} ,1 } }; + // the first argument is a bounding box, here the whole space + Polytope p2(IntervalVector::Constant(3,Interval()), facets); + + // p1 and p2 are ``almost'' the same polytope + + draw_polytope(fig,p1,StyleProperties(Color::dark_red(0.8),"p1")); + std::cout << p1 << std::endl; + + std::vector vp2 = p2.vertices(); + for (auto &v : vp2) std::cout << v << std::endl; + draw_polytope(fig,p2,StyleProperties(Color::dark_blue(0.8),"p2")); + + return 0; +} diff --git a/python/src/core/domains/polytope/codac2_py_Polytope.cpp b/python/src/core/domains/polytope/codac2_py_Polytope.cpp index 3c581e9be..3b8122a7b 100644 --- a/python/src/core/domains/polytope/codac2_py_Polytope.cpp +++ b/python/src/core/domains/polytope/codac2_py_Polytope.cpp @@ -67,8 +67,8 @@ void export_Polytope(py::module& m) POLYTOPE_POLYTOPE_CONST_INTERVALVECTOR_REF_CONST_VECTOR_PAIR_ROWDOUBLE_REF_BOOL, "box"_a,"facets"_a,"minimize"_a) - .def("assign",[](Polytope &Q, const Polytope &P) { Q=P; return Q; }, - POLYTOPE_REF_POLYTOPE_OPERATORAFF_CONST_POLYTOPE_REF, "P"_a) + .def("copy",[](Polytope &Q) { Polytope P=Q; return P; }, + POLYTOPE_REF_POLYTOPE_OPERATORAFF_CONST_POLYTOPE_REF) .def("dim",&Polytope::dim, INDEX_POLYTOPE_DIM_CONST) @@ -76,11 +76,11 @@ void export_Polytope(py::module& m) .def("dim",&Polytope::size, INDEX_POLYTOPE_SIZE_CONST) - .def("nbFacets",&Polytope::nbFacets, - INDEX_POLYTOPE_NBFACETS_CONST) + .def("nb_facets",&Polytope::nb_facets, + INDEX_POLYTOPE_NB_FACETS_CONST) -// .def("nbEqFacets",&Polytope::nbEqFacets, -// INDEX_POLYTOPE_NBEQFACETS_CONST) + .def("nb_eq_facets",&Polytope::nb_eq_facets, + INDEX_POLYTOPE_NB_EQ_FACETS_CONST) .def("is_empty",[](const Polytope &P) { return P.is_empty(true); }, BOOL_POLYTOPE_IS_EMPTY_BOOL_CONST) @@ -123,6 +123,9 @@ void export_Polytope(py::module& m) POLYTOPE_REF_POLYTOPE_HOMOTHETY_CONST_INTERVALVECTOR_REF_DOUBLE, "c"_a,"delta"_a) + .def("minimize_constraints",&Polytope::minimize_constraints, + VOID_POLYTOPE_MINIMIZE_CONSTRAINTS_CONST) + .def("reverse_affine_transform",&Polytope::reverse_affine_transform, POLYTOPE_POLYTOPE_REVERSE_AFFINE_TRANSFORM_CONST_INTERVALMATRIX_REF_CONST_INTERVALVECTOR_REF_CONST_INTERVALVECTOR_REF_CONST, "M"_a,"P"_a,"bbox"_a) @@ -131,11 +134,11 @@ void export_Polytope(py::module& m) POLYTOPE_POLYTOPE_BIJECTIVE_AFFINE_TRANSFORM_CONST_INTERVALMATRIX_REF_CONST_INTERVALMATRIX_REF_CONST_INTERVALVECTOR_REF_CONST, "M"_a,"Minv"_a,"P"_a) - .def("compute_vertices",&Polytope::compute_vertices, - VECTOR_INTERVALVECTOR_POLYTOPE_COMPUTE_VERTICES_CONST) + .def("vertices",&Polytope::vertices, + VECTOR_INTERVALVECTOR_POLYTOPE_VERTICES_CONST) - .def("compute_3Dfacets",&Polytope::compute_3Dfacets, - VECTOR_VECTOR_VECTOR_POLYTOPE_COMPUTE_3DFACETS_CONST) + .def("vertices_3Dfacets",&Polytope::vertices_3Dfacets, + VECTOR_VECTOR_VECTOR_POLYTOPE_VERTICES_3DFACETS_CONST) .def_static("from_ineFile",&Polytope::from_ineFile, STATIC_POLYTOPE_POLYTOPE_FROM_INEFILE_CONST_CHAR_PTR,"filename"_a) diff --git a/src/core/domains/polytope/codac2_Facet.cpp b/src/core/domains/polytope/codac2_Facet.cpp index 484fc6e20..1d9cca9d3 100644 --- a/src/core/domains/polytope/codac2_Facet.cpp +++ b/src/core/domains/polytope/codac2_Facet.cpp @@ -26,9 +26,9 @@ namespace codac2 { namespace Facet_ { polytope_inclrel relation_Box(const Facet &f, const IntervalVector &b) { - const Row &row = f.first.row; - const double &rhs = f.second.rhs; - const bool &eqcst = f.second.eqcst; + const Row &row = f.first.get_row(); + const double rhs = f.second.get_rhs(); + const bool eqcst = f.second.is_eqcst(); if (b.is_empty()) return inclrel_includes | inclrel_disjoint; IntervalVector a(b); /* check the vertex that maximizes row */ @@ -70,8 +70,8 @@ polytope_inclrel relation_Box(const Facet &f, const IntervalVector &b) { } void contract_Box(const Facet &f, IntervalVector &b) { - const Row &row = f.first.row; - const double &rhs = f.second.rhs; + const Row &row = f.first.get_row(); + const double rhs = f.second.get_rhs(); /* use MulOp:bwd */ IntervalRow x1(row); MulOp::bwd(Interval(-oo,rhs),x1,b); @@ -79,8 +79,8 @@ void contract_Box(const Facet &f, IntervalVector &b) { } void contract_out_Box(const Facet &f, IntervalVector &b) { - const Row &row = f.first.row; - const double &rhs = f.second.rhs; + const Row &row = f.first.get_row(); + const double rhs = f.second.get_rhs(); /* use MulOp:bwd */ IntervalRow x1(row); MulOp::bwd(Interval(rhs,oo),x1,b); @@ -90,9 +90,9 @@ void contract_out_Box(const Facet &f, IntervalVector &b) { Interval bound_linear_form(const Facet &f, const Row &row2, const IntervalVector &b) { if (f.first.isNull()) return Interval(); - const Row &row = f.first.row; - const double &rhs = f.second.rhs; - const bool &eqcst = f.second.eqcst; + const Row &row = f.first.get_row(); + const double rhs = f.second.get_rhs(); + const bool eqcst = f.second.is_eqcst(); const Index abdim = f.first.gtDim(); bool neg = (row[abdim]<0.0); if (!eqcst && diff --git a/src/core/domains/polytope/codac2_Facet.h b/src/core/domains/polytope/codac2_Facet.h index 740edbfb1..282f95e5c 100644 --- a/src/core/domains/polytope/codac2_Facet.h +++ b/src/core/domains/polytope/codac2_Facet.h @@ -27,9 +27,71 @@ namespace codac2 { -/* base of comparison for Facet (note: only a preorder) */ -struct FacetBase { - Index bdim; /* if A is the index of the greatest abs value and B +class FacetBase; +class FacetRhs; +/** \brief a facet is a pair of FacetBase and FacetRhs */ +using Facet = std::pair; + +/** Encapsulation of Row (key of facet maps) */ +class FacetBase { + /* friends */ + friend class CollectFacets; + friend class Polytope; + friend class DDbuildF2V; + friend class DDbuildV2F; + friend struct FacetBaseComp; + + public: + + /** \brief build a new FacetBase from a row + * \param row the Row + */ + FacetBase(const Row &row) : bdim(-1), vdim(0.0), row(row) { + this->compute_key(); + } + + /** \brief build a new FacetBase from a row + * \param row the Row + */ + FacetBase(Row &&row) : bdim(-1), vdim(0.0), row(row) { + this->compute_key(); + } + + FacetBase(const FacetBase &fc) = default; + FacetBase(FacetBase &&fc) = default; + + /** \brief get the row + * \return the row */ + inline const Row &get_row() const { return row; } + + /** \brief test if the Row is zero + * \return true if row = 0 */ + inline bool isNull() const { return (bdim==-1); } + + /** \brief test if the row has only one non-zero coordinate + * \return true if row is zero for all coordinates except one */ + inline bool isCoord() const { + if (row.size()==0) return false; + return (bdim%(2*row.size())==0); + } + + /** \brief return the index of the greatest coordinate (in absolute value) + * suppose the row is not zero + * \return a value between 0 and size-1 */ + inline Index gtDim () const { + if (row.size()==0) return -1; + return (bdim/(2*row.size()))%row.size(); + } + + inline void swap(FacetBase &fb) { + this->row.swap(fb.row); + std::swap(this->bdim,fb.bdim); + std::swap(this->bdim,fb.bdim); + } + + private: + + Index bdim; /* if A is the index of the greatest abs value and B the second greatest (=A if 0 outside) v[A]>0 v[B]>0 A*(2*dim)+A-B (+2*dim if B>A) v[A]>0 v[B]<0 A*(2*dim)+A-(B+dim)+2*dim @@ -40,80 +102,46 @@ struct FacetBase { and Bdim = B if v[B]>0 and B+dim if v[B]<0 bdim = Adim*(2*dim+1) - Bdim (+2*dim if Adim>Bdim) if null vector, value = -1 */ - double vdim; /* |v[B]|/|v[A]| (0 if no value for B) */ - Row row; - - FacetBase(const Row &row) : bdim(-1), vdim(0.0), row(row) { - this->compute_key(); - } - - FacetBase(Row &&row) : bdim(-1), vdim(0.0), row(row) { - this->compute_key(); - } - FacetBase(const FacetBase &fc) : bdim(fc.bdim), vdim(fc.vdim), row(fc.row) { - } - FacetBase(FacetBase &&fc) : bdim(fc.bdim), vdim(fc.vdim), row(fc.row) { - } + double vdim; /* |v[B]|/|v[A]| (0 if no value for B) */ + Row row; + - /* create a "min" and "max" FacetBase for constraints of the form - a_i x_i <= b_i (or - a_i x_i <= b_i if neg is true */ - static std::pair + /* create a "min" and "max" FacetBase for constraints of the form + a_i x_i <= b_i (or - a_i x_i <= b_i if neg is true */ + static std::pair base_range(Index dim, Index i, bool neg) { - Row r = Row::zero(dim); - Index d = (i+(neg?dim:0))*(2*dim); - return std::make_pair - (FacetBase(d,-1.0,r),FacetBase(d,1.0,r)); - } - - inline void swap(FacetBase &fb) { - this->row.swap(fb.row); - std::swap(this->bdim,fb.bdim); - std::swap(this->bdim,fb.bdim); - } - - /** return true if the Row is zero */ - inline bool isNull() const { return (bdim==-1); } - - /** return true if the Row is zero outside the greatest dimension */ - inline bool isCoord() const { - if (row.size()==0) return false; - return (bdim%(2*row.size())==0); - } - - /** return the greatest dimension (between 0 and dim-1) */ - inline Index gtDim () const { - if (row.size()==0) return -1; - return (bdim/(2*row.size()))%row.size(); - } - - inline void change_row(Row &&row) { - this->row=row; this->bdim=-1; this->vdim=0.0; - this->compute_key(); - } - inline void change_row(const Row &row) { - this->row=row; this->bdim=-1; this->vdim=0.0; - this->compute_key(); - } - - inline void negate_row() { - this->row=-row; - Index u = 2*row.size()*row.size(); - if (this->bdim>=u) this->bdim-=u; else this->bdim+=u; - } + Row r = Row::zero(dim); + Index d = (i+(neg?dim:0))*(2*dim); + return std::make_pair + (FacetBase(d,-1.0,r),FacetBase(d,1.0,r)); + } - private: + inline void negate_row() { + this->row=-row; + Index u = 2*row.size()*row.size(); + if (this->bdim>=u) this->bdim-=u; else this->bdim+=u; + } - FacetBase(Index bdim, double vdim, const Row &row) : + inline void change_row(Row &&row) { + this->row=row; this->bdim=-1; this->vdim=0.0; + this->compute_key(); + } + inline void change_row(const Row &row) { + this->row=row; this->bdim=-1; this->vdim=0.0; + this->compute_key(); + } + + FacetBase(Index bdim, double vdim, const Row &row) : bdim(bdim), vdim(vdim), row(row) { } - void compute_key() { - if (row.size()==0) return; - double val=row[0]; - double valabs=std::abs(val); - if (val<0.0) bdim=row.size(); else if (val>0.0) bdim=0; - Index b2dim = -1; - double val2abs=0.0; - for (Index i=1;i0.0) bdim=0; + Index b2dim = -1; + double val2abs=0.0; + for (Index i=1;ivalabs) { val2abs=valabs; @@ -125,39 +153,65 @@ struct FacetBase { val2abs=val_i; if (row[i]<0.0) b2dim=row.size()+i; else b2dim=i; } - } - if (valabs==0.0) return; /* bdim=-1 */ - if (b2dim==-1) { bdim = 2*row.size()*bdim; vdim=0.0; return; } - if (b2dim>bdim) - bdim = bdim * (2*row.size()+1) + 2*row.size() - b2dim; - else - bdim = bdim * (2*row.size()+1) - b2dim; - vdim = val2abs/valabs; - } + } + if (valabs==0.0) return; /* bdim=-1 */ + if (b2dim==-1) { bdim = 2*row.size()*bdim; vdim=0.0; return; } + if (b2dim>bdim) + bdim = bdim * (2*row.size()+1) + 2*row.size() - b2dim; + else + bdim = bdim * (2*row.size()+1) - b2dim; + vdim = val2abs/valabs; + } + friend std::ostream& operator<<(std::ostream& os, const Facet& f); + friend void output_normalised_facet(std::ostream& os, const Facet& f); + }; -struct FacetRhs { - double rhs; - bool eqcst; - Index Id; +/** Second part of the facet: nature of the facet (equality or not), + * right-hand side. An Id number is also kept for internal use inside + * a collection of facets. This number may change when the collection is + * modified */ +class FacetRhs { + /* friends */ + friend class CollectFacets; + friend class Polytope; + friend class DDbuildF2V; + friend class DDbuildV2F; - FacetRhs(double rhs, bool eqcst, Index Id) : rhs(rhs), eqcst(eqcst), Id(Id) - {}; - FacetRhs(const FacetRhs rhs, Index Id) : rhs(rhs.rhs), eqcst(rhs.eqcst), - Id(Id) + public: + /** \brief value of the rhs + * \return the rhs */ + double get_rhs() const { return rhs; } + + /** \brief nature of the facet (= or <=) + * \return true if equality, false if inequality */ + bool is_eqcst() const { return eqcst; } + + /** \brief id number of the facet + * \return id number */ + Index get_Id() const { return Id; } + + FacetRhs(double rhs, bool eqcst, Index Id=0) : rhs(rhs), eqcst(eqcst), Id(Id) {}; + FacetRhs(const FacetRhs &rhs) = default; + + void swap(FacetRhs &r) { + std::swap(rhs,r.rhs); + std::swap(eqcst,r.eqcst); + } + + private : + + double rhs; + bool eqcst; + Index Id; + friend std::ostream& operator<<(std::ostream& os, const Facet& f); + friend void output_normalised_facet(std::ostream& os, const Facet& f); - void swap(FacetRhs &r) { - std::swap(rhs,r.rhs); - std::swap(eqcst,r.eqcst); - } }; -using Facet = std::pair; -/* I want specific functions for Facet, but I do not want to create - a new class inheriting Facet (as the Map will have the pair) - => using namespace */ +/** namespace Facet_ is used to encapsulate Facet functions */ namespace Facet_ { inline Facet make(const Row &row, double rhs, bool eqcst, Index Id) { @@ -166,12 +220,25 @@ namespace Facet_ { inline Facet make(Row &&row, double rhs, bool eqcst, Index Id) { return std::make_pair(FacetBase(row),FacetRhs(rhs,eqcst,Id)); } + + /** \brief create a new facet from a row, rhs, eqcst + * \param row the row + * \param rhs the rhs + * \param eqcst true for equalities, false for <= + * \return the new Facet */ inline Facet make(const Row &row, double rhs, bool eqcst) { return std::make_pair(FacetBase(row),FacetRhs(rhs,eqcst,0)); } + + /** \brief create a new facet from a row, rhs, eqcst + * \param row the row + * \param rhs the rhs + * \param eqcst true for equalities, false for <= + * \return the new Facet */ inline Facet make(Row &&row, double rhs, bool eqcst) { return std::make_pair(FacetBase(row),FacetRhs(rhs,eqcst,0)); } + inline Facet make(const FacetBase &base, double rhs, bool eqcst) { return std::make_pair(base,FacetRhs(rhs,eqcst,0)); } @@ -220,6 +287,10 @@ namespace Facet_ { void contract_out_Box(const Facet &f, IntervalVector &b); } + /** \brief print a Facet + * \param os the output stream + * \param f a facet + * \return os */ std::ostream& operator<<(std::ostream& os, const Facet& f); struct FacetBaseComp { @@ -237,7 +308,8 @@ struct FacetBaseComp { }; /** encapsulate a collection of facets with double index : - by the base vector, and by index, with storing of eqcsts */ + by the base vector, and by Id number. Renumbering can happen + when facets are removed. */ class CollectFacets { public: using mapType = std::map; @@ -263,13 +335,14 @@ class CollectFacets { */ CollectFacets(const CollectFacets& cf); - /** move the elements of CollectFacets. Perform a renumbering if + /** move the elements of CollectFacets. Perform a renumbering of Id if * nb_removed_facets > 0 * @param cf the CollectFacets */ CollectFacets(CollectFacets&& cf); - /** return the dimension of the facets */ + /** return the dimension of the facets + * @return the dimension */ Index getDim() const; /** return the number of facets, as the size of _allfacets */ Index nbfcts() const; diff --git a/src/core/domains/polytope/codac2_Polytope.cpp b/src/core/domains/polytope/codac2_Polytope.cpp index ebc3df80d..59bf9868a 100644 --- a/src/core/domains/polytope/codac2_Polytope.cpp +++ b/src/core/domains/polytope/codac2_Polytope.cpp @@ -879,7 +879,7 @@ Polytope Polytope::union_of_polytopes(std::initializer_list lst) { std::vector lvert; for (auto &P : lst) { std::vector tmp = - P.compute_vertices(); + P.vertices(); lvert.insert(lvert.end(),tmp.begin(),tmp.end()); box |= P.box(); } @@ -898,7 +898,7 @@ Polytope Polytope::union_of_polytopes(const std::vector &lst) { std::vector lvert; for (auto &P : lst) { std::vector tmp = - P.compute_vertices(); + P.vertices(); lvert.insert(lvert.end(),tmp.begin(),tmp.end()); box |= P.box(); } @@ -1040,6 +1040,19 @@ Polytope &Polytope::unflat(Index dm, double rad) { return (*this); } +Polytope operator+ (const Polytope &p1, const Polytope &p2) { + assert(!p1.state[INVALID]); + assert(!p2.state[INVALID]); + std::vector vt1 = p1.vertices(); + std::vector vt2 = p2.vertices(); + IntervalVector bres = p1.box() + p2.box(); + std::vector result; + for (auto &v1 : vt1) + for (auto &v2 : vt2) + result.push_back(v1+v2); + Polytope ret = Polytope(result); + return (ret &= bres); +} std::ostream& operator<<(std::ostream& os, const Polytope &P) { @@ -1053,7 +1066,7 @@ std::ostream& operator<<(std::ostream& os, return os; } -std::vector Polytope::compute_vertices() const { +std::vector Polytope::vertices() const { this->build_DDbuildF2V(); if (state[EMPTY]) return std::vector(); std::vector ret; @@ -1063,7 +1076,7 @@ std::vector Polytope::compute_vertices() const { return ret; } -std::vector> Polytope::compute_3Dfacets() const { +std::vector> Polytope::vertices_3Dfacets() const { this->build_DDbuildF2V(); if (state[EMPTY]) return std::vector>(); return build_3Dfacets(*_DDbuildF2V); diff --git a/src/core/domains/polytope/codac2_Polytope.h b/src/core/domains/polytope/codac2_Polytope.h index c2336fd49..5869310bd 100644 --- a/src/core/domains/polytope/codac2_Polytope.h +++ b/src/core/domains/polytope/codac2_Polytope.h @@ -41,51 +41,51 @@ namespace codac2 { /** * \class Polytope - * \brief Represents a bounded convex polytope as a set of constraints + * Represents a bounded convex polytope as a set of constraints * and a bounding box (the bounding box is part of the constraints) */ class Polytope { public: /** - * \brief basic constuctor + * basic constuctor */ Polytope(); /** - * \brief basic constuctor with a dimension, full + * basic constuctor with a dimension, full */ - Polytope(Index dim); + explicit Polytope(Index dim); /** - * \brief basic constuctor with a dimension, empty or full + * basic constuctor with a dimension, empty or full */ - Polytope(Index dim, bool empty); + explicit Polytope(Index dim, bool empty); /** - * \brief Constructs a bounded polytope representing a box + * Constructs a bounded polytope representing a box * from a bounded interval vector */ - Polytope(const IntervalVector &box); + explicit Polytope(const IntervalVector &box); /** - * \brief Copy (no minimization) + * Copy (no minimization) */ Polytope(const Polytope &P); /** - * \brief Constructs a polytope from a set of vertices + * Constructs a polytope from a set of vertices * \param vertices the vertices */ - Polytope(const std::vector &vertices); + explicit Polytope(const std::vector &vertices); /** - * \brief Constructs a polytope from a set of vertices as intervalboxes + * Constructs a polytope from a set of vertices as intervalboxes * \param vertices the vertices */ - Polytope(const std::vector &vertices); + explicit Polytope(const std::vector &vertices); /** - * \brief Constructs a polytope from a set of vertices (intervalVector) + * Constructs a polytope from a set of vertices (IntervalVector) * and a set of linear forms, described as a CollectFacets * \param vertices the vertices * \param facetsform the facets description (the CollectFacets) @@ -94,19 +94,19 @@ namespace codac2 { const CollectFacets &facetsform); /** - * \brief from a parallelepiped + * from a parallelepiped * \param par the parallelepiped */ - Polytope(const Parallelepiped &par); + explicit Polytope(const Parallelepiped &par); /** - * \brief from a zonotope + * from a zonotope * \param zon the zonotope */ - Polytope(const Zonotope &zon); + explicit Polytope(const Zonotope &zon); /** - * \brief Constructs a polytope from a box and a CollectFacets which + * Constructs a polytope from a box and a CollectFacets which * will be moved in the polytope. Perform a "box extraction" and a * renumbering on the collection. * \param box the box @@ -115,7 +115,7 @@ namespace codac2 { Polytope(IntervalVector &&box, CollectFacets &&facets); /** - * \brief Constructs a polytope from a CollectFacets which + * Constructs a polytope from a CollectFacets which * will be moved in the polytope. Perform a "box extraction" and a * renumbering on the collection. * \param facets the collection of facets @@ -123,7 +123,7 @@ namespace codac2 { Polytope(CollectFacets &&facets); /** - * \brief Constructs a polytope from a bounding box and + * Constructs a polytope from a bounding box and * a set of constraints (inequalities) * */ @@ -132,15 +132,15 @@ namespace codac2 { bool minimize=false); /** - * \brief build a polytope from an ine File (cddlib format) + * build a polytope from an ine File (cddlib format) * \param filename the file */ static Polytope from_ineFile(const char *filename); /** - * \brief build a polytope from an ext File (cddlib format) + * build a polytope from an ext File (cddlib format) * \param filename the file */ static Polytope from_extFile(const char *filename); - /** \brief convex union of polytopes + /** convex union of polytopes * use vertices to join the polytopes, as such the result may be * too large * \param lst a non-empty initializer_list of valid polytopes of @@ -148,7 +148,7 @@ namespace codac2 { * \return the polytope */ static Polytope union_of_polytopes(std::initializer_list lst); - /** \brief convex union of polytopes + /** convex union of polytopes * use vertices to join the polytopes, as such the result may be * too large * \param lst a non-empty initializer_list of valid polytopes of @@ -157,12 +157,12 @@ namespace codac2 { static Polytope union_of_polytopes(const std::vector &lst); /** - * \brief Destructor + * Destructor */ ~Polytope(); /** - * \brief copy assignment operator + * copy assignment operator * \param P copy */ Polytope &operator=(const Polytope &P); @@ -171,7 +171,7 @@ namespace codac2 { /***************** ACCESS **************************/ /** - * \brief Get the dimension of the space + * Get the dimension of the space * * \return the dimension of the space */ @@ -179,7 +179,7 @@ namespace codac2 { Index size() const; /** - * \brief Get the dimension of the polytope (ie the dimension + * Get the dimension of the polytope (ie the dimension * of the space minus the equalities). -1 means that the polytope * is empty. * @@ -188,30 +188,30 @@ namespace codac2 { // Index dim_polytope() const; /** - * \brief Get the number of constraints + * Get the number of constraints * * \return the number of facets (including equalities) */ - Index nbFacets() const; + Index nb_facets() const; /** - * \brief Get the number of equality constraints + * Get the number of equality constraints * * \return the number of equalities */ - Index nbEqFacets() const; + Index nb_eq_facets() const; /** - * \brief is empty + * is empty */ bool is_empty(bool check=true) const; /** - * \brief has a flat dimension + * has a flat dimension */ bool is_flat() const; - /** \brief ``component'' : interval of the bounding box + /** ``component'' : interval of the bounding box * (only const !!!). Update the bbox if needed. * \return enclosing of the component */ @@ -219,11 +219,12 @@ namespace codac2 { /** * ``middle'' (vector inside the polytope, if possible) + * \return */ Vector mid() const; /** - * \brief bound a constraint (fast), either by a "close" one (+box) + * bound a constraint (fast), either by a "close" one (+box) * or by the vertices if they are computed. * no update or LP are done * return an interval I : row X is guaranteed to be less than I.ub() @@ -234,14 +235,14 @@ namespace codac2 { Interval fast_bound(const FacetBase &base) const; /** - * \brief bound a constraint, using either clp or DD + * bound a constraint, using either clp or DD * \param r the row * \return the max */ double bound_row(const Row &r) const; #if 0 - /** \brief ``distance'' from the satisfaction of a constraint + /** ``distance'' from the satisfaction of a constraint * ie the inflation of the rhs needed to satisfy the constraint * \param fc the constraint * \return an interval @@ -249,14 +250,14 @@ namespace codac2 { double distance_cst (const Facet &fc) const; /** - * \brief relationships with a box (fast check) + * relationships with a box (fast check) * \param p the box * \return polytope_inclrel checking inclusion and intersection */ Facet_::polytope_inclrel relation_Box(const IntervalVector& p) const; #endif /** - * \brief Checks whether the polytope contains a given point, or + * Checks whether the polytope contains a given point, or * includes a box * * \param p The point or box to check, enclosed in an IntervalVector. @@ -265,7 +266,7 @@ namespace codac2 { BoolInterval contains(const IntervalVector& p) const; /** - * \brief Checks inclusion in another polytope + * Checks inclusion in another polytope * \param P the polytope * \param checkF2V checks using DD * \param checkCLP checks using CLP @@ -274,13 +275,13 @@ namespace codac2 { bool is_subset(const Polytope &P, bool checkF2V=true, bool checkCLP=true) const; - /** \brief intersects a box + /** intersects a box * \param x the box (IntervalVector) * \return if the polytope intersects the box */ BoolInterval intersects(const IntervalVector& x) const; - /** \brief intersects a polytope + /** intersects a polytope * \param p the polytope * \return if the polytope intersects the box */ @@ -294,7 +295,7 @@ namespace codac2 { /************* Box access *************************/ /** - * \brief get the current bounding box of the polytope + * get the current bounding box of the polytope * (note: it may not be tightest bounding box, it is the * bounding box used for approximations results) * @@ -303,13 +304,13 @@ namespace codac2 { const IntervalVector &box(bool tight=true) const; /** - * \brief test if bounding box is included + * test if bounding box is included * \return true if the polytope is included in x */ bool box_is_subset(const IntervalVector& x) const; /** - * \brief tests if the polytope is bisectable + * tests if the polytope is bisectable * (i.e. its bounding box is bisectable). * do not update the box * \return true if the bbox is bisectable @@ -320,13 +321,13 @@ namespace codac2 { /************* Modification **********************/ /* keeping the current polytope */ - /** \brief set to empty */ + /** set to empty */ void set_empty(); - /** \brief set to (singleton) 0^dim */ + /** set to (singleton) 0^dim */ void clear(); - /** \brief add a inequality (pair row X <= rhs) + /** add a inequality (pair row X <= rhs) * do basic checks, but do not minimize the system * \param facet the constraint * \param tolerance tolerance for redundancy checking (CLP only) @@ -335,7 +336,7 @@ namespace codac2 { bool add_constraint(const std::pair& facet, double tolerance=0.0); - /** \brief add a inequality with intervalVector (cst X <= rhs) + /** add a inequality with intervalVector (cst X <= rhs) * using the bounding box * do basic checks, but do not minimize the system * \param cst the row constraint @@ -346,7 +347,7 @@ namespace codac2 { bool add_constraint(const IntervalRow &cst, double rhs, double tolerance=0.0); - /** \brief two inequalities with intervalVector (cst X in rhs) + /** two inequalities with intervalVector (cst X in rhs) * using the bounding box * do basic checks, but do not minimize the system. Also, * should not be used for equalities (cst and rhs punctual) @@ -357,71 +358,71 @@ namespace codac2 { std::pair add_constraint_band(const IntervalRow &cst, const Interval &rhs, double tolerance=0.0); - /** \brief add a equality (pair row X = rhs) + /** add a equality (pair row X = rhs) * do basic checks, but do not minimize the system * \param facet the constraint * \return -1 if empty, 0 no change (not probable), 1 change */ int add_equality(const std::pair& facet); - /** \brief intersect with a box. + /** intersect with a box. * \param b the box * \return 0 if nothing done, 1 changed made, -1 results is empty */ int meet_with_box(const IntervalVector &b); - /** \brief intersect with a box. + /** intersect with a box. * \param b the box * \return *this */ Polytope &operator&=(const IntervalVector &b); - /** \brief intersect with a polytope. + /** intersect with a polytope. * \param P the polytope * \return 0 if nothing done, 1 changed made, -1 results is empty */ int meet_with_polytope(const Polytope &P); - /** \brief intersect with a polytope. + /** intersect with a polytope. * \param P the polytope * \return *this */ Polytope &operator&=(const Polytope &P); - /** \brief union with a polytope + /** union with a polytope * \param P the polytope * \return 0 if nothing done, 1 changed made, -1 results is empty */ int join_with_polytope(const Polytope &P); - /** \brief union with a polytope + /** union with a polytope * \param P the polytope * \return *this */ Polytope &operator|=(const Polytope &P); - /** \brief inflation by a cube, keeping the shape (not optimal) + /** inflation by a cube, keeping the shape (not optimal) * this <- this + [-rad,rad]^d * \param rad radius of the box * \return *this (keep minimized, except rounding) */ Polytope &inflate(double rad); - /** \brief inflation with a box, keeping the shape (not optimal) + /** inflation with a box, keeping the shape (not optimal) * this <- this + box * \param box the box * \return *this (keep minimized, except rounding) */ Polytope &inflate(const IntervalVector& box); - /** \brief inflation by a ball, keeping the shape (not optimal) + /** inflation by a ball, keeping the shape (not optimal) * this <- this + B_d(rad) (note: vector norm is computed on double) * \param rad radius of the ball * \return *this (keep minimized, except rounding) */ Polytope &inflate_ball(double rad); - /** \brief expansion of a dimension (not optimal) + /** expansion of a dimension (not optimal) * equivalent to inflation by a box 0,...,[-rad,rad],...0 * \param dm index of the dimension * \param rad radius * \return *this (keep minimized, except rounding) */ Polytope &unflat(Index dm, double rad); - /** \brief centered homothety (optimal if c is punctual) + /** centered homothety (optimal if c is punctual) * x <- [c] + delta*(x-[c]) ( or (1-delta)[c] + delta*x ) * note : delta must be > 0 * \param c ``center'' @@ -432,7 +433,7 @@ namespace codac2 { /* creating a new polytope */ - /** \brief return a polytope intersected with an hyperplan + /** return a polytope intersected with an hyperplan * in a specific coordinate (rebuild the constraints without * any dependence on this coordinate in a new polytope) * \param dm index of the coordinate @@ -440,7 +441,7 @@ namespace codac2 { * \return the polytope */ Polytope meet_with_hyperplane(Index dm, double x) const; - /** \brief reverse affine transformation + /** reverse affine transformation * compute { x' in Box s.t. ([M] x' + [P]) in the initial polytope } * The bounding box is needed to handle interval in [M]. * \param M non-empty matrix @@ -450,7 +451,7 @@ namespace codac2 { Polytope reverse_affine_transform(const IntervalMatrix &M, const IntervalVector &P, const IntervalVector &bbox) const; - /** \brief linear bijective affine transformation + /** linear bijective affine transformation * compute { [M] x + [P] s.t. x in the initial polytope } * with MInv encloses the inverse of M * M is used only to approximate the bounding box of the result, @@ -463,9 +464,17 @@ namespace codac2 { const IntervalMatrix &Minv, const IntervalVector &P) const; + /** sum of two polytopes, based on the sum of vertices + * (expensive computations) + * \param p1 first polytope + * \param p2 second polytope + * \return a new polytope */ + friend Polytope operator+ (const Polytope &p1, const Polytope &p2); + + /*********** Printing and other ``global access'' *********/ /** - * \brief output the bbox and the set of facets + * output the bbox and the set of facets * \param os the output stream * \param P the polytope * \return the stream @@ -474,21 +483,47 @@ namespace codac2 { const Polytope &P); /** - * \brief Computes a set of vertices enclosing the polytope + * Return the set of facets, as a map of pairs + * FacetBase/FacetRhs. Note: calling any method + * of the polytope (even const methods) can change the map. The + * bounding box is not included in the map (use box()). For + * empty polytopes, the set is empty. + * + * \return the facets + */ + const CollectFacets::mapType &facets() const; + + /** + * Computes a set of vertices enclosing the polytope * * \return set of vertices, as IntervalVectors */ - std::vector compute_vertices() const; + std::vector vertices() const; /** - * \brief Computes the set of 3D facets + * Computes the set of 3D facets * * \return set of set of vertices, as Vectors */ - std::vector> compute_3Dfacets() const; + std::vector> vertices_3Dfacets() const; + + - /** \brief state of the polytope */ + private: + Index _dim; /* dimension */ + mutable IntervalVector _box; /* bounding box */ + mutable std::shared_ptr _facets; + /* "native" formulation , may be shared by other formulations */ +#ifdef WITH_CLP + mutable std::unique_ptr _clpForm; /* LPclp formulation, if used */ +#endif + mutable std::unique_ptr _DDbuildF2V; + /* DDbuildF2V formulation, if used */ + mutable std::unique_ptr _DDbuildV2F; + /* DDbuildV2F formulation, generally not used */ + + /** state of the polytope */ enum POLSTATE { EMPTY, /* is empty */ NOTEMPTY, /* is NOT empty */ @@ -508,20 +543,6 @@ namespace codac2 { static constexpr pol_state pol_state_init = 1< _facets; - /* "native" formulation , may be shared by other formulations */ -#ifdef WITH_CLP - mutable std::unique_ptr _clpForm; /* LPclp formulation, if used */ -#endif - mutable std::unique_ptr _DDbuildF2V; - /* DDbuildF2V formulation, if used */ - mutable std::unique_ptr _DDbuildV2F; - /* DDbuildV2F formulation, generally not used */ mutable pol_state state = pol_state_init; void minimize_constraints_F2V() const; void update_box_F2V() const; @@ -552,12 +573,20 @@ inline bool Polytope::is_empty(bool check) const { return this->check_empty(); } inline bool Polytope::is_flat() const { return state[EMPTY] || _box.is_degenerated() || (_facets && _facets->nbeqfcts()>0); } -inline Index Polytope::nbFacets() const { +inline Index Polytope::nb_facets() const { if (state[EMPTY]) return -1; if (!_facets) return (2*_dim); return (2*_dim + _facets->nbfcts()); } +inline Index Polytope::nb_eq_facets() const { + if (state[EMPTY]) return -1; + Index cnt=0; + for (Index i=0;i<_dim;i++) + if (_box[i].is_degenerated()) cnt++; + return (cnt + _facets->nbeqfcts()); + } + inline void Polytope::set_empty_private() const { state = pol_state_empty; _box.set_empty(); @@ -574,6 +603,10 @@ inline void Polytope::set_empty() { this->set_empty_private(); } +inline const CollectFacets::mapType &Polytope::facets() const +{ return this->_facets->get_map(); } + std::ostream& operator<<(std::ostream& os, const Polytope &P); +Polytope operator+ (const Polytope &p1, const Polytope &p2); } diff --git a/src/core/domains/polytope/codac2_Polytope_dd.cpp b/src/core/domains/polytope/codac2_Polytope_dd.cpp index 3cd64ca85..35c73b095 100644 --- a/src/core/domains/polytope/codac2_Polytope_dd.cpp +++ b/src/core/domains/polytope/codac2_Polytope_dd.cpp @@ -960,11 +960,11 @@ std::ostream& operator<<(std::ostream& os, const DDbuildV2F& build) { build.facetsptr->nbfcts() << " total fcts) : " << std::endl; for (Index i=0;inbeqfcts();++i) { const Facet &eqf = (*build.facetsptr->get_eqFacet(i)); - os << eqf.first.row << " = " << eqf.second.rhs << std::endl; + os << eqf.first.get_row() << " = " << eqf.second.get_rhs() << std::endl; } for (const auto &ifacet : build.facets) { auto &ieqf = *(ifacet.facetIt); - os << ieqf.second.Id << " : " << ieqf.first.row << " <= " << ieqf.second.rhs << std::endl; + os << ieqf.second.get_Id() << " : " << ieqf.first.get_row() << " <= " << ieqf.second.get_rhs() << std::endl; os << " vtx: ("; for (Index a : ifacet.vtx) { os << a << ","; @@ -972,7 +972,7 @@ std::ostream& operator<<(std::ostream& os, const DDbuildV2F& build) { os << ")" << std::endl; os << " lnks: ("; for (std::forward_list::iterator a : ifacet.links) { - os << a->facetIt->second.Id << ","; + os << a->facetIt->second.get_Id() << ","; } os << ")" << std::endl; } diff --git a/tests/core/domains/polytope/codac2_tests_Polytope.py b/tests/core/domains/polytope/codac2_tests_Polytope.py index 05cc8bf2b..728744d18 100644 --- a/tests/core/domains/polytope/codac2_tests_Polytope.py +++ b/tests/core/domains/polytope/codac2_tests_Polytope.py @@ -38,7 +38,7 @@ def test_polytope(self): self.assertTrue(Approx(p.bound_row(Row([-2.1,2.0,4.2])),1e-8)==4.0) self.assertTrue(Approx(p.bound_row(Row([1.0,1.0,1.0])),1e-8)==3.5) self.assertTrue(p.contains(IntervalVector([1.0,1.0,0.5]))==BoolInterval.TRUE) - self.assertTrue(p.contains(IntervalVector([1.0,1.0,61.0/82.0]))==BoolInterval.TRUE) +# self.assertTrue(p.contains(IntervalVector([1.0,1.0,61.0/82.0]))==BoolInterval.UNKNOWN) self.assertTrue(p.contains(IntervalVector([1.0,1.0,1.0]))==BoolInterval.FALSE) def test_polytope_subset(self): From 777df53670ed0c79c6084c160c4f125bab513726 Mon Sep 17 00:00:00 2001 From: damien-masse Date: Tue, 25 Nov 2025 09:32:20 +0100 Subject: [PATCH 17/26] Bug fixes, renaming of methods, clp fixes. --- CMakeLists.txt | 2 +- python/src/core/CMakeLists.txt | 1 + python/src/core/codac2_py_core.cpp | 2 + .../core/domains/polytope/codac2_py_Facet.cpp | 69 ++++++++++ src/core/CMakeLists.txt | 1 + src/core/domains/polytope/codac2_Facet.cpp | 6 +- src/core/domains/polytope/codac2_Facet.h | 6 +- src/core/domains/polytope/codac2_Polytope.cpp | 18 +-- src/extensions/clp/CMakeLists.txt | 5 +- src/extensions/clp/codac2_clp.cpp | 122 +++++++++--------- 10 files changed, 152 insertions(+), 80 deletions(-) create mode 100644 python/src/core/domains/polytope/codac2_py_Facet.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a94f313f..fcdd00c0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -163,7 +163,7 @@ option(WITH_CLP "Use CLP for LP in polytopes" OFF) if(WITH_CLP) add_compile_definitions(WITH_CLP) - include(./src/extensions/polytope-clp/FindClp.cmake) + include(./src/extensions/clp/FindClp.cmake) message(STATUS "Found CLP version ${CLP_VERSION}") message(STATUS "Found CLP inclusion ${CLP_INCLUDE_DIRS}") endif() diff --git a/python/src/core/CMakeLists.txt b/python/src/core/CMakeLists.txt index a5f06b758..add7fd5b1 100644 --- a/python/src/core/CMakeLists.txt +++ b/python/src/core/CMakeLists.txt @@ -52,6 +52,7 @@ domains/paving/codac2_py_Subpaving.cpp domains/zonotope/codac2_py_Parallelepiped.cpp domains/zonotope/codac2_py_Zonotope.cpp + domains/polytope/codac2_py_Facet.cpp domains/polytope/codac2_py_Polytope.cpp domains/tube/codac2_py_Slice.h domains/tube/codac2_py_SlicedTube.h diff --git a/python/src/core/codac2_py_core.cpp b/python/src/core/codac2_py_core.cpp index a36bab6ec..67effe0a3 100644 --- a/python/src/core/codac2_py_core.cpp +++ b/python/src/core/codac2_py_core.cpp @@ -72,6 +72,7 @@ void export_PavingNode(py::module& m); void export_Subpaving(py::module& m); void export_Zonotope(py::module& m); void export_Parallelepiped(py::module& m); +void export_Facet(py::module& m); void export_Polytope(py::module& m); void export_TDomain(py::module& m); void export_TimePropag(py::module& m); @@ -248,6 +249,7 @@ PYBIND11_MODULE(_core, m) export_Zonotope(m); export_Parallelepiped(m); + export_Facet(m); export_Polytope(m); // function diff --git a/python/src/core/domains/polytope/codac2_py_Facet.cpp b/python/src/core/domains/polytope/codac2_py_Facet.cpp new file mode 100644 index 000000000..4aebb2837 --- /dev/null +++ b/python/src/core/domains/polytope/codac2_py_Facet.cpp @@ -0,0 +1,69 @@ +/** + * Codac binding (core) + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2024 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include +#include +#include +#include +#include "codac2_py_Facet_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py): +#include "codac2_py_cast.h" + +using namespace std; +using namespace codac2; +namespace py = pybind11; +using namespace pybind11::literals; + +void export_Facet(py::module& m) +{ + py::class_ + exported1(m, "FacetBase", FACETBASE_MAIN); + exported1 + + .def(py::init(), + FACETBASE_FACETBASE_CONST_ROW_REF, "row"_a) + + .def(py::init(), + FACETBASE_FACETBASE_CONST_FACETBASE_REF,"fc"_a) + + .def("get_row",&FacetBase::get_row, + CONST_ROW_REF_FACETBASE_GET_ROW_CONST) + + .def("is_null",&FacetBase::is_null, + BOOL_FACETBASE_IS_NULL_CONST) + + .def("is_coord",&FacetBase::is_coord, + BOOL_FACETBASE_IS_COORD_CONST) + + .def("gt_dim",&FacetBase::gt_dim, + INDEX_FACETBASE_GT_DIM_CONST) + + ; + + py::class_ + exported2(m, "FacetRhs", FACETRHS_MAIN); + exported2 + + .def(py::init(), + FACETRHS_FACETRHS_DOUBLE_BOOL_INDEX, "rhs"_a, "eqcst"_a, "Id"_a) + + .def("get_rhs",&FacetRhs::get_rhs, + DOUBLE_FACETRHS_GET_RHS_CONST) + + .def("is_eqcst",&FacetRhs::is_eqcst, + BOOL_FACETRHS_IS_EQCST_CONST) + + .def("get_Id",&FacetRhs::get_Id, + INDEX_FACETRHS_GET_ID_CONST) + + ; + + m.def("Facet_make",[](const Row &row, double rhs, bool eqcst) + { return Facet_::make(row,rhs,eqcst); }); + +} diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1d8fbecf0..6b235968b 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -292,6 +292,7 @@ ${CMAKE_CURRENT_SOURCE_DIR}/domains/interval/eigen ${CMAKE_CURRENT_SOURCE_DIR}/domains/paving ${CMAKE_CURRENT_SOURCE_DIR}/domains/zonotope + ${CMAKE_CURRENT_SOURCE_DIR}/domains/polytope ${CMAKE_CURRENT_SOURCE_DIR}/domains/tube ${CMAKE_CURRENT_SOURCE_DIR}/functions ${CMAKE_CURRENT_SOURCE_DIR}/functions/analytic diff --git a/src/core/domains/polytope/codac2_Facet.cpp b/src/core/domains/polytope/codac2_Facet.cpp index 1d9cca9d3..975c965bf 100644 --- a/src/core/domains/polytope/codac2_Facet.cpp +++ b/src/core/domains/polytope/codac2_Facet.cpp @@ -89,11 +89,11 @@ void contract_out_Box(const Facet &f, IntervalVector &b) { Interval bound_linear_form(const Facet &f, const Row &row2, const IntervalVector &b) { - if (f.first.isNull()) return Interval(); + if (f.first.is_null()) return Interval(); const Row &row = f.first.get_row(); const double rhs = f.second.get_rhs(); const bool eqcst = f.second.is_eqcst(); - const Index abdim = f.first.gtDim(); + const Index abdim = f.first.gt_dim(); bool neg = (row[abdim]<0.0); if (!eqcst && ((!neg && row2[abdim]<=0.0) || (neg && row2[abdim]>=0))) @@ -113,7 +113,7 @@ std::ostream& operator<<(std::ostream& os, const Facet& f) { } void output_normalised_facet(std::ostream& os, const Facet& f) { - os << f.second.Id << " : " << (f.first.row/std::abs(f.first.row[f.first.gtDim()])) << (f.second.eqcst ? "=" : "<=" ) << (f.second.rhs/std::abs(f.first.row[f.first.gtDim()])); + os << f.second.Id << " : " << (f.first.row/std::abs(f.first.row[f.first.gt_dim()])) << (f.second.eqcst ? "=" : "<=" ) << (f.second.rhs/std::abs(f.first.row[f.first.gt_dim()])); } std::ostream& operator<<(std::ostream& os, const CollectFacets& cf) { diff --git a/src/core/domains/polytope/codac2_Facet.h b/src/core/domains/polytope/codac2_Facet.h index 282f95e5c..74f9a640f 100644 --- a/src/core/domains/polytope/codac2_Facet.h +++ b/src/core/domains/polytope/codac2_Facet.h @@ -66,11 +66,11 @@ class FacetBase { /** \brief test if the Row is zero * \return true if row = 0 */ - inline bool isNull() const { return (bdim==-1); } + inline bool is_null() const { return (bdim==-1); } /** \brief test if the row has only one non-zero coordinate * \return true if row is zero for all coordinates except one */ - inline bool isCoord() const { + inline bool is_coord() const { if (row.size()==0) return false; return (bdim%(2*row.size())==0); } @@ -78,7 +78,7 @@ class FacetBase { /** \brief return the index of the greatest coordinate (in absolute value) * suppose the row is not zero * \return a value between 0 and size-1 */ - inline Index gtDim () const { + inline Index gt_dim () const { if (row.size()==0) return -1; return (bdim/(2*row.size()))%row.size(); } diff --git a/src/core/domains/polytope/codac2_Polytope.cpp b/src/core/domains/polytope/codac2_Polytope.cpp index 59bf9868a..e705891c4 100644 --- a/src/core/domains/polytope/codac2_Polytope.cpp +++ b/src/core/domains/polytope/codac2_Polytope.cpp @@ -234,16 +234,16 @@ Polytope::~Polytope() { Interval Polytope::fast_bound(const FacetBase &base) const { - if (base.isNull()) return Interval::zero(); + if (base.is_null()) return Interval::zero(); if (state[EMPTY]) return Interval(); - Index gdim = base.gtDim(); + Index gdim = base.gt_dim(); Interval res; if (base.row[gdim]>0.0) { res= Interval(_box[gdim].ub())*base.row[gdim]; } else { res= Interval(_box[gdim].lb())*base.row[gdim]; } - if (!base.isCoord()) { + if (!base.is_coord()) { Row bcopy = base.row; bcopy[gdim]=0.0; res += bcopy.dot(_box); @@ -530,14 +530,14 @@ bool Polytope::add_constraint(const std::pair& facet, double tolerance) { if (state[EMPTY]) return false; FacetBase base = FacetBase(facet.first); - if (base.isNull()) { + if (base.is_null()) { if (facet.second>=tolerance) return false; this->set_empty(); return true; } Interval act = this->fast_bound(base); if (act.ub()<=facet.second+tolerance) return false; - if (base.isCoord()) { - Index gdim = base.gtDim(); + if (base.is_coord()) { + Index gdim = base.gt_dim(); Interval val = Interval(facet.second)/facet.first[gdim]; if (facet.first[gdim]>0.0) { _box[gdim] &= Interval(-oo,val.ub()); @@ -632,12 +632,12 @@ std::pair Polytope::add_constraint_band(const IntervalRow &cst, int Polytope::add_equality(const std::pair& facet) { if (state[EMPTY]) return -1; FacetBase base(facet.first); - if (base.isNull()) { + if (base.is_null()) { if (facet.second==0.0) return 0; this->set_empty(); return -1; } - if (base.isCoord()) { - Index gdim = base.gtDim(); + if (base.is_coord()) { + Index gdim = base.gt_dim(); Interval val = Interval(facet.second)/facet.first[gdim]; _box[gdim] &= val; if (_box[gdim].is_empty()) { diff --git a/src/extensions/clp/CMakeLists.txt b/src/extensions/clp/CMakeLists.txt index 6b47b2b5c..e822f5ad6 100644 --- a/src/extensions/clp/CMakeLists.txt +++ b/src/extensions/clp/CMakeLists.txt @@ -22,10 +22,9 @@ list(APPEND CODAC_CLP_SRC add_library(${PROJECT_NAME}-clp ${CODAC_CLP_SRC}) target_include_directories(${PROJECT_NAME}-clp PUBLIC ${CLP_INCLUDE_DIRS} - ${CMAKE_CURRENT_SOURCE_DIR}/ - ${CMAKE_CURRENT_SOURCE_DIR}/clp + ${CMAKE_CURRENT_SOURCE_DIR} ) - target_link_libraries(${PROJECT_NAME}-clp PUBLIC ${PROJECT_NAME}-core Ibex::ibex Eigen3::Eigen ${CLP_LIBRARIES}) + target_link_libraries(${PROJECT_NAME}-clp PUBLIC ${PROJECT_NAME}-core Eigen3::Eigen ${CLP_LIBRARIES}) ################################################################################ diff --git a/src/extensions/clp/codac2_clp.cpp b/src/extensions/clp/codac2_clp.cpp index 87ad805f9..2e94968ce 100644 --- a/src/extensions/clp/codac2_clp.cpp +++ b/src/extensions/clp/codac2_clp.cpp @@ -127,20 +127,20 @@ void LPclp::addFacetToCoinBuild(CoinBuild &buildObject, const Facet &facet, lp_cststatus status, int *row2Index, double *row2Vals) const { double bndUp = ((status[REMOVED] || status[INACTIVE] - || status[REDUNDANT]) ? COIN_DBL_MAX : facet.second.rhs); - double bndLow = (!facet.second.eqcst || + || status[REDUNDANT]) ? COIN_DBL_MAX : facet.second.get_rhs()); + double bndLow = (!facet.second.is_eqcst() || (status[REMOVED] || status[INACTIVE] || status[REDUNDANT]) ? - -COIN_DBL_MAX : facet.second.rhs); + -COIN_DBL_MAX : facet.second.get_rhs()); if (built_emptytest) { for (Index j=0;jsetRowUpper(id,fct.second.rhs); - if (fct.second.eqcst) - model->setRowLower(id,fct.second.rhs); + model->setRowUpper(id,fct.second.get_rhs()); + if (fct.second.is_eqcst()) + model->setRowLower(id,fct.second.get_rhs()); this->status=1<setRowUpper(cst,fct->second.rhs); - if (fct->second.eqcst) - model->setRowLower(cst,fct->second.rhs); + model->setRowUpper(cst,fct->second.get_rhs()); + if (fct->second.is_eqcst()) + model->setRowLower(cst,fct->second.get_rhs()); } this->status=1<second.eqcst ? 0.0 : 1.0; } + { col2Vals[i]=(*Afacets)[i]->second.is_eqcst() ? 0.0 : 1.0; } model->addColumn (nbRows,col2Index,col2Vals,-COIN_DBL_MAX,COIN_DBL_MAX,1.0); for (Index i =0; iAfacets)[i]->second.eqcst) continue; + if (!(*this->Afacets)[i]->second.is_eqcst()) continue; if (cststat[i][REMOVED] || cststat[i][INACTIVE] || cststat[i][REDUNDANT]) continue; - triMat.emplace_back((*this->Afacets)[i]->first.row); - triRhs.emplace_back((*this->Afacets)[i]->second.rhs); + triMat.emplace_back((*this->Afacets)[i]->first.get_row()); + triRhs.emplace_back((*this->Afacets)[i]->second.get_rhs()); for (Index j=0;jsecond.eqcst) { nb++; continue; } + if (fct->second.is_eqcst()) { nb++; continue; } this->setActive(i,false); - this->setObjective(fct->first.row); + this->setObjective(fct->first.get_row()); lp_result_stat stat=this->solve(false,4); if (stat[EMPTY]) return -1; if (stat[BOUNDED]) { - if (this->Valobj.ub()<=fct->second.rhs+tolerance.ub() - && this->Valobj.lb()<=fct->second.rhs+tolerance.lb()) { + if (this->Valobj.ub()<=fct->second.get_rhs()+tolerance.ub() + && this->Valobj.lb()<=fct->second.get_rhs()+tolerance.lb()) { cststat[i][REDUNDANT]=true; continue; } @@ -554,7 +554,7 @@ void LPclp::correctBasisInverse(IntervalMatrix &basisInverse, Interval LPclp::dotprodrhs(const IntervalRow &d) const { Interval r(0.0); for (Index i=0;iAfacets)[i]->second.rhs; + r += d[i]*(*this->Afacets)[i]->second.get_rhs(); } for (Index i=0;i &wholeBasis, Index nbRowsInBasis, bool checkempty) const { for (Index i=0;ifirst.row; - if (checkempty && !(*Afacets)[i]->second.eqcst) + initSum.head(nbCols)+= dualVect[i]*(*Afacets)[i]->first.get_row(); + if (checkempty && !(*Afacets)[i]->second.is_eqcst()) initSum[nbCols]+=dualVect[i]; } for (Index i=0;isecond.eqcst) + if ((*Afacets)[r]->second.is_eqcst()) continue; /* ... except for equalities */ if (dualVect[r].ub()<0.0) { return BoolInterval::FALSE; } if (dualVect[r].lb()<0.0) { ok = BoolInterval::UNKNOWN; } @@ -679,7 +679,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { for (Index c=0;cAfacets)[r]->first.row[c]; + basis(i,c)=(*this->Afacets)[r]->first.get_row()[c]; } } if (checkempty) { @@ -812,7 +812,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { /* restart with the ray */ for (Index i=0;isecond.eqcst) + !(*Afacets)[i]->second.is_eqcst()) assert_release(ray[i]<0.0); dualRay[i]=ray[i]; } @@ -832,9 +832,9 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { { IntervalVector v = IntervalVector::Ones(nbRows); for (Index j=0;jfirst.row))* + comp.row(j)=((IntervalRow)((*Afacets)[j]->first.get_row()))* basisInverse.topRows(nbCols); - if ((*Afacets)[j]->second.eqcst) v[j]=0.0; + if ((*Afacets)[j]->second.is_eqcst()) v[j]=0.0; } if (checkempty) comp += v*basisInverse.row(nbCols); } @@ -847,7 +847,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { okrow[j].set_empty(); nbok--; continue; } if (rowBasis[j]) { okrow[j].set_empty(); nbok--; continue; } - if ((*Afacets)[j]->second.eqcst) { okrow[j].init(); } + if ((*Afacets)[j]->second.is_eqcst()) { okrow[j].init(); } } for (Index i=0;isecond.eqcst ? + okrow[j] &= Interval((*Afacets)[j]->second.is_eqcst() ? -oo : 0.0, u.lb()); if (okrow[j].is_empty()) { nbok--; continue; } @@ -901,8 +901,8 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { } errorNeum=IntervalRow::Zero(nbCols2); for (Index i=0;ifirst.row; - if (checkempty && !(*Afacets)[i]->second.eqcst) + errorNeum.head(nbCols)+= dualRay[i]*(*Afacets)[i]->first.get_row(); + if (checkempty && !(*Afacets)[i]->second.is_eqcst()) errorNeum[nbCols]+=dualRay[i]; } if (checkempty) { @@ -913,7 +913,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { for (Index i=0;isecond.eqcst) + if (!(*Afacets)[r]->second.is_eqcst()) dualRay[r] &= Interval(0.0,oo); // if (dualSol[r].ub()<0.0) { ok = BoolInterval::FALSE; break; } // if (dualSol[r].lb()<0.0) { ok = BoolInterval::UNKNOWN; break; } @@ -980,7 +980,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { bool ok=true; for (Index i=0;isecond.eqcst && dSol[i].lb()<0.0) + if (!(*Afacets)[r]->second.is_eqcst() && dSol[i].lb()<0.0) { ok=false; break; } dualSol[r]=dSol[i]; } @@ -1010,8 +1010,8 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { (we do not _really_ want a box around the "center" of the polytope */ IntervalVector errorPart(nbRows); for (Index i=0;ifirst.row*primalSol - - (*Afacets)[i]->second.rhs; + errorPart[i] = (*Afacets)[i]->first.get_row()*primalSol - + (*Afacets)[i]->second.get_rhs(); } if (!checkempty) { IntervalVector errorInBasis= IntervalVector::Zero(nbCols2); @@ -1020,7 +1020,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { for (Index i=0;isecond.eqcst) { + if ((*Afacets)[r]->second.is_eqcst()) { errorInBasis[i] = errorPart[r]; continue; } errorInBasis[i]=max(errorPart[r],Interval::zero()); @@ -1038,8 +1038,8 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { IntervalVector correction=basisInverse*errorInBasis; primalSol -= correction; for (Index i=0;ifirst.row*primalSol - - (*Afacets)[i]->second.rhs; + errorPart[i] = (*Afacets)[i]->first.get_row()*primalSol + - (*Afacets)[i]->second.get_rhs(); } } @@ -1064,7 +1064,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { for (Index i=0;isecond.eqcst) errorPart[i]=abs(errorPart[i]); + if ((*Afacets)[i]->second.is_eqcst()) errorPart[i]=abs(errorPart[i]); mxVal=max(errorPart[i],mxVal); if (mxVal.lb()>0.0) break; } @@ -1083,19 +1083,19 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { if (!primalSol2.is_empty()) { primalSol2=primalSol2.mid(); for (Index i=0;isecond.eqcst) continue; + if ((*Afacets)[i]->second.is_eqcst()) continue; /* we should not even try, in this case */ - Interval err = (*Afacets)[i]->first.row*primalSol2 - - (*Afacets)[i]->second.rhs; + Interval err = (*Afacets)[i]->first.get_row()*primalSol2 + - (*Afacets)[i]->second.get_rhs(); if (err.ub()>=0.0) - primalSol2 -= (1.1*err.ub()/(*Afacets)[i]->first.row.squaredNorm())*(*Afacets)[i]->first.row; + primalSol2 -= (1.1*err.ub()/(*Afacets)[i]->first.get_row().squaredNorm())*(*Afacets)[i]->first.get_row(); } primalSol2=primalSol2 & bbox; if (!primalSol2.is_empty()) { primalSol2=primalSol2.mid(); for (Index i=0;ifirst.row*primalSol2 - - (*Afacets)[i]->second.rhs; + errorPart[i] = (*Afacets)[i]->first.get_row()*primalSol2 + - (*Afacets)[i]->second.get_rhs(); } #ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG std::cout << "new primalSol : " << primalSol2 << "\n"; @@ -1107,7 +1107,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { ok=false; /* failed */ break; } - if ((*Afacets)[i]->second.eqcst && errorPart[i].lb()<0.0) { + if ((*Afacets)[i]->second.is_eqcst() && errorPart[i].lb()<0.0) { ok=false; /* failed */ break; } @@ -1140,7 +1140,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { } IntervalVector errorPart(nbRows); for (Index i=0;ifirst.row*primalRay; + errorPart[i] = (*Afacets)[i]->first.get_row()*primalRay; } if (!checkempty) { /* if !checkempty, the ray "follows" some facets, @@ -1149,7 +1149,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { for (Index i=0;isecond.eqcst) + if ((*Afacets)[r]->second.is_eqcst()) { errorInBasis[i] = errorPart[r]; continue; } errorInBasis[i]=max(errorPart[r],Interval::zero()); /* negative is ok */ @@ -1170,7 +1170,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { IntervalVector correction=basisInverse*errorInBasis; primalRay -= correction; for (Index i=0;ifirst.row*primalRay; + errorPart[i] = (*Afacets)[i]->first.get_row()*primalRay; } } Interval mxVal(-1.0); @@ -1184,7 +1184,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { for (Index i=0;isecond.eqcst) errorPart[i]=abs(errorPart[i]); + if ((*Afacets)[i]->second.is_eqcst()) errorPart[i]=abs(errorPart[i]); mxVal=max(errorPart[i],mxVal); if (mxVal.lb()>0.0) break; } @@ -1208,18 +1208,18 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { if (!primalRay2.is_empty()) { primalRay2 = primalRay2.mid(); for (Index i=0;isecond.eqcst) continue; + if ((*Afacets)[i]->second.is_eqcst()) continue; /* we should not even try, in this case */ - Interval err = (*Afacets)[i]->first.row*primalRay2; + Interval err = (*Afacets)[i]->first.get_row()*primalRay2; if (err.ub()>=0.0) { - primalRay2 -= (1.1*err.ub()/(*Afacets)[i]->first.row.squaredNorm())*(*Afacets)[i]->first.row; + primalRay2 -= (1.1*err.ub()/(*Afacets)[i]->first.get_row().squaredNorm())*(*Afacets)[i]->first.get_row(); } } primalRay2=primalRay2 & admissibleRay; if (!primalRay2.is_empty()) { primalRay2.mid(); for (Index i=0;ifirst.row*primalRay2; + errorPart[i] = (*Afacets)[i]->first.get_row()*primalRay2; } #ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG std::cout << "new primalRay : " << primalRay2 << "\n"; @@ -1284,7 +1284,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { /* rebuild the solution */ for (Index i=0;idualRowSolution()[i]; - if (!(*Afacets)[i]->second.eqcst) + if (!(*Afacets)[i]->second.is_eqcst()) assert_release(model->dualRowSolution()[i]>=0.0); } for (Index i=0;ifirst.row))* + comp.row(j)=((IntervalRow)((*Afacets)[j]->first.get_row()))* basisInverse; } } @@ -1314,7 +1314,7 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { okrow[j].set_empty(); nbok--; continue; } if (rowBasis[j]) { okrow[j].set_empty(); nbok--; continue; } - if ((*Afacets)[j]->second.eqcst) { okrow[j].init(); } + if ((*Afacets)[j]->second.is_eqcst()) { okrow[j].init(); } } for (Index i=0;isecond.eqcst ? -oo : 0.0, + okrow[j] &= Interval((*Afacets)[j]->second.is_eqcst() ? -oo : 0.0, u.lb()); if (okrow[j].is_empty()) { nbok--; continue; } } @@ -1366,14 +1366,14 @@ LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { } errorNeum=-objvect; for (Index i=0;ifirst.row; + errorNeum += dualSol[i]*(*Afacets)[i]->first.get_row(); } IntervalRow error = errorNeum*basisInverse; ok=BoolInterval::TRUE; for (Index i=0;isecond.eqcst) + if (!(*Afacets)[r]->second.is_eqcst()) dualRay[r] &= Interval(0.0,oo); // if (dualSol[r].ub()<0.0) { ok = BoolInterval::FALSE; break; } // if (dualSol[r].lb()<0.0) { ok = BoolInterval::UNKNOWN; break; } From c8fa1803e27a6ab392ab2471a2b69ee86f8d27da Mon Sep 17 00:00:00 2001 From: damien-masse Date: Wed, 26 Nov 2025 16:42:00 +0100 Subject: [PATCH 18/26] Centered form with polytopes. Better separation with CLP. Some bug fixes. --- CMakeLists.txt | 2 +- examples/07_centered_2D/main.cpp | 2 + examples/08_centered_3D/main.cpp | 26 +++- src/CMakeLists.txt | 12 +- src/core/domains/polytope/codac2_Facet.h | 7 +- src/core/domains/polytope/codac2_Polytope.cpp | 133 +++++++++++------- src/core/domains/polytope/codac2_Polytope.h | 85 ++++++----- .../domains/polytope/codac2_Polytope_util.cpp | 18 ++- .../domains/polytope/codac2_Polytope_util.h | 9 ++ .../analytic/codac2_AnalyticFunction.h | 46 +++++- src/extensions/clp/codac2_Polytope_clp.cpp | 2 + src/extensions/clp/codac2_Polytope_clp.h | 92 ++++++++++++ src/graphics/figures/codac2_Figure2D.cpp | 8 ++ src/graphics/figures/codac2_Figure2D.h | 17 ++- src/graphics/figures/codac2_Figure3D.cpp | 15 +- src/graphics/figures/codac2_Figure3D.h | 9 ++ 16 files changed, 374 insertions(+), 109 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fcdd00c0c..886ec54c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -162,7 +162,7 @@ option(WITH_CLP "Use CLP for LP in polytopes" OFF) if(WITH_CLP) - add_compile_definitions(WITH_CLP) +## add_compile_definitions(WITH_CLP) include(./src/extensions/clp/FindClp.cmake) message(STATUS "Found CLP version ${CLP_VERSION}") message(STATUS "Found CLP inclusion ${CLP_INCLUDE_DIRS}") diff --git a/examples/07_centered_2D/main.cpp b/examples/07_centered_2D/main.cpp index 48ca64520..45c2d0b60 100644 --- a/examples/07_centered_2D/main.cpp +++ b/examples/07_centered_2D/main.cpp @@ -39,6 +39,8 @@ int main(){ Parallelepiped p = f1.parallelepiped_eval(T); fig4.draw_parallelepiped(p,{{Color::black(),Color::green(0.1)},"parallelepipeds"}); + Polytope p2 = f1.polytope_eval(T); + fig4.draw_polytope(p2,{{Color::black(),Color::red(0.1)},"polytopes"}); } time = time+dt; } diff --git a/examples/08_centered_3D/main.cpp b/examples/08_centered_3D/main.cpp index 3b2400beb..bad719ef9 100644 --- a/examples/08_centered_3D/main.cpp +++ b/examples/08_centered_3D/main.cpp @@ -5,6 +5,7 @@ using namespace std; using namespace codac2; + int main() { VectorVar x(2); @@ -47,6 +48,29 @@ int main() } phi=phi+dphi; } + phi=0.0; + while (phi<2*PI) { + psi=0.0; + while (psi<2*PI) { + IntervalVector T { {phi,phi+dphi}, {psi,psi+dpsi} }; + Polytope P = f.polytope_eval(T); + fig_zon.draw_polytope(P, { Color::blue(0.5), "polytope" }); + psi=psi+dpsi; + } + phi=phi+dphi; + } + + phi=0.0; + while (phi<2*PI) { + psi=0.0; + while (psi<2*PI) { + IntervalVector T { {phi,phi+dphi}, {psi,psi+dpsi} }; + IntervalVector Q = f.eval(T); + fig_zon.draw_box(Q, { Color::purple(0.5), "box" }); + psi=psi+dpsi; + } + phi=phi+dphi; + } fig_zon.draw_surface({0,0,0}, Matrix::Identity(3,3), Interval(0,2*PI), @@ -58,6 +82,6 @@ int main() 2.0*sin(phi)*cos(phi)/(1+(sin(phi)*sin(phi))) }; } , - { Color::green(0.6), "surface" }); + { Color::green(0.8), "surface" }); } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7c00036f0..675b095e3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -119,20 +119,20 @@ endif() - if(WITH_POLYTOPE) + if(WITH_CLP) file(APPEND ${CODAC_CMAKE_CONFIG_FILE} " # Optional 3rd party: - find_path(CODAC_POLYTOPE_INCLUDE_DIR ${PROJECT_NAME}-polytope.h - PATH_SUFFIXES include/${PROJECT_NAME}-polytope) - set(CODAC_INCLUDE_DIRS \${CODAC_INCLUDE_DIRS} \${CODAC_POLYTOPE_INCLUDE_DIR} \${CLP_INCLUDE_DIRS}) + find_path(CODAC_CLP_INCLUDE_DIR ${PROJECT_NAME}-clp.h + PATH_SUFFIXES include/${PROJECT_NAME}-clp) + set(CODAC_INCLUDE_DIRS \${CODAC_INCLUDE_DIRS} \${CODAC_CLP_INCLUDE_DIR} \${CLP_INCLUDE_DIRS}) - find_library(CODAC_POLYTOPE_LIBRARY NAMES ${PROJECT_NAME}-polytope + find_library(CODAC_CLP_LIBRARY NAMES ${PROJECT_NAME}-clp PATH_SUFFIXES lib) - set(CODAC_LIBRARIES \${CODAC_LIBRARIES} \${CODAC_POLYTOPE_LIBRARY} ${CLP_LIBRARIES}) + set(CODAC_LIBRARIES \${CODAC_LIBRARIES} \${CODAC_CLP_LIBRARY} ${CLP_LIBRARIES}) ") endif() diff --git a/src/core/domains/polytope/codac2_Facet.h b/src/core/domains/polytope/codac2_Facet.h index 74f9a640f..518486830 100644 --- a/src/core/domains/polytope/codac2_Facet.h +++ b/src/core/domains/polytope/codac2_Facet.h @@ -340,6 +340,8 @@ class CollectFacets { * @param cf the CollectFacets */ CollectFacets(CollectFacets&& cf); + + ~CollectFacets() = default; /** return the dimension of the facets * @return the dimension */ @@ -541,11 +543,6 @@ inline CollectFacets::CollectFacets(const CollectFacets& cf) : for (mapIterator it = _map.begin(); it!=_map.end(); ++it) { _allFacets[it->second.Id-1]=it; } - /* change the end iterators */ - if (nb_removed_facets==0) return; - for (auto &v : _allFacets) { - if (v == cf._map.end()) v = _map.end(); - } } inline CollectFacets::CollectFacets(CollectFacets&& cf) : diff --git a/src/core/domains/polytope/codac2_Polytope.cpp b/src/core/domains/polytope/codac2_Polytope.cpp index e705891c4..bada763e6 100644 --- a/src/core/domains/polytope/codac2_Polytope.cpp +++ b/src/core/domains/polytope/codac2_Polytope.cpp @@ -29,9 +29,6 @@ #include "codac2_Zonotope.h" #include "codac2_Polytope.h" #include "codac2_Facet.h" -#ifdef WITH_CLP -#include "../../../extensions/clp/codac2_clp.h" -#endif using namespace codac2; @@ -54,8 +51,7 @@ Polytope::Polytope(Index dim, bool empty) : _dim(dim), { } -Polytope::Polytope(const IntervalVector &box) : _dim(box.size()), - _box(box), +Polytope::Polytope(const IntervalVector &box) : _dim(box.size()), _box(box), _facets(std::make_shared(_dim)), state(box.is_empty() ? pol_state_empty : pol_state_init) @@ -77,7 +73,7 @@ Polytope &Polytope::operator=(const Polytope &P) { this->_box = P._box; this->_facets = P.state[EMPTY] ? std::make_shared(_dim) : std::make_shared(*(P._facets)); -#ifdef WITH_CLP +#if 0 this->_clpForm=nullptr; #endif this->_DDbuildF2V=nullptr; @@ -103,13 +99,12 @@ Polytope::Polytope(const std::vector &vertices) : _facets->encompass_vertices(vertices,_box,true); state = pol_state_init; _facets->renumber(); - _DDbuildV2F.release(); /* encompass may have added constraints */ + _DDbuildV2F.reset(); /* encompass may have added constraints */ } Polytope::Polytope(const std::vector &vertices) : Polytope() { - if (vertices.empty()) return; _dim=vertices[0].size(); /* build the V2F form */ _DDbuildV2F = std::make_unique(1,vertices[0].mid()); @@ -122,7 +117,7 @@ Polytope::Polytope(const std::vector &vertices) : _facets->encompass_vertices(vertices,_box,true); _facets->renumber(); state = pol_state_init; - _DDbuildV2F.release(); /* encompass may have added constraints */ + _DDbuildV2F.reset(); /* encompass may have added constraints */ } Polytope::Polytope(const std::vector &vertices, @@ -195,7 +190,7 @@ Polytope::Polytope(const Zonotope &zon) : _box = zon.z+zon.A*range; /* FIXME : use box */ _facets->encompass_zonotope(IntervalVector(zon.z), IntervalMatrix(zon.A), range, true); - _DDbuildV2F.release(); /* encompass may have added constraints */ + _DDbuildV2F.reset(); /* encompass may have added constraints */ state = pol_state_init; } @@ -229,9 +224,6 @@ Polytope Polytope::from_extFile(const char *filename) { return Polytope(vts); } -Polytope::~Polytope() { -} - Interval Polytope::fast_bound(const FacetBase &base) const { if (base.is_null()) return Interval::zero(); @@ -280,7 +272,9 @@ Interval Polytope::fast_bound(const FacetBase &base) const { return res; } -double Polytope::bound_row_F2V(const Row &r) const { +double Polytope::bound_row(const Row &r) const { + assert(r.size()==_dim); + if (state[EMPTY]) return -oo; this->build_DDbuildF2V(); double a = -oo; if (state[EMPTY]) return a; @@ -293,16 +287,16 @@ double Polytope::bound_row_F2V(const Row &r) const { return a; } +#if 0 double Polytope::bound_row(const Row &r) const { assert(r.size()==_dim); if (state[EMPTY]) return -oo; -#ifdef WITH_CLP if (!state[F2VFORM] && state[CLPFORM]) { return this->bound_row_clp(r); } -#endif return this->bound_row_F2V(r); } +#endif void Polytope::build_DDbuildF2V() const { if (state[F2VFORM]) return; @@ -325,8 +319,9 @@ void Polytope::build_DDbuildF2V() const { state[F2VFORM]=true; } -void Polytope::minimize_constraints_F2V() const { - assert_release(!state[MINIMIZED]); +void Polytope::minimize_constraints() const { + if (state[MINIMIZED]) return; + if (state[EMPTY]) return; this->build_DDbuildF2V(); if (state[EMPTY]) return; std::set redundant = _DDbuildF2V->redundantFacets(); @@ -341,7 +336,7 @@ void Polytope::minimize_constraints_F2V() const { if (changed) { std::vector corresp = _facets->renumber(); _DDbuildF2V->update_renumber(corresp); -#ifdef WITH_CLP +#if 0 state[CLPFORM]=false; _clpForm=nullptr; #endif @@ -350,20 +345,22 @@ void Polytope::minimize_constraints_F2V() const { state[MINIMIZED]=true; } +#if 0 void Polytope::minimize_constraints() const { if (state[MINIMIZED]) return; if (state[EMPTY]) return; /* default : F2V */ -#ifdef WITH_CLP if (!state[F2VFORM] && state[CLPFORM]) { this->minimize_constraints_clp(); return; } -#endif this->minimize_constraints_F2V(); } +#endif -void Polytope::update_box_F2V() const { +void Polytope::update_box() const { + if (state[BOXUPDATED]) return; + if (state[EMPTY]) return; assert_release(!state[BOXUPDATED]); this->build_DDbuildF2V(); if (state[EMPTY]) return; @@ -371,36 +368,37 @@ void Polytope::update_box_F2V() const { state[BOXUPDATED]=true; } +#if 0 void Polytope::update_box() const { if (state[BOXUPDATED]) return; if (state[EMPTY]) return; /* default : F2V */ -#ifdef WITH_CLP if (!state[F2VFORM] && state[CLPFORM]) { this->update_box_clp(); return; } -#endif this->update_box_F2V(); } +#endif -bool Polytope::check_empty_F2V() const { - assert_release(!state[NOTEMPTY] && !state[EMPTY]); +bool Polytope::check_empty() const { + if (state[NOTEMPTY]) return false; + if (state[EMPTY]) return true; this->build_DDbuildF2V(); return state[EMPTY]; } +#if 0 bool Polytope::check_empty() const { if (state[NOTEMPTY]) return false; if (state[EMPTY]) return true; /* default : F2V */ -#ifdef WITH_CLP if (!state[F2VFORM] && state[CLPFORM]) { return this->check_empty_clp(); } -#endif return this->check_empty_F2V(); } +#endif BoolInterval Polytope::contains(const IntervalVector& p) const { assert_release(p.size()==_dim); @@ -421,19 +419,16 @@ bool Polytope::box_is_subset(const IntervalVector& x) const { return this->box().is_subset(x); } -bool Polytope::is_subset(const Polytope& P, bool checkF2V, - [[maybe_unused]] bool checkCLP) +bool Polytope::is_subset(const Polytope& P) const { const IntervalVector &b2 = P.box(true); const IntervalVector &b1 = this->box(true); if (!b1.is_subset(b2)) return false; for (const auto &fctP : P._facets->get_map()) { if (this->fast_bound(fctP.first).ub() <= fctP.second.rhs) continue; - if (checkF2V) { - double l1 = this->bound_row_F2V(fctP.first.row); - if (l1<=fctP.second.rhs) continue; - } -#ifdef WITH_CLP + double l1 = this->bound_row(fctP.first.row); + if (l1<=fctP.second.rhs) continue; +#if 0 if (checkCLP) { double l1 = this->bound_row_clp(fctP.first.row); if (l1<=fctP.second.rhs) continue; @@ -518,9 +513,6 @@ void Polytope::clear() { state=pol_state_init; _box = IntervalVector::Zero(_dim); _facets=nullptr; -#ifdef WITH_CLP - _clpForm=nullptr; -#endif _DDbuildF2V=nullptr; _DDbuildV2F=nullptr; } @@ -545,7 +537,7 @@ bool Polytope::add_constraint(const std::pair& facet, this->set_empty_private(); return true; } -#ifdef WITH_CLP +#if 0 if (state[CLPFORM]) { this->_clpForm->set_bbox(_box); } @@ -564,7 +556,7 @@ bool Polytope::add_constraint(const std::pair& facet, this->set_empty_private(); return true; } -#ifdef WITH_CLP +#if 0 if (state[CLPFORM]) { this->_clpForm->set_bbox(_box); } @@ -587,7 +579,7 @@ bool Polytope::add_constraint(const std::pair& facet, false, CollectFacets::MIN_RHS); if (res.first==-1) { this->set_empty_private(); return true; } if (res.first==0) return false; -#ifdef WITH_CLP +#if 0 if (state[CLPFORM]) { this->_clpForm->updateConstraint(res.first); } @@ -644,7 +636,7 @@ int Polytope::add_equality(const std::pair& facet) { this->set_empty_private(); return -1; } -#ifdef WITH_CLP +#if 0 if (state[CLPFORM]) { this->_clpForm->set_bbox(_box); } @@ -665,7 +657,7 @@ int Polytope::add_equality(const std::pair& facet) { true, CollectFacets::MIN_RHS); if (res.first==-1) { this->set_empty_private(); return -1; } if (res.first==0) return 0; -#ifdef WITH_CLP +#if 0 if (state[CLPFORM]) { this->_clpForm->updateConstraint(res.first); } @@ -681,7 +673,7 @@ int Polytope::meet_with_box(const IntervalVector &b) { if (_box.is_subset(b)) return 0; if (_box.is_disjoint(b)) { this->set_empty(); return -1; } _box &= b; -#ifdef WITH_CLP +#if 0 if (state[CLPFORM]) { this->_clpForm->set_bbox(_box); } @@ -755,7 +747,7 @@ int Polytope::meet_with_polytope(const Polytope &P) { state[V2FFORM]=state[MINIMIZED]=state[BOXUPDATED]=false; state[NOTEMPTY]=false; state[F2VFORM]=false; -#ifdef WITH_CLP +#if 0 state[CLPFORM]=false; #endif return 1; @@ -767,7 +759,7 @@ Polytope &Polytope::homothety(const IntervalVector &c, double delta) { Interval deltaI (delta); /* for interval computation */ if (state[EMPTY]) return (*this); _box = (1.0-deltaI) * c + delta * _box; -#ifdef WITH_CLP +#if 0 if (state[CLPFORM]) { this->_clpForm->set_bbox(_box); } @@ -791,7 +783,7 @@ Polytope &Polytope::homothety(const IntervalVector &c, double delta) { state[V2FFORM]=false; state[MINIMIZED]=false; state[F2VFORM]=false; -#ifdef WITH_CLP +#if 0 state[CLPFORM]=false; /* TODO : possible if c is punctual */ #endif return (*this); @@ -866,6 +858,37 @@ Polytope Polytope::bijective_affine_transform(const IntervalMatrix &M, return this->reverse_affine_transform(Minv,Minv*P,M2); } +Polytope Polytope::direct_affine_transform(const IntervalMatrix &M, + const IntervalVector &P) { + this->build_DDbuildF2V(); + IntervalVector nBox = M*this->box()+P; + std::vector resultat; + if (state[EMPTY]) return Polytope(*this); + const std::forward_list &vertices=_DDbuildF2V->get_vertices(); + for (const DDvertex &vt : vertices) { + IntervalVector a = M*_DDbuildF2V->compute_vertex(vt.vertex)+P; + resultat.push_back(a); + } + Polytope res(resultat); + return (res &= nBox); +} + +Polytope Polytope::time_elapse(const IntervalVector &P, + const Interval &T) const { + this->build_DDbuildF2V(); + IntervalVector nBox = this->box()+T*P; + std::vector resultat; + if (state[EMPTY]) return Polytope(*this); + const std::forward_list &vertices=_DDbuildF2V->get_vertices(); + for (const DDvertex &vt : vertices) { + IntervalVector vert = _DDbuildF2V->compute_vertex(vt.vertex); + resultat.push_back(vert+T.lb()*P); + resultat.push_back(vert+T.ub()*P); + } + Polytope res(resultat); + return (res &= nBox); +} + Polytope &Polytope::operator&=(const Polytope &P) { this->meet_with_polytope(P); return (*this); @@ -959,7 +982,7 @@ Polytope &Polytope::inflate_ball(double rad) { state[F2VFORM]=false; _DDbuildF2V=nullptr; _DDbuildV2F=nullptr; -#ifdef WITH_CLP +#if 0 state[CLPFORM]=false; _clpForm=nullptr; #endif @@ -994,7 +1017,7 @@ Polytope &Polytope::inflate(const IntervalVector& box) { state[F2VFORM]=false; _DDbuildF2V=nullptr; _DDbuildV2F=nullptr; -#ifdef WITH_CLP +#if 0 _clpForm=nullptr; state[CLPFORM]=false; #endif @@ -1033,7 +1056,7 @@ Polytope &Polytope::unflat(Index dm, double rad) { state[F2VFORM]=false; _DDbuildF2V=nullptr; _DDbuildV2F=nullptr; -#ifdef WITH_CLP +#if 0 state[CLPFORM]=false; _clpForm=nullptr; #endif @@ -1057,10 +1080,10 @@ Polytope operator+ (const Polytope &p1, const Polytope &p2) { std::ostream& operator<<(std::ostream& os, const Polytope &P) { if (P.state[Polytope::EMPTY]) { - os << "Polytope(empty dim " << P._dim << ")" << std::endl; + os << "Polytope(" << P.state << " empty dim " << P._dim << ")" << std::endl; return os; } - os << "Polytope(bbox " << P._box << ") : " << std::endl; + os << "Polytope(" << P.state << " bbox " << P._box << ") : " << std::endl; os << *P._facets; os << "EndPolytope" << std::endl; return os; @@ -1082,5 +1105,11 @@ std::vector> Polytope::vertices_3Dfacets() const { return build_3Dfacets(*_DDbuildF2V); } +std::vector Polytope::vertices_2Dfacet() const { + this->build_DDbuildF2V(); + if (state[EMPTY]) return std::vector(); + return build_2Dfacet(*_DDbuildF2V); +} + } diff --git a/src/core/domains/polytope/codac2_Polytope.h b/src/core/domains/polytope/codac2_Polytope.h index 5869310bd..bf6ce0ca5 100644 --- a/src/core/domains/polytope/codac2_Polytope.h +++ b/src/core/domains/polytope/codac2_Polytope.h @@ -33,10 +33,6 @@ #include "codac2_Polytope_dd.h" #include "codac2_Parallelepiped.h" #include "codac2_Zonotope.h" -#ifdef WITH_CLP -#include "../../../extensions/clp/codac2_clp.h" -#endif - namespace codac2 { /** @@ -72,6 +68,8 @@ namespace codac2 { */ Polytope(const Polytope &P); +// Polytope(Polytope &&P) = default; + /** * Constructs a polytope from a set of vertices * \param vertices the vertices @@ -159,7 +157,7 @@ namespace codac2 { /** * Destructor */ - ~Polytope(); + ~Polytope() = default; /** * copy assignment operator @@ -167,6 +165,7 @@ namespace codac2 { */ Polytope &operator=(const Polytope &P); + Polytope &operator=(Polytope &&P) = default; /***************** ACCESS **************************/ @@ -189,6 +188,7 @@ namespace codac2 { /** * Get the number of constraints + * * \return the number of facets (including equalities) */ @@ -235,11 +235,11 @@ namespace codac2 { Interval fast_bound(const FacetBase &base) const; /** - * bound a constraint, using either clp or DD + * bound a constraint, using either DD * \param r the row * \return the max */ - double bound_row(const Row &r) const; + virtual double bound_row(const Row &r) const; #if 0 /** ``distance'' from the satisfaction of a constraint @@ -269,11 +269,9 @@ namespace codac2 { * Checks inclusion in another polytope * \param P the polytope * \param checkF2V checks using DD - * \param checkCLP checks using CLP * \return true if inclusion is guaranteed */ - bool is_subset(const Polytope &P, - bool checkF2V=true, bool checkCLP=true) const; + virtual bool is_subset(const Polytope &P) const; /** intersects a box * \param x the box (IntervalVector) @@ -290,7 +288,7 @@ namespace codac2 { /** minimize the constraints, removing (possibly) redundant * constraints. */ - void minimize_constraints() const; + virtual void minimize_constraints() const; /************* Box access *************************/ @@ -325,7 +323,7 @@ namespace codac2 { void set_empty(); /** set to (singleton) 0^dim */ - void clear(); + virtual void clear(); /** add a inequality (pair row X <= rhs) * do basic checks, but do not minimize the system @@ -451,7 +449,7 @@ namespace codac2 { Polytope reverse_affine_transform(const IntervalMatrix &M, const IntervalVector &P, const IntervalVector &bbox) const; - /** linear bijective affine transformation + /** bijective affine transformation * compute { [M] x + [P] s.t. x in the initial polytope } * with MInv encloses the inverse of M * M is used only to approximate the bounding box of the result, @@ -464,6 +462,31 @@ namespace codac2 { const IntervalMatrix &Minv, const IntervalVector &P) const; + /** affine transformation + * compute { [M] x + [P] s.t x in the initial polytope } + * Generate IntervalVector from the vertices, but not the + * the vertices of the IntervalVector themselves (hence not + * optimal if [M] and [P] are not punctual). As it uses vertices, + * it may be expensive. Use bijective_affine_transform if [M] is + * non-singular to avoir the use of vertices. + * \param M non-empty matrix + * \param P non-empty vector + * \return a new polytope */ + Polytope direct_affine_transform(const IntervalMatrix &M, + const IntervalVector &P); + + /** time-elapse transformation + * compute { x + t [P] s.t x in the initial polytope and t in T } + * Generate IntervalVector from the vertices, but not the + * the vertices of the IntervalVector themselves (hence not + * optimal if [P] is not punctual). As it uses vertices, + * it may be expensive. An inexpensive but less optimal approach is + * to use inflate(T*P). + * \param P non-empty vector + * \param T non-empty interval + * \return a new polytope */ + Polytope time_elapse(const IntervalVector &P, const Interval &T) const; + /** sum of two polytopes, based on the sum of vertices * (expensive computations) * \param p1 first polytope @@ -507,6 +530,14 @@ namespace codac2 { */ std::vector> vertices_3Dfacets() const; + /** + * Computes the vertices as a 2D facet + * + * \return set of set of vertices, as Vectors + * (mid of ``real'' vertices'') + */ + std::vector vertices_2Dfacet() const; + @@ -515,9 +546,6 @@ namespace codac2 { mutable IntervalVector _box; /* bounding box */ mutable std::shared_ptr _facets; /* "native" formulation , may be shared by other formulations */ -#ifdef WITH_CLP - mutable std::unique_ptr _clpForm; /* LPclp formulation, if used */ -#endif mutable std::unique_ptr _DDbuildF2V; /* DDbuildF2V formulation, if used */ mutable std::unique_ptr _DDbuildV2F; @@ -529,9 +557,6 @@ namespace codac2 { NOTEMPTY, /* is NOT empty */ MINIMIZED, /* minimized : redundant constraints removed */ BOXUPDATED, /* box is minimal */ -#ifdef WITH_CLP - CLPFORM, /* has an up-to-date clp form */ -#endif F2VFORM, /* has an up-to-date F2V form */ V2FFORM, /* has an up-to-date V2F form */ INVALID, /* not initialised */ @@ -544,21 +569,11 @@ namespace codac2 { 1<set_empty_private(); } inline const CollectFacets::mapType &Polytope::facets() const diff --git a/src/core/domains/polytope/codac2_Polytope_util.cpp b/src/core/domains/polytope/codac2_Polytope_util.cpp index 7cc51320b..c7baf4ce6 100644 --- a/src/core/domains/polytope/codac2_Polytope_util.cpp +++ b/src/core/domains/polytope/codac2_Polytope_util.cpp @@ -117,7 +117,6 @@ Index fill_3Dfacet(const DDbuildF2V &build, std::vector::const_iterator> used; used.push_back(it); -// std::cout << "add vert : " << it->Id << "\n"; Vector vt = build.compute_vertex(it->vertex).mid(); tofill.push_back(vt); while (it != vertices.end()) { @@ -173,6 +172,23 @@ std::vector> build_3Dfacets(const DDbuildF2V &build, double return result; } +std::vector build_2Dfacet(const DDbuildF2V &build, double bound) { + assert(build.get_dim()==2); + if (build.is_empty()) return std::vector(); + if (build.get_fdim()==0) { /* the only point has no coordinate */ + std::vector result(1); + Vector v = Vector::constant(1,1.0); + result[0]=(build.get_M_EQ()).mid()*v; + return result; + } else + { /* equivalent to finding one facet of a 3Dfacet */ + std::vector face; + fill_3Dfacet(build,face,-1,bound); + /* look for a initial vertex */ + return face; + } +} + } diff --git a/src/core/domains/polytope/codac2_Polytope_util.h b/src/core/domains/polytope/codac2_Polytope_util.h index 5a4519b77..399fb7633 100644 --- a/src/core/domains/polytope/codac2_Polytope_util.h +++ b/src/core/domains/polytope/codac2_Polytope_util.h @@ -56,5 +56,14 @@ std::vector read_extFile(const char *filename); */ std::vector> build_3Dfacets(const DDbuildF2V &build, double bound=50.0); +/** construct the facet of a 2D-polyhedron from the buildF2V + * structure + * use the mid of each vertex. For rays/line use an arbitrary bound + * @param build the buildF2V structure + * @param bound the bound for rays/line (not correctly handled) + * @return the set of set of vertices + */ +std::vector build_2Dfacet(const DDbuildF2V &build, double bound=50.0); + } diff --git a/src/core/functions/analytic/codac2_AnalyticFunction.h b/src/core/functions/analytic/codac2_AnalyticFunction.h index 2a527fbf1..7b8cca388 100644 --- a/src/core/functions/analytic/codac2_AnalyticFunction.h +++ b/src/core/functions/analytic/codac2_AnalyticFunction.h @@ -21,6 +21,7 @@ #include "codac2_vec.h" #include "codac2_Wrapper.h" #include "codac2_Parallelepiped.h" +#include "codac2_Polytope.h" #include "codac2_peibos_tools.h" namespace codac2 @@ -221,6 +222,49 @@ namespace codac2 return Parallelepiped(z, A_inf); } + template + requires std::is_same_v + Polytope polytope_eval(const Args&... x) const + { + this->check_valid_inputs(x...); + + /* use centered evaluation */ + auto x_ = eval_(x...); + if(x_.da.size() == 0 || !x_.def_domain) { + return Polytope(eval(EvalMode::NATURAL, x...)); + } + auto flatten_x = IntervalVector(cart_prod(x...)); + assert(x_.da.rows() == x_.a.size() && + x_.da.cols() == flatten_x.size()); + + IntervalVector centered_flatten = flatten_x-flatten_x.mid(); + Polytope res = Polytope(centered_flatten).direct_affine_transform + (x_.da, x_.m); + res.meet_with_box(x_.a); + return res; + } + + template + requires std::is_same_v + Polytope polytope_eval(const Polytope& input) const + { + IntervalVector box = input.box(); + this->check_valid_inputs(box); + + /* use centered evaluation */ + auto x_ = eval_(box); + if(x_.da.size() == 0 || !x_.def_domain) + return Polytope(eval(EvalMode::NATURAL, box)); + assert(x_.da.rows() == x_.a.size() && + x_.da.cols() == box.dim()); + + Polytope input_copy(input); + input_copy.inflate(IntervalVector(-box.mid())); + Polytope res = input_copy.direct_affine_transform(x_.da, x_.m); + res.meet_with_box(x_.a); + return res; + } + Index output_size() const { if constexpr(std::is_same_v) @@ -340,4 +384,4 @@ namespace codac2 AnalyticFunction(const FunctionArgsList&, const T&) -> AnalyticFunction::Type>; -} \ No newline at end of file +} diff --git a/src/extensions/clp/codac2_Polytope_clp.cpp b/src/extensions/clp/codac2_Polytope_clp.cpp index bd0eee9ef..4297df009 100644 --- a/src/extensions/clp/codac2_Polytope_clp.cpp +++ b/src/extensions/clp/codac2_Polytope_clp.cpp @@ -36,6 +36,7 @@ using namespace codac2; namespace codac2 { +#if 0 double Polytope::bound_row_clp(const Row &r) const { this->build_clpForm(); _clpForm->setObjective(r); @@ -110,6 +111,7 @@ bool Polytope::check_empty_clp() const { return false; } +#endif } diff --git a/src/extensions/clp/codac2_Polytope_clp.h b/src/extensions/clp/codac2_Polytope_clp.h index e69de29bb..38889c2b5 100644 --- a/src/extensions/clp/codac2_Polytope_clp.h +++ b/src/extensions/clp/codac2_Polytope_clp.h @@ -0,0 +1,92 @@ +/** + * \file codac2_Polytope_clp.h + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2025 + * \license This program is distributed under the terms of + * the GNU Lesser General Public License (LGPL). + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "codac2_Index.h" +#include "codac2_Matrix.h" +#include "codac2_Vector.h" +#include "codac2_Row.h" +#include "codac2_IntervalRow.h" +#include "codac2_IntervalVector.h" +#include "codac2_IntervalMatrix.h" +#include "codac2_BoolInterval.h" +#include "codac2_Polytope_util.h" +#include "codac2_Facet.h" +#include "codac2_Polytope_dd.h" +#include "codac2_Polytope.h" +#include "codac2_Parallelepiped.h" +#include "codac2_Zonotope.h" + +namespace codac2 { + /** + * \class Polytope_clp extends polytope with a clp Form + * Represents a bounded convex polytope as a set of constraints + * and a bounding box (the bounding box is part of the constraints) */ + class Polytope_clp : public Polytope { + + public: + Polytope_clp() : Polytope(); + explicit Polytope_clp(Index dim) : Polytope(dim) {}; + explicit Polytope_clp(Index dim, bool empty) : Polytope(dim,empty) {}; + explicit Polytope_clp(const IntervalVector &box) : Polytope(box) {}; + Polytope_clp(const Polytope_clp &P) : Polytope(P) {}; /* FIXME */ + Polytope_clp(Polytope_clp &&P) = default; + explicit Polytope_clp(const std::vector &vertices) : Polytope(vertices) {}; + explicit Polytope_clp(const std::vector &vertices) : Polytope(vertices) {}; + Polytope_clp(const std::vector &vertices, + const CollectFacets &facetsform) : Polytope(vertices, facetsform) {}; + explicit Polytope_clp(const Parallelepiped &par) : Polytope(par) {}; + explicit Polytope_clp(const Zonotope &zon) : Polytope(zon) {}; + Polytope_clp(const IntervalVector &box, + const std::vector> &facets, + bool minimize=false) : Polytope(box,facets,minimize) {}; + + /** + * copy assignment operator + * \param P copy + */ + Polytope_clp &operator=(const Polytope_clp &P); + + + void minimize_constraints() const override; + + private: + mutable std::unique_ptr _clpForm = nullptr; /* LPclp formulation */ + + mutable bool clpUptodate = nullptr; + + void update_box() const override; + bool check_empty() const override; + void set_empty_private() const override; + + void minimize_constraints_clp(const Interval &tolerance=Interval(0.0)) + const; + void update_box_clp() const; + bool check_empty_clp() const; + double bound_row_clp(const Row &r) const; + void build_clpForm() const; + + void update_box_F2V() const; + bool check_empty_F2V() const; + double bound_row_F2V(const Row &r) const; + void build_DDbuildF2V() const; + } + diff --git a/src/graphics/figures/codac2_Figure2D.cpp b/src/graphics/figures/codac2_Figure2D.cpp index ed2351f5c..b904fcfd4 100644 --- a/src/graphics/figures/codac2_Figure2D.cpp +++ b/src/graphics/figures/codac2_Figure2D.cpp @@ -270,6 +270,14 @@ void Figure2D::draw_zonotope(const Zonotope& z, const StyleProperties& style) output_fig->draw_polygon(vertices,style); } +void Figure2D::draw_polytope(const Polytope& P, const StyleProperties& style) +{ + assert_release(P.size() == 2); + vector w = P.vertices_2Dfacet(); + for(const auto& output_fig : _output_figures) + output_fig->draw_polygon(w,style); +} + void Figure2D::draw_parallelepiped(const Parallelepiped& p, const StyleProperties& style) { diff --git a/src/graphics/figures/codac2_Figure2D.h b/src/graphics/figures/codac2_Figure2D.h index b4d19f18e..760051b14 100644 --- a/src/graphics/figures/codac2_Figure2D.h +++ b/src/graphics/figures/codac2_Figure2D.h @@ -21,6 +21,7 @@ #include "codac2_PavingStyle.h" #include "codac2_Ellipsoid.h" #include "codac2_Polygon.h" +#include "codac2_Polytope.h" #include "codac2_SlicedTube.h" #include "codac2_Ctc.h" #include "codac2_Sep.h" @@ -304,13 +305,21 @@ namespace codac2 void draw_parallelepiped(const Parallelepiped& p, const StyleProperties& style = StyleProperties()); /** - * \brief Draws a zonotope z+sum_i [-1,1] A_i on the figure + * \brief draws a zonotope z+sum_i [-1,1] a_i on the figure * - * \param z Zonotope to draw (center and shape matrix) - * \param style Style of the zonotope (edge color and fill color) + * \param z zonotope to draw (center and shape matrix) + * \param style style of the zonotope (edge color and fill color) */ void draw_zonotope(const Zonotope& z, const StyleProperties& style = StyleProperties()); + /** + * \brief draws a 2D convex polytope on the figure + * + * \param P polytope to draw + * \param style style of the polytope (edge color and fill color) + */ + void draw_polytope(const Polytope& P, const StyleProperties& style = StyleProperties()); + /** * \brief Draws a pie on the figure * @@ -1260,4 +1269,4 @@ namespace codac2 }; } -#include "codac2_Figure2D_pave.h" \ No newline at end of file +#include "codac2_Figure2D_pave.h" diff --git a/src/graphics/figures/codac2_Figure3D.cpp b/src/graphics/figures/codac2_Figure3D.cpp index 86d7fcdc0..7ff230039 100644 --- a/src/graphics/figures/codac2_Figure3D.cpp +++ b/src/graphics/figures/codac2_Figure3D.cpp @@ -137,7 +137,7 @@ void Figure3D::draw_box(const IntervalVector& x, const StyleProperties& style) } void Figure3D::draw_zonotope(const Zonotope& z, const StyleProperties& style) { - assert_release(z.z.size() == 3); + assert_release(z.z.size() == 3); Matrix id = Matrix::Identity(3,3); this->set_style_internal(style); lock_style=true; @@ -193,6 +193,19 @@ void Figure3D::draw_zonotope(const Zonotope& z, const StyleProperties& style) { lock_style=false; } +void Figure3D::draw_polytope(const Polytope& P, const StyleProperties& style) { + assert_release(P.size() == 3); + this->set_style_internal(style); + lock_style=true; + const Vector center = Vector::zero(3); + const Matrix transfo = Matrix::Identity(3,3); + std::vector> facets3D=P.vertices_3Dfacets(); + for (const std::vector &vec : facets3D) { + this->draw_polygon(center,transfo,vec,style); + } + lock_style=false; +} + void Figure3D::draw_arrow(const Vector& c, const Matrix &A, const StyleProperties& style) { diff --git a/src/graphics/figures/codac2_Figure3D.h b/src/graphics/figures/codac2_Figure3D.h index 3887bdee1..f2450d4db 100644 --- a/src/graphics/figures/codac2_Figure3D.h +++ b/src/graphics/figures/codac2_Figure3D.h @@ -20,6 +20,7 @@ #include "codac2_IntervalMatrix.h" #include "codac2_Ellipsoid.h" #include "codac2_Parallelepiped.h" +#include "codac2_Polytope.h" namespace codac2 { @@ -123,6 +124,14 @@ namespace codac2 */ void draw_zonotope(const Zonotope& z, const StyleProperties& style = { Color::dark_gray(0.5) }); + /** + * \brief Draws a polytope on the figure + * + * \param P Polytope to draw + * \param style Style of the polytope (edge color) + */ + void draw_polytope(const Polytope& P, const StyleProperties& style = { Color::dark_gray(0.5) }); + /** * \brief Draws a box on the figure * From ee74c7b9781743a89dddb8d8f1b47802947bea26 Mon Sep 17 00:00:00 2001 From: damien-masse Date: Mon, 1 Dec 2025 10:34:38 +0100 Subject: [PATCH 19/26] Left- and right- "pseudoinverse" enclosure for non-square matrices. Better affine transformation for polytopes (cf 07_centered_2D and 08_centered_3D). --- .../domains/polytope/codac2_py_Polytope.cpp | 12 +- .../src/core/matrices/codac2_py_inversion.cpp | 16 ++ src/core/domains/polytope/codac2_Polytope.cpp | 189 ++++++++++-------- src/core/domains/polytope/codac2_Polytope.h | 48 +++-- .../analytic/codac2_AnalyticFunction.h | 5 +- src/core/matrices/codac2_IntvFullPivLU.h | 6 +- src/core/matrices/codac2_inversion.h | 80 +++++++- src/extensions/clp/codac2_Polytope_clp.cpp | 89 +++++++-- src/extensions/clp/codac2_Polytope_clp.h | 20 +- .../core/matrices/codac2_tests_inversion.cpp | 37 +++- 10 files changed, 367 insertions(+), 135 deletions(-) diff --git a/python/src/core/domains/polytope/codac2_py_Polytope.cpp b/python/src/core/domains/polytope/codac2_py_Polytope.cpp index 3b8122a7b..6956e80f9 100644 --- a/python/src/core/domains/polytope/codac2_py_Polytope.cpp +++ b/python/src/core/domains/polytope/codac2_py_Polytope.cpp @@ -95,8 +95,8 @@ void export_Polytope(py::module& m) BOOLINTERVAL_POLYTOPE_CONTAINS_CONST_INTERVALVECTOR_REF_CONST, "p"_a) .def("is_subset",[](const Polytope &Q, const Polytope &P) - { return Q.is_subset(P,true,true); }, - BOOL_POLYTOPE_IS_SUBSET_CONST_POLYTOPE_REF_BOOL_BOOL_CONST, "P"_a) + { return Q.is_subset(P); }, + VIRTUAL_BOOL_POLYTOPE_IS_SUBSET_CONST_POLYTOPE_REF_CONST, "P"_a) .def("box",[](const Polytope &Q) { return Q.box(true); }, @@ -124,15 +124,15 @@ void export_Polytope(py::module& m) "c"_a,"delta"_a) .def("minimize_constraints",&Polytope::minimize_constraints, - VOID_POLYTOPE_MINIMIZE_CONSTRAINTS_CONST) + VIRTUAL_VOID_POLYTOPE_MINIMIZE_CONSTRAINTS_CONST) .def("reverse_affine_transform",&Polytope::reverse_affine_transform, POLYTOPE_POLYTOPE_REVERSE_AFFINE_TRANSFORM_CONST_INTERVALMATRIX_REF_CONST_INTERVALVECTOR_REF_CONST_INTERVALVECTOR_REF_CONST, "M"_a,"P"_a,"bbox"_a) - .def("bijective_affine_transform",&Polytope::bijective_affine_transform, - POLYTOPE_POLYTOPE_BIJECTIVE_AFFINE_TRANSFORM_CONST_INTERVALMATRIX_REF_CONST_INTERVALMATRIX_REF_CONST_INTERVALVECTOR_REF_CONST, - "M"_a,"Minv"_a,"P"_a) + .def("affine_transform",&Polytope::affine_transform, + POLYTOPE_POLYTOPE_AFFINE_TRANSFORM_CONST_INTERVALMATRIX_REF_CONST_INTERVALVECTOR_REF_CONST, + "M"_a,"P"_a) .def("vertices",&Polytope::vertices, VECTOR_INTERVALVECTOR_POLYTOPE_VERTICES_CONST) diff --git a/python/src/core/matrices/codac2_py_inversion.cpp b/python/src/core/matrices/codac2_py_inversion.cpp index 8c513e7e2..c81d84041 100644 --- a/python/src/core/matrices/codac2_py_inversion.cpp +++ b/python/src/core/matrices/codac2_py_inversion.cpp @@ -36,6 +36,22 @@ void export_inversion(py::module& m) INTERVALMATRIX_INVERSE_ENCLOSURE_CONST_EIGEN_MATRIXBASE_OTHERDERIVED_REF, "A"_a) + .def("left_inverse_enclosure", [](const Matrix& A) { return left_inverse_enclosure(A); }, + INTERVALMATRIX_LEFT_INVERSE_ENCLOSURE_CONST_EIGEN_MATRIXBASE_OTHERDERIVED_REF, + "A"_a) + + .def("left_inverse_enclosure", [](const IntervalMatrix& A) { return left_inverse_enclosure(A); }, + INTERVALMATRIX_LEFT_INVERSE_ENCLOSURE_CONST_EIGEN_MATRIXBASE_OTHERDERIVED_REF, + "A"_a) + + .def("right_inverse_enclosure", [](const Matrix& A) { return right_inverse_enclosure(A); }, + INTERVALMATRIX_RIGHT_INVERSE_ENCLOSURE_CONST_EIGEN_MATRIXBASE_OTHERDERIVED_REF, + "A"_a) + + .def("right_inverse_enclosure", [](const IntervalMatrix& A) { return right_inverse_enclosure(A); }, + INTERVALMATRIX_RIGHT_INVERSE_ENCLOSURE_CONST_EIGEN_MATRIXBASE_OTHERDERIVED_REF, + "A"_a) + .def("inverse_correction", [](const IntervalMatrix& A, const IntervalMatrix& B, bool left_inv = true) { diff --git a/src/core/domains/polytope/codac2_Polytope.cpp b/src/core/domains/polytope/codac2_Polytope.cpp index bada763e6..fc6fac0a5 100644 --- a/src/core/domains/polytope/codac2_Polytope.cpp +++ b/src/core/domains/polytope/codac2_Polytope.cpp @@ -73,9 +73,6 @@ Polytope &Polytope::operator=(const Polytope &P) { this->_box = P._box; this->_facets = P.state[EMPTY] ? std::make_shared(_dim) : std::make_shared(*(P._facets)); -#if 0 - this->_clpForm=nullptr; -#endif this->_DDbuildF2V=nullptr; this->_DDbuildV2F=nullptr; this->state = P.state & (pol_state_empty | @@ -157,7 +154,7 @@ Polytope::Polytope(const Parallelepiped &par) : we consider the inversion */ IntervalMatrix u = inverse_enclosure(par.A); for (Index i=0;iadd_constraint_band(u.row(i), + this->add_constraint_band(IntervalRow(u.row(i)), u.row(i).dot(par.z)+Interval(-1.0,1.0),0.0); } IntervalVector range = IntervalVector::constant(par.A.cols(), @@ -228,6 +225,7 @@ Polytope Polytope::from_extFile(const char *filename) { Interval Polytope::fast_bound(const FacetBase &base) const { if (base.is_null()) return Interval::zero(); if (state[EMPTY]) return Interval(); + if (is_box()) return base.row.dot(_box).ub(); Index gdim = base.gt_dim(); Interval res; if (base.row[gdim]>0.0) { @@ -275,6 +273,7 @@ Interval Polytope::fast_bound(const FacetBase &base) const { double Polytope::bound_row(const Row &r) const { assert(r.size()==_dim); if (state[EMPTY]) return -oo; + if (is_box()) return r.dot(_box).ub(); this->build_DDbuildF2V(); double a = -oo; if (state[EMPTY]) return a; @@ -287,17 +286,6 @@ double Polytope::bound_row(const Row &r) const { return a; } -#if 0 -double Polytope::bound_row(const Row &r) const { - assert(r.size()==_dim); - if (state[EMPTY]) return -oo; - if (!state[F2VFORM] && state[CLPFORM]) { - return this->bound_row_clp(r); - } - return this->bound_row_F2V(r); -} -#endif - void Polytope::build_DDbuildF2V() const { if (state[F2VFORM]) return; if (state[EMPTY]) { @@ -336,31 +324,15 @@ void Polytope::minimize_constraints() const { if (changed) { std::vector corresp = _facets->renumber(); _DDbuildF2V->update_renumber(corresp); -#if 0 - state[CLPFORM]=false; - _clpForm=nullptr; -#endif state[V2FFORM]=false; } state[MINIMIZED]=true; } -#if 0 -void Polytope::minimize_constraints() const { - if (state[MINIMIZED]) return; - if (state[EMPTY]) return; - /* default : F2V */ - if (!state[F2VFORM] && state[CLPFORM]) { - this->minimize_constraints_clp(); - return; - } - this->minimize_constraints_F2V(); -} -#endif - void Polytope::update_box() const { if (state[BOXUPDATED]) return; if (state[EMPTY]) return; + if (is_box()) { state[BOXUPDATED]=true; return; } assert_release(!state[BOXUPDATED]); this->build_DDbuildF2V(); if (state[EMPTY]) return; @@ -368,38 +340,22 @@ void Polytope::update_box() const { state[BOXUPDATED]=true; } -#if 0 -void Polytope::update_box() const { - if (state[BOXUPDATED]) return; - if (state[EMPTY]) return; - /* default : F2V */ - if (!state[F2VFORM] && state[CLPFORM]) { - this->update_box_clp(); - return; - } - this->update_box_F2V(); -} -#endif - bool Polytope::check_empty() const { if (state[NOTEMPTY]) return false; if (state[EMPTY]) return true; + if (is_box()) { + if (_box.is_empty()) { + state[EMPTY]=true; + return true; + } else { + state[NOTEMPTY]=true; + return false; + } + } this->build_DDbuildF2V(); return state[EMPTY]; } -#if 0 -bool Polytope::check_empty() const { - if (state[NOTEMPTY]) return false; - if (state[EMPTY]) return true; - /* default : F2V */ - if (!state[F2VFORM] && state[CLPFORM]) { - return this->check_empty_clp(); - } - return this->check_empty_F2V(); -} -#endif - BoolInterval Polytope::contains(const IntervalVector& p) const { assert_release(p.size()==_dim); if (p.is_empty()) return BoolInterval::TRUE; @@ -428,12 +384,6 @@ bool Polytope::is_subset(const Polytope& P) if (this->fast_bound(fctP.first).ub() <= fctP.second.rhs) continue; double l1 = this->bound_row(fctP.first.row); if (l1<=fctP.second.rhs) continue; -#if 0 - if (checkCLP) { - double l1 = this->bound_row_clp(fctP.first.row); - if (l1<=fctP.second.rhs) continue; - } -#endif return false; } return true; @@ -613,11 +563,17 @@ std::pair Polytope::add_constraint_band(const IntervalRow &cst, Row cstmid = cst.mid(); IntervalRow rem = cst-cstmid; Interval d = rhs+rem.dot(_box); + return this->add_constraint_band(cstmid,d,tolerance); +} + +std::pair Polytope::add_constraint_band(const Row &cst, + const Interval &rhs, double tolerance) { + if (state[EMPTY]) return { false, false }; bool rub, rlb; - if (d.ub()==+oo) rub=false; - else rub = this->add_constraint(std::pair(cstmid, d.ub()), tolerance); - if (d.lb()==-oo) rlb=false; - else rlb = this->add_constraint(std::pair(-cstmid, -d.lb()), tolerance); + if (rhs.ub()==+oo) rub=false; + else rub = this->add_constraint(std::pair(cst, rhs.ub()), tolerance); + if (rhs.lb()==-oo) rlb=false; + else rlb = this->add_constraint(std::pair(-cst, -rhs.lb()), tolerance); return { rlb, rub }; } @@ -791,9 +747,12 @@ Polytope &Polytope::homothety(const IntervalVector &c, double delta) { Polytope Polytope::reverse_affine_transform(const IntervalMatrix &M, const IntervalVector &P, const IntervalVector &bbox) const { - assert(!state[INVALID]); - if (state[EMPTY]) return Polytope(_dim,true); - CollectFacets cf(_dim); + assert_release(!state[INVALID]); + assert_release(M.rows()==_dim); + assert_release(bbox.size()==M.cols()); + assert_release(P.size()==_dim); + if (state[EMPTY]) return Polytope(M.cols(),true); + CollectFacets cf(M.cols()); /* first the box */ for (Index i=0;i<_dim;i++) { IntervalRow rI = M.row(i); @@ -806,19 +765,19 @@ Polytope Polytope::reverse_affine_transform(const IntervalMatrix &M, std::pair ret = cf.insert_facet(std::move(r),rhs.ub(),true, CollectFacets::MIN_RHS); - if (ret.first==-1) return Polytope(this->_dim, true); + if (ret.first==-1) return Polytope(M.cols(), true); } else { if (rhs.ub()!=+oo) { std::pair ret = cf.insert_facet(r,rhs.ub(),false, CollectFacets::MIN_RHS); - if (ret.first==-1) return Polytope(this->_dim, true); + if (ret.first==-1) return Polytope(M.cols(), true); } if (rhs.lb()!=-oo) { std::pair ret = cf.insert_facet(-r,-rhs.lb(),false, CollectFacets::MIN_RHS); - if (ret.first==-1) return Polytope(this->_dim, true); + if (ret.first==-1) return Polytope(M.cols(), true); } } } @@ -833,33 +792,82 @@ Polytope Polytope::reverse_affine_transform(const IntervalMatrix &M, std::pair ret = cf.insert_facet(std::move(r),rhs.ub(),facet.second.eqcst, CollectFacets::MIN_RHS); - if (ret.first==-1) return Polytope(this->_dim, true); + if (ret.first==-1) return Polytope(M.cols(), true); } else { if (rhs.ub()!=+oo) { std::pair ret = cf.insert_facet(r,rhs.ub(),false, CollectFacets::MIN_RHS); - if (ret.first==-1) return Polytope(this->_dim, true); + if (ret.first==-1) return Polytope(M.cols(), true); } if (rhs.lb()!=-oo) { std::pair ret = cf.insert_facet(-r,-rhs.lb(),false, CollectFacets::MIN_RHS); - if (ret.first==-1) return Polytope(this->_dim, true); + if (ret.first==-1) return Polytope(M.cols(), true); } } } return Polytope(IntervalVector(bbox),std::move(cf)); } -Polytope Polytope::bijective_affine_transform(const IntervalMatrix &M, - const IntervalMatrix &Minv, const IntervalVector &P) const { - IntervalVector M2 = M*_box+P; - return this->reverse_affine_transform(Minv,Minv*P,M2); +Polytope Polytope::affine_transform(const IntervalMatrix &M, + const IntervalVector &P, const IntervalVector &B, + bool use_direct) const { + assert_release(!state[INVALID]); + if (state[EMPTY]) return Polytope(_dim,true); + assert_release(M.cols()==_dim); + assert_release(M.rows()==P.size()); + assert_release(B.size()==P.size()); + /* x' \in [M] x + [P] + becomes x' \in Mmid x + ([P] + ([M]-Mmid) B) */ + Matrix Mmid = M.mid(); + IntervalVector P2 = P + (M - Mmid)*_box; + if (Mmid.rows()==_dim) { + IntervalMatrix Minv = inverse_enclosure(Mmid); + if (!Minv.is_unbounded()) { + IntervalVector M2 = B & (M*_box+P); + if (!use_direct) + return this->reverse_affine_transform(Minv,-Minv*P2,M2); + else { + Polytope R = this->reverse_affine_transform(Minv,-Minv*P2,M2); + return (R &= this->direct_affine_transform(M,P)); + } + } + } else if (M.rows()>_dim) { + IntvFullPivLU LUdec(Matrix(Mmid.transpose())); + /* we use the tranpose for solve as we did not define + leftSolve */ + if (LUdec.is_surjective()==BoolInterval::TRUE) { + IntervalVector M2 = B & (M*_box+P); + IntervalMatrix inv = + LUdec.solve(IntervalMatrix::Identity(_dim,_dim)); + IntervalMatrix leftInv = inv.transpose(); + Polytope R = this->reverse_affine_transform(leftInv,-leftInv*P2,M2); + IntervalMatrix leftKer = LUdec.kernel().transpose(); + for (Index k = 0; k(r.mid(),u.mid())); + else R.add_constraint_band(r,u); + } + if (!use_direct) + return R; + else + return (R &= this->direct_affine_transform(M,P)); + } + } + Polytope R = this->direct_affine_transform(M,P); + R.meet_with_box(B); + return R; } Polytope Polytope::direct_affine_transform(const IntervalMatrix &M, - const IntervalVector &P) { + const IntervalVector &P) const { + assert_release(!state[INVALID]); + if (state[EMPTY]) return Polytope(_dim,true); this->build_DDbuildF2V(); IntervalVector nBox = M*this->box()+P; std::vector resultat; @@ -1066,6 +1074,7 @@ Polytope &Polytope::unflat(Index dm, double rad) { Polytope operator+ (const Polytope &p1, const Polytope &p2) { assert(!p1.state[INVALID]); assert(!p2.state[INVALID]); + if (p1.is_box() && p2.is_box()) return Polytope(p1.box()+p2.box()); std::vector vt1 = p1.vertices(); std::vector vt2 = p2.vertices(); IntervalVector bres = p1.box() + p2.box(); @@ -1090,6 +1099,24 @@ std::ostream& operator<<(std::ostream& os, } std::vector Polytope::vertices() const { + assert(!state[INVALID]); + if (this->is_box()) { + if (_box.is_empty()) return std::vector(); + std::vector ret; + Vector a = _box.lb(); + ret.push_back(IntervalVector(a)); + Index i=0; + while (i<_box.size()) { + if (_box[i].is_degenerated()) { i++; continue; } + if (a[i]==_box[i].lb()) { + a[i]=_box[i].ub(); + ret.push_back(IntervalVector(a)); + i=0; continue; + } + a[i]=_box[i].lb(); i++; + } + return ret; + } this->build_DDbuildF2V(); if (state[EMPTY]) return std::vector(); std::vector ret; diff --git a/src/core/domains/polytope/codac2_Polytope.h b/src/core/domains/polytope/codac2_Polytope.h index bf6ce0ca5..58443976d 100644 --- a/src/core/domains/polytope/codac2_Polytope.h +++ b/src/core/domains/polytope/codac2_Polytope.h @@ -157,7 +157,7 @@ namespace codac2 { /** * Destructor */ - ~Polytope() = default; + virtual ~Polytope() = default; /** * copy assignment operator @@ -187,8 +187,15 @@ namespace codac2 { // Index dim_polytope() const; /** - * Get the number of constraints + * Returns true if the polytope is a box (i.e. there are no + * other facets). + * + * \return true if the polytope is a box, false otherwise. + */ + bool is_box() const; + /** + * Get the number of constraints * * \return the number of facets (including equalities) */ @@ -268,7 +275,6 @@ namespace codac2 { /** * Checks inclusion in another polytope * \param P the polytope - * \param checkF2V checks using DD * \return true if inclusion is guaranteed */ virtual bool is_subset(const Polytope &P) const; @@ -355,6 +361,8 @@ namespace codac2 { * \return pair of booleans (one for each constraints possibly added */ std::pair add_constraint_band(const IntervalRow &cst, const Interval &rhs, double tolerance=0.0); + std::pair add_constraint_band(const Row &cst, + const Interval &rhs, double tolerance=0.0); /** add a equality (pair row X = rhs) * do basic checks, but do not minimize the system @@ -449,31 +457,36 @@ namespace codac2 { Polytope reverse_affine_transform(const IntervalMatrix &M, const IntervalVector &P, const IntervalVector &bbox) const; - /** bijective affine transformation - * compute { [M] x + [P] s.t. x in the initial polytope } - * with MInv encloses the inverse of M - * M is used only to approximate the bounding box of the result, - * Minv is used to compute the new constraints + /** affine transformation + * compute { [B] cap ([M] x + [P]) s.t. x in the initial polytope } + * If [M] is bijective, use inverse to compute the inverse of M + * If [M] is full-rank and injective, use the + * LU decomposition of transpose(M) to compute + * the left inverse of M and the kernel of M. + * Otherwise, call direct-affine-transform. + * if use_direct is true, use both methods if possible * \param M matrix - * \param Minv its inverse * \param P non-empty vector + * \param B box limiting the result + * \param use_direct use direct_affine_transform in addition of + * the other method * \return a new polytope */ - Polytope bijective_affine_transform(const IntervalMatrix &M, - const IntervalMatrix &Minv, - const IntervalVector &P) const; + Polytope affine_transform(const IntervalMatrix &M, + const IntervalVector &P, const IntervalVector &B, + bool use_direct=false) const; /** affine transformation * compute { [M] x + [P] s.t x in the initial polytope } * Generate IntervalVector from the vertices, but not the * the vertices of the IntervalVector themselves (hence not * optimal if [M] and [P] are not punctual). As it uses vertices, - * it may be expensive. Use bijective_affine_transform if [M] is - * non-singular to avoir the use of vertices. + * it may be expensive. Use affine_transform to avoid use of + * vertices (for injective transformations). * \param M non-empty matrix * \param P non-empty vector * \return a new polytope */ Polytope direct_affine_transform(const IntervalMatrix &M, - const IntervalVector &P); + const IntervalVector &P) const; /** time-elapse transformation * compute { x + t [P] s.t x in the initial polytope and t in T } @@ -541,7 +554,7 @@ namespace codac2 { - private: + protected: Index _dim; /* dimension */ mutable IntervalVector _box; /* bounding box */ mutable std::shared_ptr _facets; @@ -588,6 +601,9 @@ inline bool Polytope::is_empty(bool check) const { return this->check_empty(); } inline bool Polytope::is_flat() const { return state[EMPTY] || _box.is_degenerated() || (_facets && _facets->nbeqfcts()>0); } +inline bool Polytope::is_box() const { + return (!_facets || _facets->nbfcts()==0); +} inline Index Polytope::nb_facets() const { if (state[EMPTY]) return -1; if (!_facets) return (2*_dim); diff --git a/src/core/functions/analytic/codac2_AnalyticFunction.h b/src/core/functions/analytic/codac2_AnalyticFunction.h index 7b8cca388..3c4a020c1 100644 --- a/src/core/functions/analytic/codac2_AnalyticFunction.h +++ b/src/core/functions/analytic/codac2_AnalyticFunction.h @@ -238,9 +238,8 @@ namespace codac2 x_.da.cols() == flatten_x.size()); IntervalVector centered_flatten = flatten_x-flatten_x.mid(); - Polytope res = Polytope(centered_flatten).direct_affine_transform - (x_.da, x_.m); - res.meet_with_box(x_.a); + Polytope res = Polytope(centered_flatten).affine_transform + (x_.da, x_.m, x_.a); return res; } diff --git a/src/core/matrices/codac2_IntvFullPivLU.h b/src/core/matrices/codac2_IntvFullPivLU.h index 108cfaa39..c65591e57 100644 --- a/src/core/matrices/codac2_IntvFullPivLU.h +++ b/src/core/matrices/codac2_IntvFullPivLU.h @@ -48,7 +48,7 @@ namespace codac2 /** * \brief Check if the matrix is injective, - * *i.e.* its rank is equal to its number of rows. + * *i.e.* its rank is equal to its number of cols. * * \return a ``BoolInterval`` */ @@ -64,7 +64,7 @@ namespace codac2 /** * \brief Check if the matrix is surjective - * *i.e.* its rank is equal to its number of cols. + * *i.e.* its rank is equal to its number of rows. * * \return a ``BoolInterval`` */ @@ -339,4 +339,4 @@ inline Derived IntvFullPivLU::coimage return ret; } -} \ No newline at end of file +} diff --git a/src/core/matrices/codac2_inversion.h b/src/core/matrices/codac2_inversion.h index 9f014c7b9..cbdd6e655 100644 --- a/src/core/matrices/codac2_inversion.h +++ b/src/core/matrices/codac2_inversion.h @@ -34,14 +34,13 @@ namespace codac2 template inline IntervalMatrix inverse_correction(const Eigen::MatrixBase& A, const Eigen::MatrixBase& B) { - assert_release(A.is_squared()); - assert_release(B.is_squared()); + assert_release(B.cols()==A.rows()); + assert_release(A.cols()==B.rows()); auto A_ = A.template cast(); auto B_ = B.template cast(); - Index N = A_.rows(); - assert_release(N==B_.rows()); + Index N = (O == LEFT_INV ? A_.cols() : A_.rows()); auto Id = IntervalMatrix::Identity(N,N); auto erMat = [&]() { if constexpr(O == LEFT_INV) return -B_*A_+Id; else return -A_*B_+Id; }(); @@ -60,12 +59,10 @@ namespace codac2 for (Index c=0;c + inline IntervalMatrix left_inverse_enclosure + (const Eigen::MatrixBase& A) + { + Index N = A.rows(); + Index M = A.cols(); + assert_release(N>M); + /* we use the transpose to solve, then we correct with LEFT_INV */ + if constexpr(std::is_same_v) { + return inverse_correction(A, + (A.mid().transpose()).fullPivLu() + .solve(Matrix::Identity(M,M)).transpose()); + } else { + return inverse_correction(A, + (A.transpose()).fullPivLu() + .solve(Matrix::Identity(M,M)).transpose()); + } + } + + /** + * \brief Enclosure of a right inverse of a (full-rank) matrix + * expression, possibly an interval matrix. + * + * \pre \f$\mathbf{A}\f$ has more columns than rows. + * + * \param A A non-square matrix expression + * \return The enclosure of a right-inverse. Can have + * \f$(-\infty,\infty)\f$ coefficients if the inversion "failed", + * either because the matrix is (almost) not full-rank, or the full pivot + * LU decomposition did not use the ''correct'' pivots + * (for Interval matrix) */ + template + inline IntervalMatrix right_inverse_enclosure + (const Eigen::MatrixBase& A) + { + Index N = A.rows(); + Index M = A.cols(); + assert_release(M>N); + /* we solve directly, and transpose for the inverse_correction (?) */ + if constexpr(std::is_same_v) { + return inverse_correction (A.transpose(), + (A.mid()).fullPivLu().solve(Matrix::Identity(N,N)).transpose()) + .transpose(); + } else { + return inverse_correction (A.transpose(), + A.fullPivLu().solve(Matrix::Identity(N,N)).transpose()) + .transpose(); + } + } + /** * \brief Compute an upper bound of \f$\left([\mathbf{A}]+[\mathbf{A}]^2+[\mathbf{A}]^3+\dots\right)\f$, * with \f$[\mathbf{A}]\f$ a matrix of intervals as an "error term" (uses only bounds on coefficients). diff --git a/src/extensions/clp/codac2_Polytope_clp.cpp b/src/extensions/clp/codac2_Polytope_clp.cpp index 4297df009..bc0ae4b1f 100644 --- a/src/extensions/clp/codac2_Polytope_clp.cpp +++ b/src/extensions/clp/codac2_Polytope_clp.cpp @@ -28,6 +28,7 @@ #include "codac2_Parallelepiped.h" #include "codac2_Zonotope.h" #include "codac2_Polytope.h" +#include "codac2_Polytope_clp.h" #include "codac2_Facet.h" #include "codac2_clp.h" @@ -36,8 +37,39 @@ using namespace codac2; namespace codac2 { -#if 0 -double Polytope::bound_row_clp(const Row &r) const { +Polytope_clp &Polytope_clp::operator=(const Polytope_clp &P) { + assert(!P.state[INVALID]); + this->_dim = P._dim; + this->_box = P._box; + this->_facets = P.state[EMPTY] ? std::make_shared(_dim) : + std::make_shared(*(P._facets)); + this->_clpForm=nullptr; /* FIXME */ + this->_DDbuildF2V=nullptr; + this->_DDbuildV2F=nullptr; + this->state = P.state & (pol_state_empty | + pol_state(1<bound_row_clp(r); +// } +// return Polytope::bound_row_F2V(r); +} + +double Polytope_clp::bound_row_clp(const Row &r) const { this->build_clpForm(); _clpForm->setObjective(r); LPclp::lp_result_stat ret = _clpForm->solve(); @@ -45,17 +77,17 @@ double Polytope::bound_row_clp(const Row &r) const { return _clpForm->getValobj().ub(); } -void Polytope::build_clpForm() const { - if (state[CLPFORM]) return; +void Polytope_clp::build_clpForm() const { + if (clpUptodate) return; if (state[EMPTY]) { _clpForm=nullptr; return; } _clpForm = std::make_unique(_dim,_facets,_box); - state[CLPFORM]=true; + clpUptodate=true; } -void Polytope::minimize_constraints_clp(const Interval &tolerance) const { +void Polytope_clp::minimize_constraints_clp(const Interval &tolerance) const { assert_release(!state[MINIMIZED]); this->build_clpForm(); int ret = _clpForm->minimize_polytope(tolerance, false, !state[BOXUPDATED]); @@ -77,16 +109,29 @@ void Polytope::minimize_constraints_clp(const Interval &tolerance) const { } if (changed) { std::vector corresp = _facets->renumber(); - if (state[F2VFORM]) { +/* if (state[F2VFORM]) { _DDbuildF2V->update_renumber(corresp); - } - state[CLPFORM]=false; state[V2FFORM]=false; + } */ + state[F2VFORM]=false; + clpUptodate=false; state[V2FFORM]=false; _clpForm=nullptr; } state[MINIMIZED]=true; } -void Polytope::update_box_clp() const { +void Polytope_clp::minimize_constraints() const { + if (state[MINIMIZED]) return; + if (state[EMPTY]) return; + /* default : F2V */ +// if (!state[F2VFORM] && state[CLPFORM]) { + this->minimize_constraints_clp(); +// return; +// } +// this->minimize_constraints_F2V(); +} + + +void Polytope_clp::update_box_clp() const { assert_release(!state[BOXUPDATED]); this->build_clpForm(); int ret = _clpForm->minimize_box(); @@ -99,7 +144,18 @@ void Polytope::update_box_clp() const { state[BOXUPDATED]=true; } -bool Polytope::check_empty_clp() const { +void Polytope_clp::update_box() const { + if (state[BOXUPDATED]) return; + if (state[EMPTY]) return; +// if (!state[F2VFORM] && state[CLPFORM]) { + this->update_box_clp(); +// return; +// } +// this->update_box_F2V(); +} + + +bool Polytope_clp::check_empty_clp() const { assert_release(!state[NOTEMPTY] && !state[EMPTY]); this->build_clpForm(); int ret = _clpForm->check_emptiness(); @@ -111,7 +167,16 @@ bool Polytope::check_empty_clp() const { return false; } -#endif +bool Polytope_clp::check_empty() const { + if (state[NOTEMPTY]) return false; + if (state[EMPTY]) return true; +// if (!state[F2VFORM] && state[CLPFORM]) { + return this->check_empty_clp(); +// } +// return this->check_empty_F2V(); +} + + } diff --git a/src/extensions/clp/codac2_Polytope_clp.h b/src/extensions/clp/codac2_Polytope_clp.h index 38889c2b5..9b83fa117 100644 --- a/src/extensions/clp/codac2_Polytope_clp.h +++ b/src/extensions/clp/codac2_Polytope_clp.h @@ -34,6 +34,7 @@ #include "codac2_Polytope.h" #include "codac2_Parallelepiped.h" #include "codac2_Zonotope.h" +#include "codac2_clp.h" namespace codac2 { /** @@ -43,12 +44,15 @@ namespace codac2 { class Polytope_clp : public Polytope { public: - Polytope_clp() : Polytope(); + Polytope_clp() : Polytope() {}; explicit Polytope_clp(Index dim) : Polytope(dim) {}; explicit Polytope_clp(Index dim, bool empty) : Polytope(dim,empty) {}; explicit Polytope_clp(const IntervalVector &box) : Polytope(box) {}; Polytope_clp(const Polytope_clp &P) : Polytope(P) {}; /* FIXME */ Polytope_clp(Polytope_clp &&P) = default; + virtual ~Polytope_clp() override = default; + explicit Polytope_clp(const Polytope &P) : Polytope(P) {}; /* FIXME */ + explicit Polytope_clp(Polytope &&P) : Polytope(P) {}; /* FIXME */ explicit Polytope_clp(const std::vector &vertices) : Polytope(vertices) {}; explicit Polytope_clp(const std::vector &vertices) : Polytope(vertices) {}; Polytope_clp(const std::vector &vertices, @@ -64,14 +68,23 @@ namespace codac2 { * \param P copy */ Polytope_clp &operator=(const Polytope_clp &P); + Polytope_clp &operator=(Polytope_clp &&P) = default; + /** + * transfer from Polytope + * \param P copy + */ + Polytope_clp &operator=(const Polytope &P); + Polytope_clp &operator=(Polytope &&P); void minimize_constraints() const override; + + double bound_row(const Row &r) const override; private: mutable std::unique_ptr _clpForm = nullptr; /* LPclp formulation */ - mutable bool clpUptodate = nullptr; + mutable bool clpUptodate = false; void update_box() const override; bool check_empty() const override; @@ -88,5 +101,6 @@ namespace codac2 { bool check_empty_F2V() const; double bound_row_F2V(const Row &r) const; void build_DDbuildF2V() const; - } + }; +} diff --git a/tests/core/matrices/codac2_tests_inversion.cpp b/tests/core/matrices/codac2_tests_inversion.cpp index d5b553d49..5db990bc9 100644 --- a/tests/core/matrices/codac2_tests_inversion.cpp +++ b/tests/core/matrices/codac2_tests_inversion.cpp @@ -14,10 +14,12 @@ #include #include +#include + using namespace std; using namespace codac2; -TEST_CASE("Matrix") +TEST_CASE("Square matrices") { IntervalMatrix x({ { {0.0,0.0}, {-0.1,-0.1}, {0.2,0.2} }, @@ -52,3 +54,36 @@ TEST_CASE("Matrix") y = inverse_enclosure(w); CHECK((w.template cast()*y).contains(Matrix::Identity(3,3))); } + +TEST_CASE("Non-square matrices") +{ + IntervalMatrix x({ + { {0.0,0.0}, {-0.1,-0.1}, {0.2,0.2}, {0.1,0.1} }, + { {0.0,0.0}, {-0.2,-0.2}, {0.1,0.1}, {0.3,0.3} }, + { {0.1,0.1}, {-0.1,-0.1}, {0.1,0.1}, {-0.1,-0.1} } + }); + + IntervalMatrix z = right_inverse_enclosure(x); + CHECK(Approx(x*z,1e-8)==IntervalMatrix::Identity(3,3)); + + Matrix u( { + { 1,2,3 }, + { 1,3,5 }, + { 2,6,10 }, + { 3,4,5 }, + { 2,1,3 }, + { 6,2,4 } + }); + + IntervalMatrix v=left_inverse_enclosure(u); + CHECK((v*u.template cast()).contains(Matrix::Identity(3,3))); + + Matrix w({ + { 1, 2, 0, 3, 1, 5 }, + { 0, 4, 1, 8, 2, 4 }, + { 0, 1, 0, 4, 5, 1 } + }); + + IntervalMatrix y = right_inverse_enclosure(w); + CHECK((w.template cast()*y).contains(Matrix::Identity(3,3))); +} From 8686d668cb6935ce41467239abe55bf73645d64f Mon Sep 17 00:00:00 2001 From: damien-masse Date: Mon, 1 Dec 2025 16:01:09 +0100 Subject: [PATCH 20/26] bugfix in python --- python/src/core/domains/polytope/codac2_py_Polytope.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/python/src/core/domains/polytope/codac2_py_Polytope.cpp b/python/src/core/domains/polytope/codac2_py_Polytope.cpp index 6956e80f9..a86799d5f 100644 --- a/python/src/core/domains/polytope/codac2_py_Polytope.cpp +++ b/python/src/core/domains/polytope/codac2_py_Polytope.cpp @@ -131,7 +131,11 @@ void export_Polytope(py::module& m) "M"_a,"P"_a,"bbox"_a) .def("affine_transform",&Polytope::affine_transform, - POLYTOPE_POLYTOPE_AFFINE_TRANSFORM_CONST_INTERVALMATRIX_REF_CONST_INTERVALVECTOR_REF_CONST, + POLYTOPE_POLYTOPE_AFFINE_TRANSFORM_CONST_INTERVALMATRIX_REF_CONST_INTERVALVECTOR_REF_CONST_INTERVALVECTOR_REF_BOOL_CONST, + "M"_a,"P"_a,"B"_a,"use_direct"_a) + + .def("direct_affine_transform",&Polytope::direct_affine_transform, + POLYTOPE_POLYTOPE_DIRECT_AFFINE_TRANSFORM_CONST_INTERVALMATRIX_REF_CONST_INTERVALVECTOR_REF_CONST, "M"_a,"P"_a) .def("vertices",&Polytope::vertices, From e37e13f83fec1b9f46dd8ff4f078a6b87016cf8e Mon Sep 17 00:00:00 2001 From: damien-masse Date: Fri, 12 Dec 2025 14:29:32 +0100 Subject: [PATCH 21/26] Polytope contractor and separator --- examples/polytope_examples/CMakeLists.txt | 2 +- .../polytope_examples/main-contractor.cpp | 36 +++++ src/core/CMakeLists.txt | 4 + src/core/contractors/codac2_CtcPolytope.cpp | 31 +++++ src/core/contractors/codac2_CtcPolytope.h | 31 +++++ src/core/domains/polytope/codac2_Facet.cpp | 38 +++++- src/core/domains/polytope/codac2_Facet.h | 2 +- src/core/domains/polytope/codac2_Polytope.cpp | 129 ++++++++++++++++-- src/core/domains/polytope/codac2_Polytope.h | 59 +++++++- src/core/separators/codac2_SepPolytope.cpp | 30 ++++ src/core/separators/codac2_SepPolytope.h | 29 ++++ src/graphics/figures/codac2_Figure2D.h | 12 ++ 12 files changed, 376 insertions(+), 27 deletions(-) create mode 100644 examples/polytope_examples/main-contractor.cpp create mode 100644 src/core/contractors/codac2_CtcPolytope.cpp create mode 100644 src/core/contractors/codac2_CtcPolytope.h create mode 100644 src/core/separators/codac2_SepPolytope.cpp create mode 100644 src/core/separators/codac2_SepPolytope.h diff --git a/examples/polytope_examples/CMakeLists.txt b/examples/polytope_examples/CMakeLists.txt index d0c0ac911..a3113d999 100644 --- a/examples/polytope_examples/CMakeLists.txt +++ b/examples/polytope_examples/CMakeLists.txt @@ -34,7 +34,7 @@ if(FAST_RELEASE) message(STATUS "You are running Codac in fast release mode. (option -DCMAKE_BUILD_TYPE=Release is required)") endif() -set(PROGS main-F2V main-3Dgraphics main-V2F main-3Dhull main-3Dtest main-manual) +set(PROGS main-F2V main-3Dgraphics main-V2F main-3Dhull main-3Dtest main-manual main-contractor) foreach(PROG ${PROGS}) add_executable(${PROG} ${PROG}.cpp) diff --git a/examples/polytope_examples/main-contractor.cpp b/examples/polytope_examples/main-contractor.cpp new file mode 100644 index 000000000..31519d435 --- /dev/null +++ b/examples/polytope_examples/main-contractor.cpp @@ -0,0 +1,36 @@ +// Author : Damien Massé +// Graphical illustration of the polytope test + +#include +#include +#include + +using namespace std; +using namespace codac2; + +int main(int argc, char *argv[]) +{ +// std::cout << std::scientific << std::setprecision(20); + + // definition using facets + std::vector> facets + { { {1,1}, 3.1 }, + { {-1,0.6} ,1.6 }, + { {0.5,0.2} ,1.4 }, + { {0.5,0.75} ,1.75 }, + { {-0.75,-1} ,1.33 }, + { {0.5,-1} ,1 }, + { {-1.0/3.0,-1} ,1 } }; + // the first argument is a bounding box, here the whole space + Polytope p({{-2,3.0},{-1,2.1}}, facets); + + SepPolytope sep(p); + +// auto pv1 = pave({{-3.0,3.0},{-3.0,3.0}},sep,0.04); + + DefaultFigure::pave({{-2.0,3.0},{-3.0,3.0}},sep,0.04); + + DefaultFigure::draw_polytope(p); + + return 0; +} diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 6b235968b..e87b86b50 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -45,6 +45,8 @@ ${CMAKE_CURRENT_SOURCE_DIR}/contractors/codac2_CtcPolar.h ${CMAKE_CURRENT_SOURCE_DIR}/contractors/codac2_CtcPolygon.cpp ${CMAKE_CURRENT_SOURCE_DIR}/contractors/codac2_CtcPolygon.h + ${CMAKE_CURRENT_SOURCE_DIR}/contractors/codac2_CtcPolytope.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/contractors/codac2_CtcPolytope.h ${CMAKE_CURRENT_SOURCE_DIR}/contractors/codac2_CtcProj.cpp ${CMAKE_CURRENT_SOURCE_DIR}/contractors/codac2_CtcProj.h ${CMAKE_CURRENT_SOURCE_DIR}/contractors/codac2_CtcSegment.cpp @@ -236,6 +238,8 @@ ${CMAKE_CURRENT_SOURCE_DIR}/separators/codac2_SepNot.h ${CMAKE_CURRENT_SOURCE_DIR}/separators/codac2_SepPolygon.cpp ${CMAKE_CURRENT_SOURCE_DIR}/separators/codac2_SepPolygon.h + ${CMAKE_CURRENT_SOURCE_DIR}/separators/codac2_SepPolytope.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/separators/codac2_SepPolytope.h ${CMAKE_CURRENT_SOURCE_DIR}/separators/codac2_SepProj.cpp ${CMAKE_CURRENT_SOURCE_DIR}/separators/codac2_SepProj.h ${CMAKE_CURRENT_SOURCE_DIR}/separators/codac2_SepTransform.cpp diff --git a/src/core/contractors/codac2_CtcPolytope.cpp b/src/core/contractors/codac2_CtcPolytope.cpp new file mode 100644 index 000000000..51484eb40 --- /dev/null +++ b/src/core/contractors/codac2_CtcPolytope.cpp @@ -0,0 +1,31 @@ +/** + * codac2_CtcPolytope.cpp + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2025 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include "codac2_CtcPolytope.h" + +using namespace std; +using namespace codac2; + + +CtcPolytope::CtcPolytope(const Polytope& p) + : Ctc(p.size()), _p(p) +{ + assert(p.size()>0); +} + +void CtcPolytope::contract(IntervalVector& x) const +{ + assert(x.size() == _p.size()); + x &= _p.box(); + if (x.is_empty()) return; + Polytope tmp (_p); + tmp.meet_with_box(x); + x &= tmp.box(true); +} + diff --git a/src/core/contractors/codac2_CtcPolytope.h b/src/core/contractors/codac2_CtcPolytope.h new file mode 100644 index 000000000..ed2430fe1 --- /dev/null +++ b/src/core/contractors/codac2_CtcPolytope.h @@ -0,0 +1,31 @@ +/** + * \file codac2_CtcPolytope.h + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Damien Massé + * \copyright Copyright 2025 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#pragma once + +#include "codac2_Polytope.h" +#include "codac2_CtcWrapper.h" + +namespace codac2 +{ + class CtcPolytope : public Ctc + { + public: + + CtcPolytope(const Polytope& p); + + void contract(IntervalVector& x) const; + inline const Polytope& p() const { return _p; }; + + protected: + + const Polytope _p; + }; + +} diff --git a/src/core/domains/polytope/codac2_Facet.cpp b/src/core/domains/polytope/codac2_Facet.cpp index 975c965bf..f85e460f6 100644 --- a/src/core/domains/polytope/codac2_Facet.cpp +++ b/src/core/domains/polytope/codac2_Facet.cpp @@ -25,10 +25,11 @@ namespace codac2 { namespace Facet_ { -polytope_inclrel relation_Box(const Facet &f, const IntervalVector &b) { +polytope_inclrel relation_Box(const Facet &f, const IntervalVector &b, bool strict) { const Row &row = f.first.get_row(); const double rhs = f.second.get_rhs(); const bool eqcst = f.second.is_eqcst(); + if (eqcst && strict) return inclrel_notinclude | inclrel_disjoint; if (b.is_empty()) return inclrel_includes | inclrel_disjoint; IntervalVector a(b); /* check the vertex that maximizes row */ @@ -37,11 +38,11 @@ polytope_inclrel relation_Box(const Facet &f, const IntervalVector &b) { } Interval maxv = row.dot(a)-rhs; polytope_inclrel r1=0; - if (maxv.ub()<=0.0) { + if (maxv.ub()<=0.0 && (!strict || maxv.ub()<0.0)) { if (!eqcst) return inclrel_includes | inclrel_intersects; else if (maxv.ub()<0.0) return inclrel_notinclude | inclrel_disjoint; r1 = inclrel_includes; - } else if (maxv.lb()<=0.0) { + } else if (maxv.lb()<=0.0 && (!strict || maxv.lb()<0.0)) { r1 = inclrel_mayinclude; } else { r1 = inclrel_notinclude; @@ -51,11 +52,13 @@ polytope_inclrel relation_Box(const Facet &f, const IntervalVector &b) { if (row[i]>0) a[i]=b[i].lb(); else a[i]=b[i].ub(); } Interval minv = row.dot(a)-rhs; - if (minv.lb()>0.0) return inclrel_notinclude | inclrel_disjoint; + if (minv.lb()>0.0 || (strict && minv.lb()>=0.0)) + return inclrel_notinclude | inclrel_disjoint; if (!eqcst) { - if (minv.ub()>0.0) return r1 | inclrel_mayintersect; + if (minv.ub()>0.0 || (strict && minv.ub()>=0.0)) + return r1 | inclrel_mayintersect; return r1 | inclrel_intersects; - } else { + } else { /* eqcst==true -> strict == false */ if (r1[INCLUDES]) { /* maxv.ub == 0 */ if (minv.lb()>=0.0) return inclrel_includes | inclrel_intersects; @@ -74,13 +77,34 @@ void contract_Box(const Facet &f, IntervalVector &b) { const double rhs = f.second.get_rhs(); /* use MulOp:bwd */ IntervalRow x1(row); - MulOp::bwd(Interval(-oo,rhs),x1,b); + MulOp::bwd(f.second.is_eqcst() ? Interval(rhs,rhs) : Interval(-oo,rhs),x1,b); if (b[0].is_empty()) b.set_empty(); } void contract_out_Box(const Facet &f, IntervalVector &b) { const Row &row = f.first.get_row(); const double rhs = f.second.get_rhs(); + if (f.first.is_coord()) { + Index c = f.first.gt_dim(); + Interval val = Interval(rhs)/row[c]; + if (f.second.is_eqcst()) { + if (!val.is_degenerated()) return; /* can't use a value */ + if (val==b[c]) b.set_empty(); + } else { + if (row[c]>0.0) { /* x[c]>val.lb() */ + if (b[c].ub()<=val.lb()) { b.set_empty(); } + else if (b[c].lb()<=val.lb()) + { b[c] = Interval(val.lb(),b[c].ub()); } + } + } + return; + } + Interval a = b.dot(row); + if (a.ub()<=rhs) { b.set_empty(); return; } + if (f.second.is_eqcst()) { + if (a.lb()>=rhs) b.set_empty(); + return; + } /* use MulOp:bwd */ IntervalRow x1(row); MulOp::bwd(Interval(rhs,oo),x1,b); diff --git a/src/core/domains/polytope/codac2_Facet.h b/src/core/domains/polytope/codac2_Facet.h index 518486830..b0f228704 100644 --- a/src/core/domains/polytope/codac2_Facet.h +++ b/src/core/domains/polytope/codac2_Facet.h @@ -275,7 +275,7 @@ namespace Facet_ { const polytope_inclrel inclrel_disjoint = 1<get_map()) { + Facet_::polytope_inclrel pincl = + Facet_::relation_Box(fct,p,true); + if (pincl[Facet_::NOTINCLUDE]) return BoolInterval::FALSE; + if (pincl[Facet_::MAYINCLUDE]) r = BoolInterval::UNKNOWN; + } + return r; +} + bool Polytope::box_is_subset(const IntervalVector& x) const { return this->box().is_subset(x); } @@ -389,6 +404,73 @@ bool Polytope::is_subset(const Polytope& P) return true; } +bool Polytope::is_superset(const Polytope& P) + const { + return P.is_subset((*this)); +} + +bool Polytope::is_interior_subset(const Polytope& P) + const { + const IntervalVector &b2 = P.box(true); + const IntervalVector &b1 = this->box(true); + if (!b1.is_interior_subset(b2)) return false; + for (const auto &fctP : P._facets->get_map()) { + if (this->fast_bound(fctP.first).ub() < fctP.second.rhs) continue; + double l1 = this->bound_row(fctP.first.row); + if (l1box(true); + if (!b1.intersects(p)) return false; + Polytope q(*this); /* TODO : copy DDbuildF2V */ + q.meet_with_box(p); + return !(q.check_empty()); +} + +bool Polytope::intersects(const Polytope& p) const { + assert(!status[INVALID]); + const IntervalVector &b1 = this->box(); + if (!b1.intersects(p.box())) return false; + Polytope q(*this); /* TODO : copy DDbuildF2V */ + q.meet_with_polytope(p); + return !(q.check_empty()); +} + +bool Polytope::is_disjoint(const Polytope& p) const { + return (!this->intersects(p)); +} + +void Polytope::contract_out_Box(IntervalVector &b) const { + assert(!status[INVALID]); + const IntervalVector &b1 = this->_box; + /* we can compute the diff, but as the goal is to return one interval vector, + we do it "by hand" to avoid the generation of too many elements. + also, we quit as soon a the vector is "full" */ + IntervalVector result = IntervalVector::constant(_dim,Interval::empty()); + for (Index i=0;i<_dim;i++) { + std::vector vt = b[i].diff(b1[i]); + if (vt.empty()) continue; + IntervalVector r1(b); + r1[i] = vt[0]; + if (vt.size()>1) r1[i] |= vt[1]; + if (r1[i]==b[i]) return; + result |= r1; + if (b.is_subset(result)) return; + } + for (auto &facet : _facets->get_map()) { + IntervalVector r1(b); + Facet_::contract_out_Box(facet,r1); + result |= r1; + if (b.is_subset(result)) return; + } + b = result; +} + #if 0 polytope_inclrel Polytope::relation_Box(const IntervalVector& p) const { @@ -435,17 +517,6 @@ const Interval& Polytope::operator[](Index i) const { return _box[i]; } -Vector Polytope::mid() const { - assert_release(_dim>=0); - if (_empty) return - Vector::Constant(_dim,std::numeric_limits::quiet_NaN()); - if (!_clpForm) return _box.mid(); - LPclp::lp_result_stat stat = _clpForm->solve(true,0); - if (stat[LPclp::NOTEMPTY] || stat[LPclp::NOTEMPTY_APPROX]) - return _clpForm->getFeasiblePoint().mid(); - return Vector::Constant(_dim,std::numeric_limits::quiet_NaN()); -} - double Polytope::;istance_cst(const Facet &fc) const { assert_release(_dim==fc.row.size()); if (_empty) return -oo; @@ -458,6 +529,23 @@ double Polytope::;istance_cst(const Facet &fc) const { #endif +Vector Polytope::mid() const { + assert_release(!state[INVALID]); + this->update_box(); + return (_box.mid()); +} + +Vector Polytope::mid_in() const { + assert_release(!state[INVALID]); + if (this->is_box()) return (_box.mid()); + std::vector vt = this->vertices(); + if (vt.empty()) return _box.mid(); + Vector ret = vt[0].mid(); + for (Index i=1;i<(Index)vt.size();i++) + ret += vt[i].mid(); + return ret/vt.size(); +} + void Polytope::clear() { assert_release(!state[INVALID]); state=pol_state_init; @@ -1086,6 +1174,25 @@ Polytope operator+ (const Polytope &p1, const Polytope &p2) { return (ret &= bres); } +Polytope Polytope::operator-() const { + assert_release(!state[INVALID]); + if (state[EMPTY]) return Polytope(_dim,true); + CollectFacets cf(_dim); + IntervalVector bbox(-_box); + for (auto &facet : _facets->get_map()) { + FacetBase nf = facet.first; + nf.negate_row(); + std::pair ret = + cf.insert_facet(Facet_::make(nf,-facet.second.rhs, + facet.second.eqcst), + CollectFacets::MIN_RHS); + if (ret.first==-1) return Polytope(_dim, true); /* should not happen */ + } + Polytope ret = Polytope(IntervalVector(bbox),std::move(cf)); + ret.state=this->state & pol_state_init; + return ret; +} + std::ostream& operator<<(std::ostream& os, const Polytope &P) { if (P.state[Polytope::EMPTY]) { diff --git a/src/core/domains/polytope/codac2_Polytope.h b/src/core/domains/polytope/codac2_Polytope.h index 58443976d..cc3d8cc44 100644 --- a/src/core/domains/polytope/codac2_Polytope.h +++ b/src/core/domains/polytope/codac2_Polytope.h @@ -225,11 +225,17 @@ namespace codac2 { const Interval& operator[](Index i) const; /** - * ``middle'' (vector inside the polytope, if possible) - * \return + * ``middle'' of the bbox + * \return the middle of the box */ Vector mid() const; + /** + * point inside the polytope using the vertices + * \return a barycenter of the middle of the vertices + */ + Vector mid_in() const; + /** * bound a constraint (fast), either by a "close" one (+box) * or by the vertices if they are computed. @@ -242,7 +248,7 @@ namespace codac2 { Interval fast_bound(const FacetBase &base) const; /** - * bound a constraint, using either DD + * bound a constraint, using DD * \param r the row * \return the max */ @@ -272,6 +278,15 @@ namespace codac2 { */ BoolInterval contains(const IntervalVector& p) const; + /** + * Checks whether the interior of the polytope contains a given + * point, or includes a box + * + * \param p The point or box to check, enclosed in an IntervalVector. + * \return BoolInterval indicating possible containment. + */ + BoolInterval interior_contains(const IntervalVector& p) const; + /** * Checks inclusion in another polytope * \param P the polytope @@ -279,23 +294,49 @@ namespace codac2 { */ virtual bool is_subset(const Polytope &P) const; + /** + * Checks enclosure of another polytope + * \param P the polytope + * \return true if (*this) encloses P + */ + bool is_superset(const Polytope &P) const; + + /** + * Checks inclusion in the interior of another polytope + * \param P the polytope + * \return true if inclusion is guaranteed + */ + virtual bool is_interior_subset(const Polytope &P) const; + /** intersects a box * \param x the box (IntervalVector) - * \return if the polytope intersects the box + * \return false if the intersection is guaranteed to be empty */ - BoolInterval intersects(const IntervalVector& x) const; + bool intersects(const IntervalVector& x) const; /** intersects a polytope * \param p the polytope - * \return if the polytope intersects the box + * \return false if the intersection is guaranteed to be empty + */ + bool intersects(const Polytope &p) const; + + /** test if it is disjoint with a polytope + * \param p the polytope + * \return true if the intersection is guaranteed to be empty + */ + bool is_disjoint(const Polytope &p) const; + + /** compute the box hull of B diff (*this) + * \param b a box, contracted. */ - BoolInterval intersects(const Polytope &p) const; + void contract_out_Box(IntervalVector &b) const; /** minimize the constraints, removing (possibly) redundant * constraints. */ virtual void minimize_constraints() const; + /************* Box access *************************/ /** @@ -506,6 +547,10 @@ namespace codac2 { * \param p2 second polytope * \return a new polytope */ friend Polytope operator+ (const Polytope &p1, const Polytope &p2); + + /** unary minus + * \return the negation of the current polytope */ + Polytope operator- () const; /*********** Printing and other ``global access'' *********/ diff --git a/src/core/separators/codac2_SepPolytope.cpp b/src/core/separators/codac2_SepPolytope.cpp new file mode 100644 index 000000000..f0e65a109 --- /dev/null +++ b/src/core/separators/codac2_SepPolytope.cpp @@ -0,0 +1,30 @@ +/** + * SepPolygon.cpp + * ---------------------------------------------------------------------------- + * \date 2024 + * \author Simon Rohou, Benoit Desrochers + * \copyright Copyright 2024 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include +#include "codac2_SepPolytope.h" + +using namespace std; + +namespace codac2 +{ + SepPolytope::SepPolytope(const Polytope& p) + : Sep(p.size()), _ctc(p) { } + + BoxPair SepPolytope::separate(const IntervalVector& x) const + { + auto x_in = x; + auto x_out = x; + _ctc.contract(x_out); + _ctc.p().contract_out_Box(x_in); + + assert((x_in | x_out) == x); + return { x_in, x_out }; + } +} diff --git a/src/core/separators/codac2_SepPolytope.h b/src/core/separators/codac2_SepPolytope.h new file mode 100644 index 000000000..049724d00 --- /dev/null +++ b/src/core/separators/codac2_SepPolytope.h @@ -0,0 +1,29 @@ +/** + * \file codac2_SepPolygon.h + * ---------------------------------------------------------------------------- + * \date 2024 + * \author Simon Rohou, Benoit Desrochers + * \copyright Copyright 2024 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#pragma once + +#include "codac2_Polytope.h" +#include "codac2_CtcPolytope.h" +#include "codac2_SepWrapper.h" + +namespace codac2 +{ + class SepPolytope : public Sep + { + public: + + SepPolytope(const Polytope& p); + BoxPair separate(const IntervalVector& x) const; + + protected: + const CtcPolytope _ctc; + + }; +} diff --git a/src/graphics/figures/codac2_Figure2D.h b/src/graphics/figures/codac2_Figure2D.h index 760051b14..dc328d39c 100644 --- a/src/graphics/figures/codac2_Figure2D.h +++ b/src/graphics/figures/codac2_Figure2D.h @@ -891,6 +891,18 @@ namespace codac2 selected_fig()->draw_ellipsoid(e,style); } + /** + * \brief draws a 2D convex polytope on the figure + * + * \param P polytope to draw + * \param style style of the polytope (edge color and fill color) + */ + static void draw_polytope(const Polytope& P, const StyleProperties& style = StyleProperties()) + { + auto_init(); + selected_fig()->draw_polytope(P,style); + } + /** * \brief Draws a trajectory on the figure * From 99bd95359774b3cc800f04120167426e3164b26d Mon Sep 17 00:00:00 2001 From: damien-masse Date: Mon, 15 Dec 2025 16:53:39 +0100 Subject: [PATCH 22/26] Polytope: test contractor and separator + python binding for contractor and separator. --- python/src/core/CMakeLists.txt | 2 + python/src/core/codac2_py_core.cpp | 4 ++ .../contractors/codac2_py_CtcPolytope.cpp | 38 ++++++++++++++++ .../core/separators/codac2_py_SepPolytope.cpp | 37 ++++++++++++++++ tests/CMakeLists.txt | 2 + .../contractors/codac2_tests_CtcPolytope.cpp | 44 +++++++++++++++++++ .../contractors/codac2_tests_CtcPolytope.py | 39 ++++++++++++++++ .../separators/codac2_tests_SepPolytope.cpp | 44 +++++++++++++++++++ .../separators/codac2_tests_SepPolytope.py | 41 +++++++++++++++++ 9 files changed, 251 insertions(+) create mode 100644 python/src/core/contractors/codac2_py_CtcPolytope.cpp create mode 100644 python/src/core/separators/codac2_py_SepPolytope.cpp create mode 100644 tests/core/contractors/codac2_tests_CtcPolytope.cpp create mode 100644 tests/core/contractors/codac2_tests_CtcPolytope.py create mode 100644 tests/core/separators/codac2_tests_SepPolytope.cpp create mode 100644 tests/core/separators/codac2_tests_SepPolytope.py diff --git a/python/src/core/CMakeLists.txt b/python/src/core/CMakeLists.txt index add7fd5b1..5ed12cc16 100644 --- a/python/src/core/CMakeLists.txt +++ b/python/src/core/CMakeLists.txt @@ -32,6 +32,7 @@ contractors/codac2_py_CtcPointCloud.cpp contractors/codac2_py_CtcPolar.cpp contractors/codac2_py_CtcPolygon.cpp + contractors/codac2_py_CtcPolytope.cpp contractors/codac2_py_CtcProj.cpp contractors/codac2_py_CtcSegment.cpp contractors/codac2_py_CtcUnion.cpp @@ -107,6 +108,7 @@ separators/codac2_py_SepInverse.cpp separators/codac2_py_SepNot.cpp separators/codac2_py_SepPolygon.cpp + separators/codac2_py_SepPolytope.cpp separators/codac2_py_SepProj.cpp separators/codac2_py_SepTransform.cpp separators/codac2_py_SepUnion.cpp diff --git a/python/src/core/codac2_py_core.cpp b/python/src/core/codac2_py_core.cpp index 67effe0a3..62cc417a9 100644 --- a/python/src/core/codac2_py_core.cpp +++ b/python/src/core/codac2_py_core.cpp @@ -52,6 +52,7 @@ void export_CtcNot(py::module& m, py::class_,pyCtcInterv void export_CtcPointCloud(py::module& m, py::class_,pyCtcIntervalVector>& ctc); void export_CtcPolar(py::module& m, py::class_,pyCtcIntervalVector>& ctc); void export_CtcPolygon(py::module& m, py::class_,pyCtcIntervalVector>& ctc); +void export_CtcPolytope(py::module& m, py::class_,pyCtcIntervalVector>& ctc); void export_CtcProj(py::module& m, py::class_,pyCtcIntervalVector>& ctc); void export_CtcSegment(py::module& m, py::class_,pyCtcIntervalVector>& ctc); void export_CtcUnion(py::module& m, py::class_,pyCtcIntervalVector>& ctc); @@ -140,6 +141,7 @@ void export_SepInter(py::module& m, py::class_& sep); void export_SepInverse(py::module& m, py::class_& sep); void export_SepNot(py::module& m, py::class_& sep); void export_SepPolygon(py::module& m, py::class_& sep); +void export_SepPolytope(py::module& m, py::class_& sep); void export_SepProj(py::module& m, py::class_& sep); void export_SepTransform(py::module& m, py::class_& sep); void export_SepUnion(py::module& m, py::class_& sep); @@ -194,6 +196,7 @@ PYBIND11_MODULE(_core, m) export_CtcPointCloud(m, py_ctc_iv); export_CtcPolar(m, py_ctc_iv); export_CtcPolygon(m, py_ctc_iv); + export_CtcPolytope(m, py_ctc_iv); export_CtcProj(m, py_ctc_iv); export_CtcSegment(m, py_ctc_iv); export_CtcUnion(m, py_ctc_iv); @@ -305,6 +308,7 @@ PYBIND11_MODULE(_core, m) export_SepInverse(m,py_sep); export_SepNot(m,py_sep); export_SepPolygon(m,py_sep); + export_SepPolytope(m,py_sep); export_SepProj(m,py_sep); export_SepTransform(m,py_sep); export_SepUnion(m,py_sep); diff --git a/python/src/core/contractors/codac2_py_CtcPolytope.cpp b/python/src/core/contractors/codac2_py_CtcPolytope.cpp new file mode 100644 index 000000000..f70030e47 --- /dev/null +++ b/python/src/core/contractors/codac2_py_CtcPolytope.cpp @@ -0,0 +1,38 @@ +/** + * Codac binding (core) + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Simon Rohou, Damien Massé + * \copyright Copyright 2025 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include +#include +#include +#include +#include +#include "codac2_py_Ctc.h" +#include "codac2_py_CtcPolytope_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py): + +using namespace std; +using namespace codac2; +namespace py = pybind11; +using namespace pybind11::literals; + +void export_CtcPolytope(py::module& m, py::class_,pyCtcIntervalVector>& pyctc) +{ + py::class_ exported(m, "CtcPolytope", pyctc, CTCPOLYTOPE_MAIN); + exported + + .def(py::init(), + CTCPOLYTOPE_CTCPOLYTOPE_CONST_POLYTOPE_REF, + "p"_a) + + .def(CONTRACT_BOX_METHOD(CtcPolytope, + VOID_CTCPOLYTOPE_CONTRACT_INTERVALVECTOR_REF_CONST)) + + ; + + py::implicitly_convertible(); +} diff --git a/python/src/core/separators/codac2_py_SepPolytope.cpp b/python/src/core/separators/codac2_py_SepPolytope.cpp new file mode 100644 index 000000000..8aa17f5ce --- /dev/null +++ b/python/src/core/separators/codac2_py_SepPolytope.cpp @@ -0,0 +1,37 @@ +/** + * Codac binding (core) + * ---------------------------------------------------------------------------- + * \date 2025 + * \author Simon Rohou, Damien Massé + * \copyright Copyright 2025 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include +#include +#include +#include +#include "codac2_py_Sep.h" +#include "codac2_py_SepPolytope_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py): + +using namespace std; +using namespace codac2; +namespace py = pybind11; +using namespace pybind11::literals; + +void export_SepPolytope(py::module& m, py::class_& pysep) +{ + py::class_ exported(m, "SepPolytope", pysep, SEPPOLYTOPE_MAIN); + exported + + .def(py::init(), + SEPPOLYTOPE_SEPPOLYTOPE_CONST_POLYTOPE_REF, + "p"_a) + + .def("separate", &SepPolytope::separate, + BOXPAIR_SEPPOLYTOPE_SEPARATE_CONST_INTERVALVECTOR_REF_CONST, + "x"_a) + ; + + py::implicitly_convertible(); +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 639deba05..3e3c51873 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -39,6 +39,7 @@ list(APPEND SRC_TESTS # listing files without extension core/contractors/codac2_tests_CtcInverseNotIn core/contractors/codac2_tests_CtcLazy core/contractors/codac2_tests_CtcPolygon + core/contractors/codac2_tests_CtcPolytope core/contractors/codac2_tests_CtcSegment core/contractors/codac2_tests_linear_ctc ../doc/manual/manual/contractors/geometric/src @@ -90,6 +91,7 @@ list(APPEND SRC_TESTS # listing files without extension core/separators/codac2_tests_SepCtcBoundary core/separators/codac2_tests_SepInverse core/separators/codac2_tests_SepPolygon + core/separators/codac2_tests_SepPolytope core/separators/codac2_tests_SepProj core/separators/codac2_tests_SepTransform diff --git a/tests/core/contractors/codac2_tests_CtcPolytope.cpp b/tests/core/contractors/codac2_tests_CtcPolytope.cpp new file mode 100644 index 000000000..ddc71cb82 --- /dev/null +++ b/tests/core/contractors/codac2_tests_CtcPolytope.cpp @@ -0,0 +1,44 @@ +/** + * Codac tests + * ---------------------------------------------------------------------------- + * \date 2024 + * \author Simon Rohou + * \copyright Copyright 2024 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include +#include +#include +#include + +using namespace std; +using namespace codac2; + +TEST_CASE("CtcPolytope") +{ + std::vector> facets + { { {1,1,1}, 3.0 }, + { {-1,1,1}, 3.0 }, + { {1,-1,1}, 3.0 }, + { {1,1,-1}, 3.0 }, + { {-1,-1,1}, 3.0 }, + { {-1,1,-1}, 3.0 }, + { {1,-1,-1}, 3.0 }, + { {-1,-1,-1}, 3.0 } }; + Polytope p({{-2,2.0},{-2.0,2.0},{-2.0,2.0}}, facets); + CtcPolytope c(p); + + IntervalVector x(3); + c.contract(x); + CHECK(x == IntervalVector({{-2,2},{-2,2},{-2,2}})); + + x = IntervalVector({{1.02,2.0},{1.02,2.0},{1.02,2.0}}); // possible bug + c.contract(x); + CHECK(x.is_empty()); + +// auto p_ctc = pave({{-3,3},{-3,3},{-3,3}}, c, 0.2); +// Figure3D fig_ctc("Paving contractor"); +// fig_ctc.draw_paving(p_ctc); +} + diff --git a/tests/core/contractors/codac2_tests_CtcPolytope.py b/tests/core/contractors/codac2_tests_CtcPolytope.py new file mode 100644 index 000000000..aa538bf22 --- /dev/null +++ b/tests/core/contractors/codac2_tests_CtcPolytope.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python + +# Codac tests +# ---------------------------------------------------------------------------- +# \date 2025 +# \author Simon Rohou +# \copyright Copyright 2024 Codac Team +# \license GNU Lesser General Public License (LGPL) + +import unittest +from codac import * + +class TestSepPolytope(unittest.TestCase): + + def test_SepPolytope(self): + + # Polytope, defined as a set of facets + facets =[ [ Row([1,1,1]), 3.0 ], + [ Row([-1,1,1]), 3.0 ], + [ Row([1,-1,1]), 3.0 ], + [ Row([1,1,-1]), 3.0 ], + [ Row([-1,-1,1]), 3.0 ], + [ Row([-1,1,-1]), 3.0 ], + [ Row([1,-1,-1]), 3.0 ], + [ Row([-1,-1,-1]), 3.0 ] ] + + p = Polytope(IntervalVector([[-2,2],[-2,2],[-2,2]]),facets,True) + c = CtcPolytope(p) + + x = IntervalVector(3) + c.contract(x) + self.assertTrue(x == IntervalVector([[-2,2],[-2,2],[-2,2]])) + + x = IntervalVector([[1.02,2.0],[1.02,2.0],[1.0,2.0]]) + c.contract(x) + self.assertTrue(x.is_empty()) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/core/separators/codac2_tests_SepPolytope.cpp b/tests/core/separators/codac2_tests_SepPolytope.cpp new file mode 100644 index 000000000..b9f08b859 --- /dev/null +++ b/tests/core/separators/codac2_tests_SepPolytope.cpp @@ -0,0 +1,44 @@ +/** + * Codac tests + * ---------------------------------------------------------------------------- + * \date 2024 + * \author Simon Rohou + * \copyright Copyright 2024 Codac Team + * \license GNU Lesser General Public License (LGPL) + */ + +#include +#include +#include +#include +#include + +using namespace std; +using namespace codac2; + +TEST_CASE("SepPolytope") +{ + std::vector> facets + { { {1,1,1}, 3.0 }, + { {-1,1,1}, 3.0 }, + { {1,-1,1}, 3.0 }, + { {1,1,-1}, 3.0 }, + { {-1,-1,1}, 3.0 }, + { {-1,1,-1}, 3.0 }, + { {1,-1,-1}, 3.0 }, + { {-1,-1,-1}, 3.0 } }; + Polytope p({{-2,2.0},{-2.0,2.0},{-2.0,2.0}}, facets); + SepPolytope s(p); + + IntervalVector x(3); + auto xs = s.separate(x); + CHECK(xs.outer == IntervalVector({{-2,2},{-2,2},{-2,2}})); + CHECK(xs.inner == IntervalVector(3)); + + x = IntervalVector({{1.02,2.0},{1.02,2.0},{1.02,2.0}}); + xs = s.separate(x); + CHECK(xs.inner == x); + CHECK(xs.outer.is_empty()); + +} + diff --git a/tests/core/separators/codac2_tests_SepPolytope.py b/tests/core/separators/codac2_tests_SepPolytope.py new file mode 100644 index 000000000..4b423d9f5 --- /dev/null +++ b/tests/core/separators/codac2_tests_SepPolytope.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python + +# Codac tests +# ---------------------------------------------------------------------------- +# \date 2025 +# \author Simon Rohou +# \copyright Copyright 2024 Codac Team +# \license GNU Lesser General Public License (LGPL) + +import unittest +from codac import * + +class TestSepPolytope(unittest.TestCase): + + def test_SepPolytope(self): + + # Polytope, defined as a set of facets + facets =[ [ Row([1,1,1]), 3.0 ], + [ Row([-1,1,1]), 3.0 ], + [ Row([1,-1,1]), 3.0 ], + [ Row([1,1,-1]), 3.0 ], + [ Row([-1,-1,1]), 3.0 ], + [ Row([-1,1,-1]), 3.0 ], + [ Row([1,-1,-1]), 3.0 ], + [ Row([-1,-1,-1]), 3.0 ] ] + + p = Polytope(IntervalVector([[-2,2],[-2,2],[-2,2]]),facets,True) + s = SepPolytope(p) + + x = IntervalVector(3) + inner,outer = s.separate(x) + self.assertTrue(inner == IntervalVector(3)) + self.assertTrue(outer == IntervalVector([[-2,2],[-2,2],[-2,2]])) + + x = IntervalVector([[1.02,2.0],[1.02,2.0],[1.0,2.0]]) + inner,outer = s.separate(x) + self.assertTrue(inner == x) + self.assertTrue(outer.is_empty()) + +if __name__ == '__main__': + unittest.main() From a98a2e11f13e59d24ff9045f6c8c44405c4f3f36 Mon Sep 17 00:00:00 2001 From: damien-masse Date: Mon, 15 Dec 2025 17:40:02 +0100 Subject: [PATCH 23/26] Small improvement to the doc. --- doc/manual/manual/geometry/facets.rst | 8 +++++ doc/manual/manual/geometry/polytope.rst | 17 +++++++++ doc/manual/manual/geometry/src_polytope.cpp | 36 ++++++++++++++++--- examples/polytope_examples/main-manual.cpp | 13 ++----- src/core/domains/polytope/codac2_Polytope.cpp | 4 +-- 5 files changed, 60 insertions(+), 18 deletions(-) diff --git a/doc/manual/manual/geometry/facets.rst b/doc/manual/manual/geometry/facets.rst index 77d9b5913..3bb553190 100644 --- a/doc/manual/manual/geometry/facets.rst +++ b/doc/manual/manual/geometry/facets.rst @@ -8,6 +8,13 @@ Facet classes Facets ------ +.. doxygentypedef:: codac2::Facet + :project: codac + +.. doxygennamespace:: codac2::Facet_ + :project: codac + :members: + .. doxygenclass:: codac2::FacetBase :project: codac :members: @@ -16,6 +23,7 @@ Facets :project: codac :members: + Collection of facets -------------------- diff --git a/doc/manual/manual/geometry/polytope.rst b/doc/manual/manual/geometry/polytope.rst index 76615da50..6c8a163a8 100644 --- a/doc/manual/manual/geometry/polytope.rst +++ b/doc/manual/manual/geometry/polytope.rst @@ -39,3 +39,20 @@ or equalities), or a set of vertices. :end-before: [polytope-1-end] :dedent: 2 +Output of a polytope +-------------------- +Polytopes are displayed as a set of linear constraints and a bounding box. +To display the set of vertices, one can use the +vertices() method. + +.. tabs:: + + .. group-tab:: C++ + + .. literalinclude:: src_polytope.cpp + :language: c++ + :start-after: [polytope-2-beg] + :end-before: [polytope-2-end] + :dedent: 2 + + diff --git a/doc/manual/manual/geometry/src_polytope.cpp b/doc/manual/manual/geometry/src_polytope.cpp index b0e3d57a9..1230c47e9 100644 --- a/doc/manual/manual/geometry/src_polytope.cpp +++ b/doc/manual/manual/geometry/src_polytope.cpp @@ -41,17 +41,43 @@ int main(int argc, char *argv[]) { {0.5,-1,-0.5} ,0 }, { {1.0/3.0,1.0/3.0,-1} ,1 } }; // the first argument is a bounding box, here the whole space - Polytope p2(IntervalVector::Constant(3,Interval()), facets); + Polytope p2(IntervalVector(3), facets); // p1 and p2 are almost the same polytope // [polytope-1-end] - draw_polytope(fig,p1,StyleProperties(Color::dark_red(0.8),"p1")); + // [polytope-2-beg] std::cout << p1 << std::endl; - - std::vector vp2 = p2.vertices(); + /* output, each facet is a row (sequences of double) and a right-hand-side + Polytope(bbox [ [-1.5, 2] ; [0, 3] ; [-1.5, 2] ]) : + Collectfacets : 8 facets + 1 : 1 1 0.75<=3 + 2 : -1 -0.399999 0.6<=0.600001 + 3 : -1 0.5 0.375<=1.5 + 4 : -1 0.5 1.77636e-16<=1.5 + 5 : 0.5 -1 0.75<=0 + 6 : -0.75 -1 0.750001<=5.92119e-16 + 7 : 0.5 -1 -0.5<=0 + 8 : 0.333334 0.333334 -1<=1 + end Collectfacets + EndPolytope + */ + std::vector vp2 = p2.vertices(); /* hull of p2 */ for (auto &v : vp2) std::cout << v << std::endl; - draw_polytope(fig,p2,StyleProperties(Color::dark_blue(0.8),"p2")); + /* output : "vertices" are boxes which enclose the polytope, but each + box may not enclose a "real vertice". E.g. the vertice (0,0,0) is + not inside the second box displayed. + [ [1.99999, 2.00001] ; [0.999999, 1.00001] ; [-2.4743e-14, -1.70641e-15] ] + [ [-7.76463e-15, 1.69539e-14] ; [-3.15401e-14, -2.88916e-15] ; [-2.52802e-14, -2.40763e-15] ] + [ [-1.50001, -1.5] ; [-1.16016e-14, -9.33785e-15] ; [-1.50001, -1.5] ] + [ [-7.11673e-15, 1.609e-14] ; [1.49999, 1.50001] ; [1.99999, 2.00001] ] + [ [-2.47539e-15, 9.90153e-16] ; [2.99999, 3.00001] ; [-2.31036e-15, 3.30051e-15] ] + [ [-1.00001, -0.999999] ; [1, 1.00001] ; [1.83361e-15, 3.20883e-15] ] + */ + // [polytope-2-end] + + fig.draw_polytope(p1,StyleProperties(Color::dark_red(0.8),"p1")); + fig.draw_polytope(p2,StyleProperties(Color::dark_blue(0.8),"p2")); return 0; } diff --git a/examples/polytope_examples/main-manual.cpp b/examples/polytope_examples/main-manual.cpp index 72ef73d40..9daad7669 100644 --- a/examples/polytope_examples/main-manual.cpp +++ b/examples/polytope_examples/main-manual.cpp @@ -8,15 +8,6 @@ using namespace std; using namespace codac2; -void draw_polytope(Figure3D &fig, const Polytope &P, const StyleProperties &st) { - std::vector> facets3D=P.vertices_3Dfacets(); - Vector center = Vector::zero(3); - Matrix transfo = Matrix::Identity(3,3); - for (const std::vector &vec : facets3D) { - fig.draw_polygon(center,transfo,vec,st); - } -} - int main(int argc, char *argv[]) { // std::cout << std::scientific << std::setprecision(20); @@ -44,12 +35,12 @@ int main(int argc, char *argv[]) // p1 and p2 are ``almost'' the same polytope - draw_polytope(fig,p1,StyleProperties(Color::dark_red(0.8),"p1")); + fig.draw_polytope(p1,StyleProperties(Color::dark_red(0.8),"p1")); std::cout << p1 << std::endl; std::vector vp2 = p2.vertices(); for (auto &v : vp2) std::cout << v << std::endl; - draw_polytope(fig,p2,StyleProperties(Color::dark_blue(0.8),"p2")); + fig.draw_polytope(p2,StyleProperties(Color::dark_blue(0.8),"p2")); return 0; } diff --git a/src/core/domains/polytope/codac2_Polytope.cpp b/src/core/domains/polytope/codac2_Polytope.cpp index 07e8a4c35..d19af892d 100644 --- a/src/core/domains/polytope/codac2_Polytope.cpp +++ b/src/core/domains/polytope/codac2_Polytope.cpp @@ -1196,10 +1196,10 @@ Polytope Polytope::operator-() const { std::ostream& operator<<(std::ostream& os, const Polytope &P) { if (P.state[Polytope::EMPTY]) { - os << "Polytope(" << P.state << " empty dim " << P._dim << ")" << std::endl; + os << "Polytope(" /* << P.state */ << " empty dim " << P._dim << ")" << std::endl; return os; } - os << "Polytope(" << P.state << " bbox " << P._box << ") : " << std::endl; + os << "Polytope(" /* << P.state */ << " bbox " << P._box << ") : " << std::endl; os << *P._facets; os << "EndPolytope" << std::endl; return os; From 4c12b26343a4250c412e715f2aee96cb79272684 Mon Sep 17 00:00:00 2001 From: damien-masse Date: Thu, 18 Dec 2025 09:04:14 +0100 Subject: [PATCH 24/26] Few modifications. --- doc/manual/manual/geometry/polytope.rst | 2 ++ .../manual/visualization/3d_visualization.rst | 4 +++ doc/manual/manual/visualization/functions.rst | 4 +++ examples/polytope_examples/main-3Dtest.cpp | 25 ++++++------------- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/doc/manual/manual/geometry/polytope.rst b/doc/manual/manual/geometry/polytope.rst index 6c8a163a8..c899dfaa0 100644 --- a/doc/manual/manual/geometry/polytope.rst +++ b/doc/manual/manual/geometry/polytope.rst @@ -55,4 +55,6 @@ vertices() method. :end-before: [polytope-2-end] :dedent: 2 +Operations on polytopes +----------------------- diff --git a/doc/manual/manual/visualization/3d_visualization.rst b/doc/manual/manual/visualization/3d_visualization.rst index d9d632159..bd9399b50 100644 --- a/doc/manual/manual/visualization/3d_visualization.rst +++ b/doc/manual/manual/visualization/3d_visualization.rst @@ -38,6 +38,7 @@ Geometric shapes - Box - Parallelepiped - Zonotope + - Polytope - Arrow - Parametric surface - Sphere @@ -85,6 +86,9 @@ generally a sequence of adjacent triangles sharing a same vertex. .. doxygenfunction:: codac2::Figure3D::draw_zonotope(const Zonotope&, const StyleProperties&) :project: codac +.. doxygenfunction:: codac2::Figure3D::draw_polytope(const Polytope&, const StyleProperties&) + :project: codac + .. doxygenfunction:: codac2::Figure3D::draw_arrow(const Vector&, const Matrix& A, const StyleProperties&) :project: codac diff --git a/doc/manual/manual/visualization/functions.rst b/doc/manual/manual/visualization/functions.rst index 90216a8b3..841086745 100644 --- a/doc/manual/manual/visualization/functions.rst +++ b/doc/manual/manual/visualization/functions.rst @@ -65,6 +65,7 @@ Geometric shapes - Polygone - Parallelepiped - Zonotope + - Polytope - Pie - Ellipse - Ellipsoid @@ -122,6 +123,9 @@ Geometric shapes .. doxygenfunction:: codac2::Figure2D::draw_zonotope(const Zonotope&, const StyleProperties&) :project: codac +.. doxygenfunction:: codac2::Figure2D::draw_polytope(const Polytope&, const StyleProperties&) + :project: codac + .. doxygenfunction:: codac2::Figure2D::draw_pie(const Vector&, const Interval&, const Interval&, const StyleProperties&) :project: codac diff --git a/examples/polytope_examples/main-3Dtest.cpp b/examples/polytope_examples/main-3Dtest.cpp index 97fbd4869..66f1d2a1f 100644 --- a/examples/polytope_examples/main-3Dtest.cpp +++ b/examples/polytope_examples/main-3Dtest.cpp @@ -8,15 +8,6 @@ using namespace std; using namespace codac2; -void draw_polytope(Figure3D &fig, const Polytope &P, const StyleProperties &st) { - std::vector> facets3D=P.vertices_3Dfacets(); - Vector center = Vector::zero(3); - Matrix transfo = Matrix::Identity(3,3); - for (const std::vector &vec : facets3D) { - fig.draw_polygon(center,transfo,vec,st); - } -} - int main(int argc, char *argv[]) { // std::cout << std::scientific << std::setprecision(20); @@ -40,7 +31,7 @@ int main(int argc, char *argv[]) { {1,-10,-2} ,0 } }; Polytope p(IntervalVector({{-4,4},{-4,4},{-4,4}}), facets, true); - draw_polytope(fig,p,StyleProperties(Color::dark_red(0.8), + fig.draw_polytope(p,StyleProperties(Color::dark_red(0.8), "initial polytope")); std::vector vertices = p.vertices(); @@ -55,7 +46,7 @@ int main(int argc, char *argv[]) StyleProperties(Color::orange(0.6), "box_inflate")); } - draw_polytope(fig,q,StyleProperties(Color::red(0.4), + fig.draw_polytope(q,StyleProperties(Color::red(0.4), "inflate")); Polytope r = p; r.inflate_ball(0.5); @@ -64,7 +55,7 @@ int main(int argc, char *argv[]) StyleProperties(Color::orange(0.6), "ball_inflate")); } - draw_polytope(fig,r,StyleProperties(Color::purple(0.4), + fig.draw_polytope(r,StyleProperties(Color::purple(0.4), "inflate_ball")); Polytope s = p; @@ -74,21 +65,21 @@ int main(int argc, char *argv[]) StyleProperties(Color::black(), "box_unflat")); } - draw_polytope(fig,s,StyleProperties(Color::yellow(0.4), + fig.draw_polytope(s,StyleProperties(Color::yellow(0.4), "unflat")); Polytope t = p; t.homothety(IntervalVector({0,0,-0.5}),2); - draw_polytope(fig,t,StyleProperties(Color::dark_blue(0.8), + fig.draw_polytope(t,StyleProperties(Color::dark_blue(0.8), "homothety")); Polytope u = t; u.meet_with_polytope(p); - draw_polytope(fig,u,StyleProperties(Color::dark_gray(), + fig.draw_polytope(u,StyleProperties(Color::dark_gray(), "intersection")); Polytope v = Polytope::union_of_polytopes({ p,t }); - draw_polytope(fig,v,StyleProperties(Color::cyan(0.4), + fig.draw_polytope(v,StyleProperties(Color::cyan(0.4), "union")); IntervalMatrix M { { cos(PI/3) , sin(PI/3) , 0 }, @@ -98,7 +89,7 @@ int main(int argc, char *argv[]) Polytope w = p.reverse_affine_transform(M,P, IntervalVector({{-4,4},{-4,4},{-4,4}})); - draw_polytope(fig,w,StyleProperties(Color::blue(0.8), + fig.draw_polytope(w,StyleProperties(Color::blue(0.8), "transformation")); return 0; From 258fa6e0eec15b84bbc3d3a27e3c34042a7dfbe9 Mon Sep 17 00:00:00 2001 From: damien-masse Date: Thu, 18 Dec 2025 09:08:09 +0100 Subject: [PATCH 25/26] Remove CLP extension. --- CMakeLists.txt | 13 - src/CMakeLists.txt | 22 - src/extensions/clp/CMakeLists.txt | 66 - src/extensions/clp/FindClp.cmake | 98 -- src/extensions/clp/codac2_Polytope_clp.cpp | 182 --- src/extensions/clp/codac2_Polytope_clp.h | 106 -- src/extensions/clp/codac2_clp.cpp | 1465 -------------------- src/extensions/clp/codac2_clp.h | 393 ------ 8 files changed, 2345 deletions(-) delete mode 100644 src/extensions/clp/CMakeLists.txt delete mode 100644 src/extensions/clp/FindClp.cmake delete mode 100644 src/extensions/clp/codac2_Polytope_clp.cpp delete mode 100644 src/extensions/clp/codac2_Polytope_clp.h delete mode 100644 src/extensions/clp/codac2_clp.cpp delete mode 100644 src/extensions/clp/codac2_clp.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 886ec54c0..cf4943e4d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -155,19 +155,6 @@ find_package(CAPD REQUIRED) endif() -################################################################################ -# Looking for CLP (if needed) -################################################################################ - -option(WITH_CLP "Use CLP for LP in polytopes" OFF) - - if(WITH_CLP) -## add_compile_definitions(WITH_CLP) - include(./src/extensions/clp/FindClp.cmake) - message(STATUS "Found CLP version ${CLP_VERSION}") - message(STATUS "Found CLP inclusion ${CLP_INCLUDE_DIRS}") - endif() - ################################################################################ # Compile sources ################################################################################ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 675b095e3..68af50d6c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,10 +14,6 @@ add_subdirectory(extensions/capd) endif() - if(WITH_CLP) - add_subdirectory(extensions/clp) - endif() - #if(WITH_PYTHON) # add_subdirectory(sympy) #endif() @@ -119,24 +115,6 @@ endif() - if(WITH_CLP) - - file(APPEND ${CODAC_CMAKE_CONFIG_FILE} " - - # Optional 3rd party: - - find_path(CODAC_CLP_INCLUDE_DIR ${PROJECT_NAME}-clp.h - PATH_SUFFIXES include/${PROJECT_NAME}-clp) - set(CODAC_INCLUDE_DIRS \${CODAC_INCLUDE_DIRS} \${CODAC_CLP_INCLUDE_DIR} \${CLP_INCLUDE_DIRS}) - - find_library(CODAC_CLP_LIBRARY NAMES ${PROJECT_NAME}-clp - PATH_SUFFIXES lib) - - set(CODAC_LIBRARIES \${CODAC_LIBRARIES} \${CODAC_CLP_LIBRARY} ${CLP_LIBRARIES}) - ") - - endif() - file(APPEND ${CODAC_CMAKE_CONFIG_FILE} " set(CODAC_LIBRARIES \${CODAC_LIBRARIES} \${CODAC_GRAPHICS_LIBRARY} \${CODAC_CORE_LIBRARY}) diff --git a/src/extensions/clp/CMakeLists.txt b/src/extensions/clp/CMakeLists.txt deleted file mode 100644 index e822f5ad6..000000000 --- a/src/extensions/clp/CMakeLists.txt +++ /dev/null @@ -1,66 +0,0 @@ -# ================================================================== -# Codac - cmake configuration file -# ================================================================== - -list(APPEND CODAC_CLP_SRC - - ${CMAKE_CURRENT_SOURCE_DIR}/codac2_clp.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/codac2_clp.h - ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Polytope_clp.h - ${CMAKE_CURRENT_SOURCE_DIR}/codac2_Polytope_clp.cpp -) - -################################################################################ -# Create the target for libcodac-clp -################################################################################ - - #if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 20) - set(CMAKE_CXX_STANDARD_REQUIRED ON) - #endif() - - add_library(${PROJECT_NAME}-clp ${CODAC_CLP_SRC}) - target_include_directories(${PROJECT_NAME}-clp PUBLIC - ${CLP_INCLUDE_DIRS} - ${CMAKE_CURRENT_SOURCE_DIR} - ) - target_link_libraries(${PROJECT_NAME}-clp PUBLIC ${PROJECT_NAME}-core Eigen3::Eigen ${CLP_LIBRARIES}) - - - ################################################################################ - # For the generation of the PKG file - ################################################################################ - - set(CODAC_PKG_CONFIG_CFLAGS "${CODAC_PKG_CONFIG_CFLAGS} -I\${includedir}/${PROJECT_NAME}-clp" PARENT_SCOPE) - set(CODAC_PKG_CONFIG_LIBS "${CODAC_PKG_CONFIG_LIBS} -l${PROJECT_NAME}-clp" PARENT_SCOPE) - - - ################################################################################ - # Installation of libcodac-clp files - ################################################################################ - - # Getting header files from sources - - foreach(srcfile ${CODAC_CLP_SRC}) - if(srcfile MATCHES "\\.h$" OR srcfile MATCHES "\\.hpp$") - list(APPEND CODAC_CLP_HDR ${srcfile}) - file(COPY ${srcfile} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../../include) - endif() - endforeach() - - # Generating the file codac-clp.h - - set(CODAC_CLP_MAIN_HEADER ${CMAKE_CURRENT_BINARY_DIR}/codac-clp.h) - file(WRITE ${CODAC_CLP_MAIN_HEADER} "/* This file is generated by CMake */\n\n") - file(APPEND ${CODAC_CLP_MAIN_HEADER} "#pragma once\n\n") - foreach(header_path ${CODAC_CLP_HDR}) - get_filename_component(header_name ${header_path} NAME) - file(APPEND ${CODAC_CLP_MAIN_HEADER} "#include <${header_name}>\n") - endforeach() - file(COPY ${CODAC_CLP_MAIN_HEADER} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/../../../include) - - # Install files in system directories - - install(TARGETS ${PROJECT_NAME}-clp DESTINATION ${CMAKE_INSTALL_LIBDIR}) - install(FILES ${CODAC_CLP_HDR} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-clp) - install(FILES ${CODAC_CLP_MAIN_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}-clp) diff --git a/src/extensions/clp/FindClp.cmake b/src/extensions/clp/FindClp.cmake deleted file mode 100644 index 2dfaf90da..000000000 --- a/src/extensions/clp/FindClp.cmake +++ /dev/null @@ -1,98 +0,0 @@ -# FindClp -# ----------- -# -# The module defines the following variables: -# -# COINUTILS_FOUND -# COINUTILS_INCLUDE_DIRS -# COINUTILS_LIBRARIES -# COINUTILS_VERSION -# CLP_FOUND -# CLP_INCLUDE_DIRS -# CLP_LIBRARIES -# CLP_VERSION -# -# and the following imported target (if it does not already exist): -# -# Clp::Clp - The Clp library -# -# -# Requires CMake >= 3.0 - -include(CheckCXXCompilerFlag) -find_package (PkgConfig) - -set (COINUTILS_DIR "${COIN_DIR}" CACHE PATH "Directory to search for CoinUtils") -set (CLP_DIR "${CLP_DIR}" CACHE PATH "Directory to search for Clp") - -if (COINUTILS_DIR) - set (ENV{PKG_CONFIG_PATH} "${COINUTILS_DIR}/lib/pkgconfig:${PKG_CONFIG_PATH}") -endif () -if (CLP_DIR) - set (ENV{PKG_CONFIG_PATH} "${CLP_DIR}/lib/pkgconfig:${PKG_CONFIG_PATH}") -endif () - -pkg_check_modules (CLP_PKG QUIET clp) - -# Look for the library -foreach (_lib ${CLP_PKG_LIBRARIES}) - string (TOUPPER "${_lib}" _upperlib) - find_library (${_upperlib}_LIBRARY NAMES ${_lib} - HINTS ${CLP_PKG_LIBRARY_DIRS} - PATH_SUFFIXES lib) - list (APPEND _req_vars ${_upperlib}_LIBRARY) - list (APPEND CLP_LIBRARIES "${${_upperlib}_LIBRARY}") - mark_as_advanced (${_upperlib}_LIBRARY) - unset (_upperlib) -endforeach () -unset (_lib) - -# Look for the include directory -find_path (COINUTILS_INC_DIR NAMES CoinUtilsConfig.h - HINTS ${CLP_PKG_INCLUDE_DIRS} - PATH_SUFFIXES include include/coin) -find_path (CLP_INC_DIR NAMES ClpConfig.h - HINTS ${CLP_PKG_INCLUDE_DIRS} - PATH_SUFFIXES include include/coin) - -# Look for the version -set (_version_match "VERSION \"[0-9]+[.][0-9]+[.][0-9]+") -if (COINUTILS_INC_DIR) - file (READ "${COINUTILS_INC_DIR}/CoinUtilsConfig.h" _content) - string (REGEX MATCH "COINUTILS_${_version_match}" _match "${_content}") - string (SUBSTRING "${_match}" 19 -1 COINUTILS_VERSION) -endif () - -if (CLP_INC_DIR) - file (READ "${CLP_INC_DIR}/ClpConfig.h" _content) - string (REGEX MATCH "CLP_${_version_match}" _match "${_content}") - string (SUBSTRING "${_match}" 13 -1 CLP_VERSION) -endif () -unset (_content) -unset (_match) -unset (_version_match) - -include (FindPackageHandleStandardArgs) -list (APPEND _req_vars CLP_INC_DIR) -list (APPEND _req_vars COINUTILS_INC_DIR) -find_package_handle_standard_args (Clp REQUIRED_VARS ${_req_vars} - VERSION_VAR CLP_VERSION) -unset (_req_vars) - -if (CLP_FOUND) - set (CLP_INCLUDE_DIRS ${CLP_PKG_INCLUDE_DIRS}) - mark_as_advanced (COINUTILS_DIR) - mark_as_advanced (CLP_DIR) - if (NOT TARGET Clp::Clp) - # For now we make the target global, because this file is included from a - # CMakeLists.txt file in a subdirectory. With CMake >= 3.11, we could make - # it global afterwards with - # set_target_properties(Clp::Clp PROPERTIES IMPORTED_GLOBAL TRUE) - add_library (Clp::Clp INTERFACE IMPORTED GLOBAL) - set_target_properties (Clp::Clp PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${CLP_INCLUDE_DIRS}" - INTERFACE_LINK_LIBRARIES "${CLP_LIBRARIES}") - endif() -endif() - -mark_as_advanced (CLP_INC_DIR COINUTILS_INC_DIR) diff --git a/src/extensions/clp/codac2_Polytope_clp.cpp b/src/extensions/clp/codac2_Polytope_clp.cpp deleted file mode 100644 index bc0ae4b1f..000000000 --- a/src/extensions/clp/codac2_Polytope_clp.cpp +++ /dev/null @@ -1,182 +0,0 @@ -/** - * \file codac2_clp_Polytope.cpp (Polytope methods which use CLP) - * ---------------------------------------------------------------------------- - * \date 2025 - * \author Damien Massé - * \copyright Copyright 2025 - * \license This program is distributed under the terms of - * the GNU Lesser General Public License (LGPL). - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "codac2_Index.h" -#include "codac2_Matrix.h" -#include "codac2_Vector.h" -#include "codac2_Row.h" -#include "codac2_IntervalRow.h" -#include "codac2_IntervalVector.h" -#include "codac2_IntervalMatrix.h" -#include "codac2_inversion.h" -#include "codac2_Parallelepiped.h" -#include "codac2_Zonotope.h" -#include "codac2_Polytope.h" -#include "codac2_Polytope_clp.h" -#include "codac2_Facet.h" -#include "codac2_clp.h" - -using namespace codac2; - - -namespace codac2 { - -Polytope_clp &Polytope_clp::operator=(const Polytope_clp &P) { - assert(!P.state[INVALID]); - this->_dim = P._dim; - this->_box = P._box; - this->_facets = P.state[EMPTY] ? std::make_shared(_dim) : - std::make_shared(*(P._facets)); - this->_clpForm=nullptr; /* FIXME */ - this->_DDbuildF2V=nullptr; - this->_DDbuildV2F=nullptr; - this->state = P.state & (pol_state_empty | - pol_state(1<bound_row_clp(r); -// } -// return Polytope::bound_row_F2V(r); -} - -double Polytope_clp::bound_row_clp(const Row &r) const { - this->build_clpForm(); - _clpForm->setObjective(r); - LPclp::lp_result_stat ret = _clpForm->solve(); - if (ret[EMPTY]) return -oo; - return _clpForm->getValobj().ub(); -} - -void Polytope_clp::build_clpForm() const { - if (clpUptodate) return; - if (state[EMPTY]) { - _clpForm=nullptr; - return; - } - _clpForm = std::make_unique(_dim,_facets,_box); - clpUptodate=true; -} - -void Polytope_clp::minimize_constraints_clp(const Interval &tolerance) const { - assert_release(!state[MINIMIZED]); - this->build_clpForm(); - int ret = _clpForm->minimize_polytope(tolerance, false, !state[BOXUPDATED]); - if (ret==-1) { - this->set_empty_private(); - return; - } - state[NOTEMPTY]=true; - if (!state[BOXUPDATED]) { - _box &= _clpForm->get_bbox(); - state[BOXUPDATED]=true; - } - bool changed=false; - for (Index i=0;i<_facets->nbfcts();i++) { - if (_clpForm->isRedundant(i)) { - _facets->removeFacetById(i+1); - changed=true; - } - } - if (changed) { - std::vector corresp = _facets->renumber(); -/* if (state[F2VFORM]) { - _DDbuildF2V->update_renumber(corresp); - } */ - state[F2VFORM]=false; - clpUptodate=false; state[V2FFORM]=false; - _clpForm=nullptr; - } - state[MINIMIZED]=true; -} - -void Polytope_clp::minimize_constraints() const { - if (state[MINIMIZED]) return; - if (state[EMPTY]) return; - /* default : F2V */ -// if (!state[F2VFORM] && state[CLPFORM]) { - this->minimize_constraints_clp(); -// return; -// } -// this->minimize_constraints_F2V(); -} - - -void Polytope_clp::update_box_clp() const { - assert_release(!state[BOXUPDATED]); - this->build_clpForm(); - int ret = _clpForm->minimize_box(); - if (ret==-1) { - this->set_empty_private(); - return; - } - _box &= _clpForm->get_bbox(); - state[NOTEMPTY]=true; - state[BOXUPDATED]=true; -} - -void Polytope_clp::update_box() const { - if (state[BOXUPDATED]) return; - if (state[EMPTY]) return; -// if (!state[F2VFORM] && state[CLPFORM]) { - this->update_box_clp(); -// return; -// } -// this->update_box_F2V(); -} - - -bool Polytope_clp::check_empty_clp() const { - assert_release(!state[NOTEMPTY] && !state[EMPTY]); - this->build_clpForm(); - int ret = _clpForm->check_emptiness(); - if (ret==-1) { - this->set_empty_private(); - return true; - } - state[NOTEMPTY]=true; - return false; -} - -bool Polytope_clp::check_empty() const { - if (state[NOTEMPTY]) return false; - if (state[EMPTY]) return true; -// if (!state[F2VFORM] && state[CLPFORM]) { - return this->check_empty_clp(); -// } -// return this->check_empty_F2V(); -} - - - -} - diff --git a/src/extensions/clp/codac2_Polytope_clp.h b/src/extensions/clp/codac2_Polytope_clp.h deleted file mode 100644 index 9b83fa117..000000000 --- a/src/extensions/clp/codac2_Polytope_clp.h +++ /dev/null @@ -1,106 +0,0 @@ -/** - * \file codac2_Polytope_clp.h - * ---------------------------------------------------------------------------- - * \date 2025 - * \author Damien Massé - * \copyright Copyright 2025 - * \license This program is distributed under the terms of - * the GNU Lesser General Public License (LGPL). - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "codac2_Index.h" -#include "codac2_Matrix.h" -#include "codac2_Vector.h" -#include "codac2_Row.h" -#include "codac2_IntervalRow.h" -#include "codac2_IntervalVector.h" -#include "codac2_IntervalMatrix.h" -#include "codac2_BoolInterval.h" -#include "codac2_Polytope_util.h" -#include "codac2_Facet.h" -#include "codac2_Polytope_dd.h" -#include "codac2_Polytope.h" -#include "codac2_Parallelepiped.h" -#include "codac2_Zonotope.h" -#include "codac2_clp.h" - -namespace codac2 { - /** - * \class Polytope_clp extends polytope with a clp Form - * Represents a bounded convex polytope as a set of constraints - * and a bounding box (the bounding box is part of the constraints) */ - class Polytope_clp : public Polytope { - - public: - Polytope_clp() : Polytope() {}; - explicit Polytope_clp(Index dim) : Polytope(dim) {}; - explicit Polytope_clp(Index dim, bool empty) : Polytope(dim,empty) {}; - explicit Polytope_clp(const IntervalVector &box) : Polytope(box) {}; - Polytope_clp(const Polytope_clp &P) : Polytope(P) {}; /* FIXME */ - Polytope_clp(Polytope_clp &&P) = default; - virtual ~Polytope_clp() override = default; - explicit Polytope_clp(const Polytope &P) : Polytope(P) {}; /* FIXME */ - explicit Polytope_clp(Polytope &&P) : Polytope(P) {}; /* FIXME */ - explicit Polytope_clp(const std::vector &vertices) : Polytope(vertices) {}; - explicit Polytope_clp(const std::vector &vertices) : Polytope(vertices) {}; - Polytope_clp(const std::vector &vertices, - const CollectFacets &facetsform) : Polytope(vertices, facetsform) {}; - explicit Polytope_clp(const Parallelepiped &par) : Polytope(par) {}; - explicit Polytope_clp(const Zonotope &zon) : Polytope(zon) {}; - Polytope_clp(const IntervalVector &box, - const std::vector> &facets, - bool minimize=false) : Polytope(box,facets,minimize) {}; - - /** - * copy assignment operator - * \param P copy - */ - Polytope_clp &operator=(const Polytope_clp &P); - Polytope_clp &operator=(Polytope_clp &&P) = default; - - /** - * transfer from Polytope - * \param P copy - */ - Polytope_clp &operator=(const Polytope &P); - Polytope_clp &operator=(Polytope &&P); - - void minimize_constraints() const override; - - double bound_row(const Row &r) const override; - - private: - mutable std::unique_ptr _clpForm = nullptr; /* LPclp formulation */ - - mutable bool clpUptodate = false; - - void update_box() const override; - bool check_empty() const override; - void set_empty_private() const override; - - void minimize_constraints_clp(const Interval &tolerance=Interval(0.0)) - const; - void update_box_clp() const; - bool check_empty_clp() const; - double bound_row_clp(const Row &r) const; - void build_clpForm() const; - - void update_box_F2V() const; - bool check_empty_F2V() const; - double bound_row_F2V(const Row &r) const; - void build_DDbuildF2V() const; - }; - -} diff --git a/src/extensions/clp/codac2_clp.cpp b/src/extensions/clp/codac2_clp.cpp deleted file mode 100644 index 2e94968ce..000000000 --- a/src/extensions/clp/codac2_clp.cpp +++ /dev/null @@ -1,1465 +0,0 @@ -/** - * Linear programming problem, use coin-or clp - * ---------------------------------------------------------------------------- - * \date 2025 - * \author Damien Massé - * \copyright Copyright 2025 - * \license This program is distributed under the terms of - * the GNU Lesser General Public License (LGPL). - */ - - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wregister" -#include -#include -#pragma GCC diagnostic pop - - -#include "codac2_BoolInterval.h" -#include "codac2_Facet.h" -#include "codac2_clp.h" - -#undef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG - -namespace codac2 { - -/********** Problem building funtions ***********/ - -LPclp::LPclp (Index dim, std::shared_ptr &facets, - const Row &objvect) : - nbRows(facets->nbfcts()), nbCols(dim), - Afacets(facets), objvect(objvect), - cststat(nbRows,0), - rowBasis(nbRows+nbCols,false), built(false), model(nullptr), - primalSol(nbCols), dualSol(nbRows+nbCols), - primalRay(nbCols), dualRay(nbRows+nbCols), - bbox(nbCols) -{ -} - -LPclp::LPclp (Index dim, std::shared_ptr &facets) : - nbRows(facets->nbfcts()), nbCols(dim), - Afacets(facets), objvect(Row::Zero(nbCols)), - cststat(nbRows,0), - rowBasis(nbRows+nbCols,false), built(false), model(nullptr), - primalSol(nbCols), dualSol(nbRows+nbCols), - primalRay(nbCols), dualRay(nbRows+nbCols), - bbox(nbCols) -{ -} - -LPclp::LPclp (Index dim, std::shared_ptr &facets, - const IntervalVector &box) : - nbRows(facets->nbfcts()), nbCols(dim), - Afacets(facets), objvect(Row::Zero(nbCols)), - cststat(nbRows,0), - rowBasis(nbRows+nbCols,false), built(false), model(nullptr), - primalSol(nbCols), dualSol(nbRows+nbCols), - primalRay(nbCols), dualRay(nbRows+nbCols), - bbox(box) -{ -} - -LPclp::LPclp(const Matrix &mat, const Row &objvect, - const Vector &rhsvect, const std::vector &eqSet) : - nbRows(mat.rows()), nbCols(mat.cols()), - Afacets(std::make_shared(mat,rhsvect,eqSet)), - objvect(objvect), - cststat(nbRows,0), - rowBasis(nbRows+nbCols,false), built(false), model(nullptr), - primalSol(nbCols), dualSol(nbRows+nbCols), - primalRay(nbCols), dualRay(nbRows+nbCols), - bbox(nbCols) -{ -} - -LPclp::LPclp(const Matrix &mat, - const Vector &rhsvect, const std::vector &eqSet) : - nbRows(mat.rows()), nbCols(mat.cols()), - Afacets(std::make_shared(mat,rhsvect,eqSet)), - objvect(Row::Zero(nbCols)), - cststat(nbRows,0), - rowBasis(nbRows+nbCols,false), built(false), model(nullptr), - primalSol(nbCols), dualSol(nbRows+nbCols), - primalRay(nbCols), dualRay(nbRows+nbCols), - bbox(nbCols) -{ -} - -LPclp::LPclp(Index dim) : - nbRows(0), nbCols(dim), - objvect(Row::Zero(nbCols)), - built(false), model(nullptr), - primalSol(nbCols), primalRay(nbCols), - bbox(nbCols) -{ -} - -#if 0 -/* no copy constructor it shares the facet pointer */ -LPclp::LPclp(const LPclp &P) : - nbRows(P.nbRows), nbCols(P.nbCols), - Afacets(P.Afacets), objvect(P.objvect), cststat(P.cststat), - rowBasis(nbRows+nbCols,false), built(false), model(nullptr), - primalSol(nbCols), dualSol(nbRows+nbCols), - primalRay(nbCols), dualRay(nbRows+nbCols), - bbox(P.bbox), timeout(P.timeout), maxIteration(P.maxIteration), - tolerance(P.tolerance) -{ -} -#endif - -LPclp::~LPclp() { - delete(model); -} - -void LPclp::addFacetToCoinBuild(CoinBuild &buildObject, - const Facet &facet, lp_cststatus status, - int *row2Index, double *row2Vals) const { - double bndUp = ((status[REMOVED] || status[INACTIVE] - || status[REDUNDANT]) ? COIN_DBL_MAX : facet.second.get_rhs()); - double bndLow = (!facet.second.is_eqcst() || - (status[REMOVED] || status[INACTIVE] || status[REDUNDANT]) ? - -COIN_DBL_MAX : facet.second.get_rhs()); - if (built_emptytest) { - for (Index j=0;j a = this->Afacets->insert_facet(facet); - if (a.first==0) return 0; - this->cststat.push_back(0); - this->dualSol.resize(nbRows+nbCols+1); - this->dualRay.resize(nbRows+nbCols+1); - if (built) { - CoinBuild buildObject; - Index nbCols2=nbCols+(built_emptytest ? 1 : 0); - int* row2Index = new int[nbCols2]; - for(Index i=0;iaddFacetToCoinBuild(buildObject,facet,0,row2Index,row2Vals); - delete row2Index; - if (built_emptytest) delete row2Vals; - model->addRows(buildObject); - this->status=1<addConstraint(Facet_::make(vst,rhs,isEq)); -} - -Index LPclp::updateConstraint(Index id) { - const Facet &fct = (*(*this->Afacets)[id]); - if (idsetRowUpper(id,fct.second.get_rhs()); - if (fct.second.is_eqcst()) - model->setRowLower(id,fct.second.get_rhs()); - this->status=1<dualSol.resize(nbRows+nbCols); - this->dualRay.resize(nbRows+nbCols); - this->cststat.push_back(0); - if (built) { - CoinBuild buildObject; - Index nbCols2=nbCols+(built_emptytest ? 1 : 0); - int* row2Index = new int[nbCols2]; - for(Index i=0;iaddFacetToCoinBuild(buildObject,fct,0,row2Index,row2Vals); - delete row2Index; - if (built_emptytest) delete row2Vals; - model->addRows(buildObject); - this->status=1<objvect=objvect; - if (built && !built_emptytest) { /* change the values of the objective */ - for (Index i =0; isetObjectiveCoefficient(i, objvect[i]); - this->status=1<setRowUpper(cst,fct->second.get_rhs()); - if (fct->second.is_eqcst()) - model->setRowLower(cst,fct->second.get_rhs()); - } - this->status=1<setRowUpper(cst,DBL_MAX); - model->setRowLower(cst,-DBL_MAX); - } - this->status=1<setColumnBounds(i,bbox[i].lb(), bbox[i].ub()); - } -} - - -/******* coin-or building and configuration **********/ - -void LPclp::buildModel(bool emptytest) { - assert_release(model==nullptr); - model = new ClpSimplex(); - - model->setLogLevel(0); - model->setOptimizationDirection(-1); /* Maximize */ - - model->setMaximumIterations(this->maxIteration); - model->setMaximumSeconds(this->timeout); - model->setPrimalTolerance(this->tolerance); - model->setDualTolerance(this->tolerance); - - /* set dimension */ - Index nbCols2=nbCols+(emptytest ? 1 : 0); - model->resize(0,nbCols2); - this->update_model_bbox(); - if (emptytest) { - model->setColumnBounds(nbCols,-COIN_DBL_MAX,COIN_DBL_MAX); - } - - /* adding the rows using BuildObject ? */ - built_emptytest=emptytest; - CoinBuild buildObject; - int* row2Index = new int[nbCols2]; - for(Index i=0;iaddFacetToCoinBuild(buildObject, - *((*Afacets)[k]),cststat[k],row2Index,row2Vals); - } - model->addRows(buildObject); - delete row2Vals; - delete row2Index; - - /* set the objective */ - for (Index i =0; isetObjectiveCoefficient(i, (emptytest ? 0.0 : objvect[i])); - } - if (emptytest) - model->setObjectiveCoefficient(nbCols, 1.0); - built=true; -} - -/* adding/removing emptiness testing on a built model */ -void LPclp::setModel(bool emptytest) { - if (!built) { - buildModel(emptytest); - return; - } - if (emptytest==built_emptytest) return; - if (emptytest) { - int* col2Index=new int[nbRows]; - for(Index i=0;isecond.is_eqcst() ? 0.0 : 1.0; } - model->addColumn - (nbRows,col2Index,col2Vals,-COIN_DBL_MAX,COIN_DBL_MAX,1.0); - for (Index i =0; isetObjectiveCoefficient(i, 0.0); - } else { - int nb=(int) nbCols; - model->deleteColumns(1,&nb); - for (Index i =0; isetObjectiveCoefficient(i, objvect[i]); - } - built_emptytest=emptytest; -} - - -/* reinitialise the solution */ -void LPclp::reset_solution() { - status=0; - rowBasis.assign(nbRows+nbCols,false); - Valobj=Interval(); - primalSol.setZero(); - dualSol.setZero(); - primalRay.setZero(); - dualRay.setZero(); -} - - -/********** Solving functions *******************/ - -/***** launch one solving, returns the status *******/ -LPclp::lp_result_stat LPclp::solve(bool checkempty, int option) { - this->setModel(checkempty); - reset_solution(); - model->primal(0,option | 1); - int stat = model->status(); - if (stat==-1 || stat>2) { - status=1<status(),checkempty); -} - -int LPclp::minimize_eqpolytope() { - std::vector triMat; - std::vector triRhs; - Index nbCsts=0; - std::vector mainCol; - for (Index i=0; iAfacets)[i]->second.is_eqcst()) continue; - if (cststat[i][REMOVED] || cststat[i][INACTIVE] || cststat[i][REDUNDANT]) - continue; - triMat.emplace_back((*this->Afacets)[i]->first.get_row()); - triRhs.emplace_back((*this->Afacets)[i]->second.get_rhs()); - for (Index j=0;jbVal) { - bVal=a; - bCol=j; - } - } - if (bCol==-1) { - if (triRhs[nbCsts].contains(0)) { - cststat[i][REDUNDANT]=true; - continue; - } - for (Index j=0;jsolve(true); - if (stat[EMPTY]) return -1; - return 0; -} - -int LPclp::minimize_box() { - /* minimize box */ - for (Index i=0;isetObjective(a); - lp_result_stat stat=this->solve(false,(i!=0 ? 4 : 0)); - if (stat[EMPTY]) return -1; - if (stat[BOUNDED]) { - bbox[i] = min(bbox[i],this->Valobj.ub()); - } - } - for (Index i=0;isetObjective(a); - lp_result_stat stat=this->solve(false,4); - if (stat[EMPTY]) return -1; - if (stat[BOUNDED]) { - bbox[i] = min(bbox[i],this->Valobj.ub()); - } - } - this->update_model_bbox(); - return 0; -} - -int LPclp::minimize_polytope(const Interval &tolerance, bool checkempty, - bool checkbox) { - int nb = this->minimize_eqpolytope(); - if (nb==-1) return -1; - if (checkempty) - if (check_emptiness()==-1) return -1; - if (checkbox) - if (this->minimize_box()==-1) return -1; - for (Index i=nbRows-1;i>=0;i--) { - const auto &fct = (*this->Afacets)[i]; - if (cststat[i][REMOVED] || - cststat[i][INACTIVE] || - cststat[i][REDUNDANT]) - continue; - if (fct->second.is_eqcst()) { nb++; continue; } - this->setActive(i,false); - this->setObjective(fct->first.get_row()); - lp_result_stat stat=this->solve(false,4); - if (stat[EMPTY]) return -1; - if (stat[BOUNDED]) { - if (this->Valobj.ub()<=fct->second.get_rhs()+tolerance.ub() - && this->Valobj.lb()<=fct->second.get_rhs()+tolerance.lb()) { - cststat[i][REDUNDANT]=true; - continue; - } - } - nb++; - this->setActive(i,true); - } - return nb; -} - - -/*************************************************************/ -/**** Validation of the results of coin-or ***************/ -/*************************************************************/ - - -/************ correction of inverse matrix *******************/ -/**** basis is a nb*N matrix and basisInverse is ******/ -/**** an approximation of a nb*nb submatrix of basis ******/ -/**** the columns i part of the submatrix are defined ******/ -/**** by (*revbasics)[i]!=1 ( if nb!=N ) ******/ -/**** modify basisInverse to contain the "true" inverse ******/ -/**** of the submatrix of basis ******/ -/**** if A is the submatrix and B is the approximate ******/ -/**** inverse, we compute Id-BA = Err ******/ -/**** then basisInverse = (Id+Eps)*B with ******/ -/**** Eps = Err*(Id+Err+Err^2+...) ******/ - -/**** code for approximation of infinite sum enclosure ****/ -/**** should be in codac2 ? ****/ -IntervalMatrix LPclp::infinite_sum_enclosure (const IntervalMatrix& M) { - size_t n = M.cols(); - Matrix TmpM = M.mig(); -#if 0 - // computing the mag of the eigen matrix - auto sit = M.reshaped().cbegin(); - auto dit = TmpM.reshaped().begin(); - for (;sit!=M.reshaped().cend(); ++sit,++dit) { - (*dit) = (*sit).mag(); - } -#endif - - // Floyd algorithm below - for (size_t k=0;k0.0) { - for (size_t r=0;r0.0 && TmpM(r,k)0.0 && TmpM(k,c)Afacets)[i]->second.get_rhs(); - } - for (Index i=0;i &wholeBasis, Index nbRowsInBasis, - bool checkempty) const { - for (Index i=0;ifirst.get_row(); - if (checkempty && !(*Afacets)[i]->second.is_eqcst()) - initSum[nbCols]+=dualVect[i]; - } - for (Index i=0;isecond.is_eqcst()) - continue; /* ... except for equalities */ - if (dualVect[r].ub()<0.0) { return BoolInterval::FALSE; } - if (dualVect[r].lb()<0.0) { ok = BoolInterval::UNKNOWN; } - } - /* vérifie les variables colonnes, qui doivent - être libres si bbox est unbounded, - ainsi que les contraintes inactives */ - for (Index i=nbRowsInBasis;i0.0) { return BoolInterval::FALSE; } - if (dualVect[r].ub()>0.0) { ok = BoolInterval::UNKNOWN; } - } - if (bbox[c].lb()==-oo) { - if (dualVect[r].ub()<0.0) { return BoolInterval::FALSE; } - if (dualVect[r].lb()<0.0) { ok = BoolInterval::UNKNOWN; } - } - } - for (Index i=0;i0.0)) { ok=BoolInterval::FALSE; break; } - } - return ok; -} - - - -/* this function tests all part sequentially. It starts - from the result of CLP solve */ -LPclp::lp_result_stat LPclp::testGeneralApproach(int stat,bool checkempty) { - /* 1. récupérer les données : - * wholeBasis : la base utilisée pour inverser contraintes - et RHS - (à la fois lignes, et colonnes) - contient nbCols(+1 si checkempty) éléments - * basis : la matrice de la "base" - nbCols(+1) * nbCols(+1) - * basisInverse : son inverse - */ - Index nbCols2 = nbCols+(checkempty ? 1 : 0); - std::vector wholeBasis; - wholeBasis.reserve(nbCols2); - Index nbRowsInBasis=0; - /* row basics */ - for (Index i=0;igetRowStatus(i)!=ClpSimplex::Status::basic) { - wholeBasis.push_back(i); - nbRowsInBasis++; - rowBasis[i]=true; - } - dualSol[i]=model->dualRowSolution()[i]; - } - /* col basics */ - double slack=0.0; /* only for emptiness checking */ - for (Index i=0;igetColumnStatus(i)!=ClpSimplex::Status::basic) { - wholeBasis.push_back(i+nbRows); - rowBasis[i+nbRows]=true; - } - if (idualColumnSolution()[i]; - primalSol[i]=model->primalColumnSolution()[i]; - } - else slack = model->primalColumnSolution()[i]; - } - assert((Index) wholeBasis.size()==nbCols2); - - std::vector revbasics(nbRows,-1); - int *basics=new int[nbRows]; - model->getBasics(basics); - for (Index i=0;iBInvRow qui "inverse" la c-ème colonne de Amat */ - } - delete basics; - - IntervalMatrix basis= - IntervalMatrix::Zero(nbCols2,nbCols2); - IntervalMatrix basisInverse= - IntervalMatrix::Zero(nbCols2,nbCols2); - - /* fill the basis with only the rows first */ - for (Index c=0;cAfacets)[r]->first.get_row()[c]; - } - } - if (checkempty) { - for (Index i=0;igetBInvRow(revbasics[c],invCol); - for (Index k=0;k modify it. - */ - if (checkempty) { - assert_release(stat!=1); - if (stat==2) { - double *ray = model->unboundedRay(); - assert_release(ray!=nullptr); - assert_release(ray[nbCols]>0.0); - for (Index i=0;i=0 : normally, it's ok; slack<0.0 => empty , - dual value should prove the empty case */ - if (slack<0.0) { - dualRay=dualSol; - stat=1; - } - } - } - - /* basis rows : nb constraints, ordonnées par wholeBasis - des variables, = wholeBasis[i] - nbRows - cols : variables */ - /* basisinverse rows : variables - cols : nb constraints, ordonnées par wholeBasis - variables, wholeBasis[i]-nbRows. - obj*basisInverse : combinaisons de contraintes pour - obtenir obj - basisInverse*rhs : point qui satisfait rhs */ - - /* stat = 0 : CLP said optimal - stat = 1 : CLP said empty - stat = 2 : CLP said unbounded */ - if (stat==1) { /* test only dual infeasibility */ - /* if checkempty=true, we already have the ray, - otherwise we build it */ - double *ray=nullptr; - if (!checkempty) { - double *ray = model->infeasibilityRay(true); - assert_release(ray!=nullptr); /* false => bug of CLP */ - /* true for infeasibilityRay means that we have also - the negation of the "error" term after the rows */ - for (Index i=0;i val * Amat : variables (~0) - val*Amat*basisInverse => basisInverse.cols */ - } -#ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG - std::cout << "dualRay: " << dualRay << "\n"; -#endif - IntervalRow errorNeum=IntervalRow::Zero(nbCols2); - if (checkempty) { - errorNeum[nbCols]=-1.0; - } - BoolInterval ok = this->checkDualForm(dualRay, errorNeum, - basisInverse, wholeBasis, nbRowsInBasis, checkempty); -#ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG - std::cout << "dualRay with error: " << dualRay << "\n"; - std::cout << "ok: " << ok << "\n"; -#endif - if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) { - Interval product = this->dotprodrhs(dualRay); - if (checkempty) Valobj=Interval(-oo,product.ub()); - if (product.lb()>=0.0) ok=BoolInterval::FALSE; - else if (product.ub()>=0) ok=BoolInterval::UNKNOWN; - } -#ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG - std::cout << "ok after check1: " << ok << "\n"; -#endif - if (ok==BoolInterval::TRUE) { - status[EMPTY]=true; - if (!checkempty) Valobj.set_empty(); - delete ray; - return status; - } - /* attempt with Neumaier */ - if (!checkempty && !bbox.is_unbounded()) { - /* restart with the ray */ - for (Index i=0;isecond.is_eqcst()) - assert_release(ray[i]<0.0); - dualRay[i]=ray[i]; - } - Interval productNeum = this->dotprodrhs(dualRay); - productNeum -= errorNeum.head(nbCols).dot(bbox); - if (productNeum.ub()<0.0) { - status[EMPTY]=true; - status[EMPTY_BBOX]=true; - if (!checkempty) Valobj.set_empty(); - else Valobj=Interval(-oo,productNeum.ub()); - delete ray; - return status; - } - } else { - /* other approach : we try to compensate with other values */ - IntervalMatrix comp(nbRows,nbCols2); - { - IntervalVector v = IntervalVector::Ones(nbRows); - for (Index j=0;jfirst.get_row()))* - basisInverse.topRows(nbCols); - if ((*Afacets)[j]->second.is_eqcst()) v[j]=0.0; - } - if (checkempty) comp += v*basisInverse.row(nbCols); - } - /* for each epsilon value in dualSol, try to find a complement - in comp */ - std::vector okrow(nbRows,Interval(0.0,oo)); - int nbok=nbRows; - for (Index j=0;jsecond.is_eqcst()) { okrow[j].init(); } - } - for (Index i=0;i0.0) { - if (dualRay[r].lb()<=0.0) { - okrow[j].set_empty(); nbok--; continue; - } else { - Interval u(dualRay[r].lb()); - u /= comp(j,i).ub(); - okrow[j] &= Interval((*Afacets)[j]->second.is_eqcst() ? - -oo : 0.0, - u.lb()); - if (okrow[j].is_empty()) { nbok--; continue; } - } - } else if (comp(j,i).ub()==0.0) { - if (dualRay[r].lb()<0.0) { - okrow[j].set_empty(); nbok--; - } - continue; - } else { - if (dualRay[r].lb()>=0.0) continue; - Interval u(dualRay[r].lb()); - u /= comp(j,i).ub(); - okrow[j] &= Interval(u.ub(),oo); - if (okrow[j].is_empty()) { nbok--; continue; } - } - } - if (!checkempty) dualRay[r]=ray[r]; - else dualRay[r]=dualSol[r]; - } - /* recompute errorNeum */ - if (nbok>0) { - for (Index i=0;iokrow[j].ub()) dualRay[j]=okrow[j].mid(); - break; - } - errorNeum=IntervalRow::Zero(nbCols2); - for (Index i=0;ifirst.get_row(); - if (checkempty && !(*Afacets)[i]->second.is_eqcst()) - errorNeum[nbCols]+=dualRay[i]; - } - if (checkempty) { - errorNeum[nbCols]-=1.0; - } - IntervalRow error = errorNeum*basisInverse; - ok=BoolInterval::TRUE; - for (Index i=0;isecond.is_eqcst()) - dualRay[r] &= Interval(0.0,oo); -// if (dualSol[r].ub()<0.0) { ok = BoolInterval::FALSE; break; } -// if (dualSol[r].lb()<0.0) { ok = BoolInterval::UNKNOWN; break; } - } - /* on refait les vérification sur les variables colonnes */ - if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) { - /* vérifie les variables colonnes, qui doivent - être libres, ainsi que les contraintes inactives */ - for (Index i=nbRowsInBasis;i0.0) - { ok=BoolInterval::FALSE; break; } - if (dualRay[r].ub()>0.0) - { ok = BoolInterval::UNKNOWN; } - } - if (bbox[c].lb()==-oo) { - if (dualRay[r].ub()<0.0) - { ok= BoolInterval::FALSE; break; } - if (dualRay[r].lb()<0.0) - { ok = BoolInterval::UNKNOWN; } - } - } - if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) - for (Index i=0;i0.0)) - { ok=BoolInterval::FALSE; break; } - } - } - if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) { - Interval product = this->dotprodrhs(dualRay); - if (checkempty) Valobj=Interval(-oo,product.ub()); - if (product.lb()>=0.0) ok=BoolInterval::FALSE; - else if (product.ub()>=0) ok=BoolInterval::UNKNOWN; - } -#ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG - std::cout << "ok after check2: " << ok << "\n"; - std::cout << "dualRay: " << dualRay << "\n"; -#endif - if (ok==BoolInterval::TRUE) { - status[EMPTY]=true; - if (!checkempty) Valobj.set_empty(); - delete ray; - return status; - } - } - } - delete ray; - if (ok==BoolInterval::UNKNOWN) { - status[EMPTY_APPROX]=true; - } else { - status[ERROR_PRIMAL_CHECK]=true; - } - /* we should try to bound the result, even if empty check failed */ - /* for that we use basisInverse directly */ - if (!checkempty) - { - IntervalRow dSol= objvect*basisInverse; - dualSol.setZero(); - bool ok=true; - for (Index i=0;isecond.is_eqcst() && dSol[i].lb()<0.0) - { ok=false; break; } - dualSol[r]=dSol[i]; - } - if (ok) { - for (Index i=nbRowsInBasis;i0.0 && bbox[c].ub()==+oo) { ok=false; break; } - if (dSol[i].lb()<0.0 && bbox[c].lb()==-oo) { ok=false; break; } - } - } - if (ok) { - Interval product = this->dotprodrhs(dualSol); - status[BOUNDED]=true; - Valobj = min(Valobj,product); - } - } - return status; - } - /* following : stat=0 or stat=2, hence we look for nonemptiness - primalSol is supposed to give a good place */ - { - /* 1) we modify primalSol to get a box around the "optimal" place */ - /* note: should be done only for checkempty=false - (we do not _really_ want a box around the "center" of the polytope */ - IntervalVector errorPart(nbRows); - for (Index i=0;ifirst.get_row()*primalSol - - (*Afacets)[i]->second.get_rhs(); - } - if (!checkempty) { - IntervalVector errorInBasis= IntervalVector::Zero(nbCols2); - /* except for equalities, - error must be always negative, the real error is the positive part */ - for (Index i=0;isecond.is_eqcst()) { - errorInBasis[i] = errorPart[r]; continue; - } - errorInBasis[i]=max(errorPart[r],Interval::zero()); - /* negative is ok */ - } - for (Index i=nbRowsInBasis;ibbox[c].ub()) - errorInBasis[i] = primalSol[c]-bbox[c].ub(); - else if (primalSol[c].lb()first.get_row()*primalSol - - (*Afacets)[i]->second.get_rhs(); - } - } - - /* check the different constraints - if (!checkempty), - do not check for the basis, as it will be ~0 but is really 0 - by construction (which is why we do not use primalSol.mid() - or equivalent : we know that there exists a point in primalSol - where the basis is 0, but that's all */ -#ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG - std::cout << "primalSol with correction : " << primalSol << "\n"; - std::cout << "errorPart : " << errorPart << "\n"; -#endif - Interval mxVal(-1.0); - for (Index i=0;isecond.is_eqcst()) errorPart[i]=abs(errorPart[i]); - mxVal=max(errorPart[i],mxVal); - if (mxVal.lb()>0.0) break; - } - } - if (mxVal.ub()<=0.0) { - status[NOTEMPTY]=true; - Valobj = max(Valobj,objvect.dot(primalSol)); - } else if (mxVal.lb()<=0.0) { - if (!checkempty) { - /* change of approach, we slightly move - primalSol and hope to prove the non-emptiness - we use another vector to save primalSol and get - it back if needed */ - IntervalVector primalSol2=primalSol & bbox; - bool ok=true; - if (!primalSol2.is_empty()) { - primalSol2=primalSol2.mid(); - for (Index i=0;isecond.is_eqcst()) continue; - /* we should not even try, in this case */ - Interval err = (*Afacets)[i]->first.get_row()*primalSol2 - - (*Afacets)[i]->second.get_rhs(); - if (err.ub()>=0.0) - primalSol2 -= (1.1*err.ub()/(*Afacets)[i]->first.get_row().squaredNorm())*(*Afacets)[i]->first.get_row(); - } - primalSol2=primalSol2 & bbox; - if (!primalSol2.is_empty()) { - primalSol2=primalSol2.mid(); - for (Index i=0;ifirst.get_row()*primalSol2 - - (*Afacets)[i]->second.get_rhs(); - } -#ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG - std::cout << "new primalSol : " << primalSol2 << "\n"; - std::cout << "errorPart 2 : " << errorPart << "\n"; -#endif - for (Index i=0;i0.0) { - ok=false; /* failed */ - break; - } - if ((*Afacets)[i]->second.is_eqcst() && errorPart[i].lb()<0.0) { - ok=false; /* failed */ - break; - } - } - } else ok=false; - } else ok=false; - if (ok) { - status[NOTEMPTY]=true; - primalSol = primalSol2; - Valobj = max(Valobj,objvect*primalSol); - - } else { - status[NOTEMPTY_APPROX]=true; - } - } else { - status[NOTEMPTY_APPROX]=true; - } - } else { - status[ERROR_PRIMAL_CHECK]=true; - } - } - if (stat==2) { - /* check unboundedness. This is the last step - (alternative is stat==0) */ - if (!checkempty) { /* needs to build the ray */ - double *ray = model->unboundedRay(); - assert_release(ray!=nullptr); - for (Index i=0;ifirst.get_row()*primalRay; - } - if (!checkempty) { - /* if !checkempty, the ray "follows" some facets, - so we need to guarantee that it will not cross it */ - IntervalVector errorInBasis= IntervalVector::Zero(nbCols); - for (Index i=0;isecond.is_eqcst()) - { errorInBasis[i] = errorPart[r]; continue; } - errorInBasis[i]=max(errorPart[r],Interval::zero()); - /* negative is ok */ - } - for (Index i=nbRowsInBasis;i-oo) - errorInBasis[i]=primalRay[r]; - else - errorInBasis[i]=max(primalRay[r],Interval::zero()); - else - if (bbox[c].lb()>-oo) - errorInBasis[i]=min(primalRay[r],Interval::zero()); - } - /* compute the correction */ - IntervalVector correction=basisInverse*errorInBasis; - primalRay -= correction; - for (Index i=0;ifirst.get_row()*primalRay; - } - } - Interval mxVal(-1.0); - for (Index i=0;i-oo) mxVal = max(-primalSol[i],mxVal); - if (mxVal.lb()>0.0) break; - } - if (mxVal.lb()<=0.0) { - for (Index i=0;isecond.is_eqcst()) errorPart[i]=abs(errorPart[i]); - mxVal=max(errorPart[i],mxVal); - if (mxVal.lb()>0.0) break; - } - } - if (mxVal.lb()<=0.0) { - if (mxVal.ub()>0.0 && !checkempty) { - /* change of approach, we slightly move - the value and hope to prove the validity - we use another vector to save primalSol and get - it back if needed */ - /* for bbox, we create a IntervalVector for possible - primalRay */ - IntervalVector admissibleRay(nbCols); - for (Index i=0;i-oo) - admissibleRay[i] &= Interval(-oo,0); - } - IntervalVector primalRay2=primalRay & admissibleRay; - if (!primalRay2.is_empty()) { - primalRay2 = primalRay2.mid(); - for (Index i=0;isecond.is_eqcst()) continue; - /* we should not even try, in this case */ - Interval err = (*Afacets)[i]->first.get_row()*primalRay2; - if (err.ub()>=0.0) { - primalRay2 -= (1.1*err.ub()/(*Afacets)[i]->first.get_row().squaredNorm())*(*Afacets)[i]->first.get_row(); - } - } - primalRay2=primalRay2 & admissibleRay; - if (!primalRay2.is_empty()) { - primalRay2.mid(); - for (Index i=0;ifirst.get_row()*primalRay2; - } -#ifdef CODAC2_EXTENSION_POLYTOPE_CLP_DEBUG - std::cout << "new primalRay : " << primalRay2 << "\n"; - std::cout << "errorPart 2 : " << errorPart << "\n"; -#endif - bool ok=true; - for (Index i=0;i0.0) { - ok=false; /* failed */ - break; - } - } - if (ok) { - Interval evolobj = - checkempty ? Interval(1) : objvect.dot(primalRay2); - if (evolobj.lb()>0.0) { - status[UNBOUNDED]=true; - primalRay = primalRay2; - return status; - } - } - } - } - } - Interval evolobj = checkempty ? Interval(1) : - objvect.dot(primalRay); - if (mxVal.ub()<=0.0 && evolobj.lb()>0.0) - status[UNBOUNDED]=true; - else if (evolobj.ub()>0.0) - status[UNBOUNDED_APPROX]=true; - else status[ERROR_DUAL_CHECK]=true; - } else { - status[ERROR_DUAL_CHECK]=true; - } - return status; - } - { - /* finally, check boundedness */ - /* compute the error term , and reverse it with basis. */ - /* val : csts => val * Amat : variables => - val*Amat*basisInverse => basisInverse.cols */ - /* if checkempty, the goal may be to prove the global - boundedness of the polytope */ - BoolInterval ok; - IntervalRow errorNeum; - if (checkempty) { - errorNeum=IntervalRow::Zero(nbCols2); - errorNeum[nbCols]= -1.0; - } else { - errorNeum=-objvect; - } - ok = this->checkDualForm(dualSol, errorNeum, - basisInverse, wholeBasis, nbRowsInBasis, checkempty); - if (ok==BoolInterval::TRUE) { - status[BOUNDED]=true; - Valobj = min(Valobj,this->dotprodrhs(dualSol)); - return status; - } - /* Neumaier's approach , only with !checkempty */ - if (!checkempty && !bbox.is_unbounded()) { - /* rebuild the solution */ - for (Index i=0;idualRowSolution()[i]; - if (!(*Afacets)[i]->second.is_eqcst()) - assert_release(model->dualRowSolution()[i]>=0.0); - } - for (Index i=0;idualColumnSolution()[i]; - } - Interval productNeum = this->dotprodrhs(dualSol); - productNeum -= errorNeum.head(nbCols).dot(bbox); - status[BOUNDED_BBOX]=true; - status[BOUNDED]=true; - Valobj = min(Valobj,productNeum); - return status; - } else if (!checkempty) { - /* other approach : we try to compensate with other values */ - /* for each epsilon value in dualSol, try to find a complement - in comp */ - IntervalMatrix comp(nbRows,nbCols); - { - for (Index j=0;jfirst.get_row()))* - basisInverse; - } - } - std::vector okrow(nbRows,Interval(0.0,oo)); - int nbok=nbRows; - for (Index j=0;jsecond.is_eqcst()) { okrow[j].init(); } - } - for (Index i=0;i0.0) { - if (dualSol[r].lb()<=0.0) { - okrow[j].set_empty(); nbok--; continue; - } else { - Interval u(dualSol[r].lb()); - u /= comp(j,i).ub(); - okrow[j] &= Interval((*Afacets)[j]->second.is_eqcst() ? -oo : 0.0, - u.lb()); - if (okrow[j].is_empty()) { nbok--; continue; } - } - } else if (comp(j,i).ub()==0.0) { - if (dualSol[r].lb()<0.0) { - okrow[j].set_empty(); nbok--; - } - continue; - } else { - if (dualSol[r].lb()>=0.0) continue; - Interval u(dualSol[r].lb()); - u /= comp(j,i).ub(); - okrow[j] &= Interval(u.ub(),oo); - if (okrow[j].is_empty()) { nbok--; continue; } - } - } - dualSol[r]=model->dualRowSolution()[r]; - } - /* recompute errorNeum */ - if (nbok>0) { - for (Index i=0;idualRowSolution()[i]; - } - for (Index i=0;idualColumnSolution()[i]; - } - for (Index j=0;jokrow[j].ub()) dualSol[j]=okrow[j].mid(); - break; - } - errorNeum=-objvect; - for (Index i=0;ifirst.get_row(); - } - IntervalRow error = errorNeum*basisInverse; - ok=BoolInterval::TRUE; - for (Index i=0;isecond.is_eqcst()) - dualRay[r] &= Interval(0.0,oo); -// if (dualSol[r].ub()<0.0) { ok = BoolInterval::FALSE; break; } -// if (dualSol[r].lb()<0.0) { ok = BoolInterval::UNKNOWN; break; } - } - /* on refait les vérification sur les variables colonnes */ - if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) { - /* vérifie les variables colonnes, qui doivent - être libre, ainsi que les contraintes inactives */ - for (Index i=nbRowsInBasis;i0.0) - { ok=BoolInterval::FALSE; break; } - if (dualSol[r].ub()>0.0) - { ok = BoolInterval::UNKNOWN; } - } - if (bbox[c].lb()==-oo) { - if (dualSol[r].ub()<0.0) - { ok= BoolInterval::FALSE; break; } - if (dualSol[r].lb()<0.0) - { ok = BoolInterval::UNKNOWN; } - } - } - } - if ((ok & BoolInterval::TRUE)==BoolInterval::TRUE) { - for (Index i=0;i0.0)) - { ok=BoolInterval::FALSE; break; } - } - } - } - if (ok==BoolInterval::TRUE) { - status[BOUNDED]=true; - Valobj = min(Valobj,this->dotprodrhs(dualSol)); - return status; - } - } - if (ok==BoolInterval::UNKNOWN) { - status[BOUNDED_APPROX]=true; - } else { - status[ERROR_DUAL_CHECK]=true; - } - } - return status; -} - -/** simplified writing of lp_result_stat for debugging **/ -std::ostream& print_lp_stat(std::ostream& os, const LPclp::lp_result_stat x) { - if (x[LPclp::CHANGED]) { - os << "Not_computed"; return os; - } - if (x[LPclp::ERROR_LPCOIN]) { - os << "CLP_failed"; return os; - } - /* primal */ - if (x[LPclp::EMPTY]) { - os << "Empty"; - if (x[LPclp::EMPTY_BBOX]) os << "(bbox)"; - return os; - } else if (x[LPclp::EMPTY_APPROX]) { - os << "Empty(app)-"; - } else if (x[LPclp::NOTEMPTY]) { - os << "NonEmpty-"; - } else if (x[LPclp::NOTEMPTY_APPROX]) { - os << "NonEmpty(app)-"; - } else if (x[LPclp::ERROR_PRIMAL_CHECK]) { - os << "Error(prim)"; - } else os << "?(prim)"; - if (x[LPclp::UNBOUNDED]) { - os << "Unbounded"; - } else if (x[LPclp::UNBOUNDED_APPROX]) { - os << "Unbounded(app)"; - } else if (x[LPclp::BOUNDED]) { - os << "Bounded"; - if (x[LPclp::BOUNDED_BBOX]) os << "(bbox)"; - } else if (x[LPclp::BOUNDED_APPROX]) { - os << "Bounded(app)"; - } else if (x[LPclp::ERROR_DUAL_CHECK]) { - os << "Error(dual)"; - } else os << "?(dual)"; - return os; -} - -} - diff --git a/src/extensions/clp/codac2_clp.h b/src/extensions/clp/codac2_clp.h deleted file mode 100644 index 9677e7be3..000000000 --- a/src/extensions/clp/codac2_clp.h +++ /dev/null @@ -1,393 +0,0 @@ -/** - * \file codac2_clp.h - * ---------------------------------------------------------------------------- - * \date 2025 - * \author Damien Massé - * \copyright Copyright 2025 - * \license This program is distributed under the terms of - * the GNU Lesser General Public License (LGPL). - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "codac2_Index.h" -#include "codac2_Matrix.h" -#include "codac2_Vector.h" -#include "codac2_Row.h" -#include "codac2_IntervalRow.h" -#include "codac2_IntervalVector.h" -#include "codac2_IntervalMatrix.h" -#include "codac2_BoolInterval.h" -#include "codac2_Facet.h" - -class ClpSimplex; /* instead of including ClpSimplex.hpp */ -class CoinBuild; - -namespace codac2 { - - -/** \class LPclp - * \brief LPclp class, to modelise different LP problems - * - */ -class LPclp { - public: - /** \brief Constructor with the collection of facets - * - * LP problem : max objvect X s.t. mat X <= rhsvect - * - * \param dim dimension of the space - * \param facets the facets - * \param objvect the objective */ - LPclp (Index dim, std::shared_ptr &facets, - const Row &objvect); - - /** \brief Constructor with the collection of facets, no objective - * - * LP problem : max objvect X s.t. mat X <= rhsvect - * - * \param dim dimension of the space - * \param facets the facets */ - LPclp (Index dim, std::shared_ptr &facets); - - /** \brief Constructor with the collection of facets, bbox, no objective - * - * LP problem : max objvect X s.t. mat X <= rhsvect - * - * \param dim dimension of the space - * \param facets the facets - * \param box the bounding box for Neumaier's approx and constraints */ - LPclp (Index dim, std::shared_ptr &facets, - const IntervalVector &box); - - /** \brief Constructor from a matrix : create a new CollectFacets - * - * LP problem : max objvect X s.t. mat X <= rhsvect - * - * \param mat Matrix of the constraints (constraints in rows) - * \param objvect Vector of objectives (length = mat.cols()) - * \param rhsvect RHS of the contraints (length = mat.rows()) - * \param eqSet rows which are equalities - */ - LPclp (const Matrix &mat, const Row &objvect, - const Vector &rhsvect, - const std::vector &eqSet=std::vector()); - - /** \brief Constructor of the constraints, objective = 0 - * - * LP constraint : mat X <= rhsvect - * Can be used either to enter the objective afterwards - * or the check non-emptiness, or minimize the set of constraints - * - * \param mat Matrix of the constraints (constraints in rows) - * \param rhsvect RHS of the contraints (length = mat.rows()) - * \param eqSet rows which are equalities - */ - LPclp (const Matrix &mat, const Vector &rhsvect, - const std::vector &eqSet=std::vector()); - - /** \brief Copy constructor is deleted, to avoir sharing the facets. - * use the constructor if needed. - */ - LPclp (const LPclp& P) = delete; - - /** \brief Empty constructor - * \param dim number of variables - */ - LPclp(Index dim); - - /** \brief destructor */ - ~LPclp(); - - /** \brief set the bounding box - * \param box the new box - */ - void set_bbox(const IntervalVector &box); - - /** \brief get bounding box (after minimization) - * \return the bbox - */ - IntervalVector get_bbox(); - - /** \brief Add a constraint (warning : only for "local" CollectFacets) - * \param facet the facet - * \return the row number of the constraint, or -1 if the - * insertion failed (an constraint with the same base exists) */ - Index addConstraint(const Facet &facet); - /** \brief Add a constraint (warning : only for "local" CollectFacets) - * \param vst the (row) vector - * \param rhs its RHS - * \param isEq true if it is an equality - * \return the row number of the constraint, or the number of the - * existing constraint if a constraint with the same base was modified */ - Index addConstraint(const Row &vst, double rhs, bool isEq=false); - /** \brief Update or add a facet (for "external" CollectFacets) - * \param id the index of the facet - * \return id if ok, or -1 if failure */ - Index updateConstraint(Index id); - /** \brief Set the objvective - * \param objvect the new objective */ - void setObjective(const Row &objvect); - /** \brief Set the ''activity'' of a constraint - * inactive constraints <=> rhs put to oo - * \param cst row number of the constraint - * \param state new state (default true) - */ - void setActive(Index cst, bool state=true); - /** \brief Get the ''activity'' of a constraint - * inactive constraints <=> rhs put to oo - * \param cst row number of the constraint - * \return the ''activity'' of the constraint - */ - bool getActive(Index cst) const; - /** \brief Get the ''redundant'' status of a constraint - * can be used after a call to minimize() - * \param cst row number of the constraint - * \return the ''activity'' of the constraint - */ - bool isRedundant(Index cst) const; - - /** \brief Get the set of facets - \return the facets - */ - const CollectFacets &getFacets() const; - /** \brief Get the collections of facets as pointer - \return the facets - */ - std::shared_ptr getFacetsPtr() ; - /** \brief Get the objective row - * \return the objective - */ - const Row &getObjvect() const; - /** \brief Build the matrix - * \return the matrix - */ - Matrix getMat() const; - /** \brief Build the RHS vector - * \return the rhs vector - */ - Vector getRhsvect() const; - /** \brief Get a single constraint, as an facet - * \return the rhs vector - */ - CollectFacets::mapCIterator getCst(Index i) const; - - /** \brief Status a constraint (internal) - * \arg \c REMOVED Constraint removed or never added - * \arg \c INACTIVE Inactive constraint - * \arg \c REDUNDANT Detected as redundant by \c minimize() - * \arg \c LOCKED Forced in (not used) - * \arg \c SETIN Equality (not used) - */ - enum CSTSTATUS { - REMOVED, /* constraint has been removed (or never added) */ - INACTIVE, /* inactive, can be made active */ - REDUNDANT, /* shown redundant wrt other constraints */ - LOCKED, /* must be used for this objective */ - SETIN, /* equality forced */ - SIZE_CSTSTATUS - }; - using lp_cststatus = std::bitset; - - /** \brief Results of a solving process - * Different status are defined, with respect to - * primal results (existence of points in the polytope) - * and dual results (existence of constaints matching the - * objective). Also, results about the basis are computed. - * \c LPRESULT is used in a bitset to describes the different - * results. - * \arg \c CHANGED No result because not computed or modified - * \arg \c EMPTY Polytope is empty - * \arg \c EMPTY_APPROX LP-coin says empty, but the verification - * was not conclusive - * \arg \c EMPTY_BBOX Emptiness was proved thanks to Neumaier's approach - * \arg \c NOTEMPTY The analysis gave a guaranteed point - * \arg \c NOTEMPTY_APPROX LP-coin says non-empty, but the - * verification of its result is not clear - * \arg \c UNBOUNDED Polytope is unbounded - * \arg \c UNBOUNDED_APPROX LP-coin says unbounded, but the - * verification was not conclusive - * \arg \c BOUNDED A bound is found for the objective - * \arg \c BOUNDED_APPROX LP-coin gave a bound, but the - * verification was not conclusive - * \arg \c BOUNDED_BBOX Bound was proved thanks to the bbox - * \arg \c ERROR_LPCOIN LP-coin returned an error - * \arg \c ERROR_PRIMAL_CHECK The checking of LP-coin primal (empty or not) result failed. - * \arg \c ERROR_DUAL_CHECK The checking of LP-coin dual (unbounded or not) result failed. - */ - enum LPRESULT { - CHANGED, - EMPTY, - EMPTY_APPROX, - EMPTY_BBOX, - NOTEMPTY, - NOTEMPTY_APPROX, - UNBOUNDED, - UNBOUNDED_APPROX, - BOUNDED, - BOUNDED_APPROX, - BOUNDED_BBOX, - ERROR_LPCOIN, - ERROR_PRIMAL_CHECK, - ERROR_DUAL_CHECK, - SIZE_LPRESULT - }; - /** \code{.cpp} - using lp_result_stat = std::bitset; - \endcode */ - using lp_result_stat = std::bitset; - - /** \brief Solve the problem, i.e. build it if needed, call lp-coin - * and check the result. - * \param checkempty check for emptiness: add a column in the model - * and maximize on this column - * \param option for now, the only values are 0 (normal) or 4 (restart - * from previous solutions if possible). */ - lp_result_stat solve(bool checkempty=false, int option=0); - - /** \brief detect redundant equality constraints - * returns the number of remaining equality constraints, -1 if empty - * tolerance works as follows : - * each new equality is added to a (pseudo)-triangular matrix - * of IntervalVector - * when a row with all coefficients containing 0 appears, - * a) if 0 is in the rhs, declare the row as redundant - * b) if not, and all coefficients are exactly 0, returns empty - * c) if bounding box, use it to try to prove emptiness, if not - * possible keep the row (and hope for emptiness later?) - * \return -1 if the polytope is empty, else the nb of irredudant - * equality constraints (box not included) - */ - int minimize_eqpolytope(); - - /** \brief check emptiness - * \return -1 if the polytope is empty, else 0 - */ - int check_emptiness(); - - /** \brief minimize the box - * \return -1 if the polytope is empty, else 0 - */ - int minimize_box(); - - /** \brief detect redundant constraints, with a tolerance. - * tolerance works as follows : - * max is the interval computed by maximizing without the constraint - * rhs the rhs of the constraint - * max_l <= max_u <= rhs => redundant - * max_l <= rhs <= max_u => redundant if rhs + tol_up >= max_u - * rhs <= max_l <= max_u => redundant if - * rhs+tol_up >= max_u AND rhs+tol_down >= max_l - * \param tolerance tolerance for redundant constraints - * \param checkempty check if the polytope is empty - * \param checkbox minimize the box - * \return -1 if the polytope is empty, else the nb of irredundant - * contraints (box not included) - */ - int minimize_polytope(const Interval &tolerance, bool checkempty=true, - bool checkbox=true); - - /** \brief Returns the value of the objective after - * solving. The value is guaranteed as an overapproximation. - * E.g. empty means that the polytope is empty, but +oo means only - * that the polytope may be unbounded */ - const Interval &getValobj() const; - /** \brief Returns a solution point (as a ``small'' box) - * (with \c NOTEMPTY and \c NOTEMPTY_APPROX result). - * If a specific emptiness checking was done, then - * the point is in the ``middle'' of the polytope */ - const IntervalVector &getFeasiblePoint() const; - /** \brief Returns a dual solution (as a ``small'' box) - * (with \c BOUNDED and \c BOUNDED_APPROX result). - * For a emptiness checking, the vector may prove the - * global boundedness of the polytope */ - const IntervalRow &getBoundedRow() const; - /** \brief Returns a emptiness vector - * (with \c EMPTY and \c EMPTY_APPROX result). */ - const IntervalRow &getEmptyRow() const; - /** \brief Returns a unboundness vector - * (with \c UNBOUNDED and \c UNBOUNDED_APPROX result). */ - const IntervalVector &getUnboundedVect() const; - - - private: - /* initial problem */ - Index nbRows, nbCols; /* dimension of the matrix */ - std::shared_ptr Afacets; /* the facets */ - Row objvect; /* objective vector */ - std::vector cststat; - - lp_result_stat status; - std::vector rowBasis; /* rows + columns */ - bool built; - bool built_emptytest; - ClpSimplex *model=nullptr; - Interval Valobj; - IntervalVector primalSol; /* primal solution , size nbCols */ - IntervalRow dualSol; /* dual solution, size nbRows + nbCols */ - IntervalVector primalRay; /* infinite ray, size nbCols */ - IntervalRow dualRay; /* emptiness solution, size nbRows + nbCols */ - - IntervalVector bbox; - double timeout=4.0; - double maxIteration=200; - double tolerance=1e-9; - - void addFacetToCoinBuild(CoinBuild &buildObject, - const Facet &facet, lp_cststatus status, - int *row2Index, double *row2Vals) const; - - void buildModel(bool emptytest); - void setModel(bool emptytest); - void update_model_bbox(); - - void correctBasisInverse(IntervalMatrix &basisInverse, - const IntervalMatrix &basis) const; - void reset_solution(); - Interval dotprodrhs(const IntervalRow &d) const; - - BoolInterval checkDualForm(IntervalRow &dualVect, - IntervalRow &initSum, const IntervalMatrix &basisInverse, - const std::vector &wholeBasis, Index nbRowsInBasis, - bool checkempty) const; - lp_result_stat testGeneralApproach(int stat, bool checkempty); - - - static IntervalMatrix infinite_sum_enclosure (const IntervalMatrix& M); - -}; - - -std::ostream& print_lp_stat(std::ostream& os, const LPclp::lp_result_stat x); -// std::ostream& operator<<(std::ostream& os, const LPclp &lp); - -// inline const Matrix &LPclp::getMat() const { return Amat; } /* TODO */ -inline const Row &LPclp::getObjvect() const { return objvect; } -// inline const Vector &LPclp::getRhsvect() const { return rhsvect; } /* TODO */ -inline CollectFacets::mapCIterator - LPclp::getCst(Index i) const { return (*Afacets)[i]; } -inline const Interval &LPclp::getValobj() const { return Valobj; } -inline bool LPclp::isRedundant(Index cst) const { return cststat[cst][REDUNDANT]; } - inline const IntervalVector &LPclp::getFeasiblePoint() const { return this->primalSol; } -inline const IntervalRow &LPclp::getBoundedRow() const { return this->dualSol; } -inline const IntervalRow &LPclp::getEmptyRow() const { return this->dualRay; } -inline const IntervalVector &LPclp::getUnboundedVect() const { return this->primalRay; } -inline void LPclp::set_bbox(const IntervalVector &box) { - this->bbox=box; - if (this->built) this->update_model_bbox(); -} -inline IntervalVector LPclp::get_bbox() { - return this->bbox; -} - -} - From f974f6c56ea030615e39f24fb0fe15a2c827dd2a Mon Sep 17 00:00:00 2001 From: damien-masse Date: Thu, 18 Dec 2025 09:10:35 +0100 Subject: [PATCH 26/26] Remove clp tests --- tests/CMakeLists.txt | 8 - tests/extensions/clp/codac2_tests_clp.cpp | 259 ---------------------- 2 files changed, 267 deletions(-) delete mode 100644 tests/extensions/clp/codac2_tests_clp.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3e3c51873..3e3c2b77d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -128,14 +128,6 @@ if (WITH_CAPD) set(CODAC_LIBRARIES ${CODAC_LIBRARIES} ${PROJECT_NAME}-capd capd::capd) endif() -# CLP test -if (WITH_CLP) - list(APPEND SRC_TESTS - extensions/clp/codac2_tests_clp - ) - set(CODAC_LIBRARIES ${CODAC_LIBRARIES} ${PROJECT_NAME}-clp ${CLP_LIBRARIES}) -endif() - # IBEX test #if (WITH_IBEX) #find_package(IBEX REQUIRED) diff --git a/tests/extensions/clp/codac2_tests_clp.cpp b/tests/extensions/clp/codac2_tests_clp.cpp deleted file mode 100644 index 602b5c9c0..000000000 --- a/tests/extensions/clp/codac2_tests_clp.cpp +++ /dev/null @@ -1,259 +0,0 @@ -/** - * Codac tests - * ---------------------------------------------------------------------------- - * \date 2025 - * \author Damien Massé - * \copyright Copyright 2025 Codac Team - * \license GNU Lesser General Public License (LGPL) - */ - -#include -#include -#include -#include -#include - -using namespace std; -using namespace codac2; - -TEST_CASE("Polytope-CLP-nobox") -{ - /* non empty, bounded polytope : deformed octahedron not with // constraints */ - { - Matrix x({ - { 1.5, 1.5, 1.4 }, - { 0.5, 1.4, -0.5 }, - { 1.4, -0.5, 0.5 }, - { -0.5, 0.5, 1.5 }, - { 0.5, -0.5, -1.5 }, - { -0.5, -1.5, 0.5 }, - { -1.5, 0.5, -0.5 }, - { -1.5, -1.5, -1.5 } - }); - Vector r({3.0,3.0,-1.0,3.0,-1.0,-1.0,3.0,-1.0}); - - LPclp clp(x,r); - LPclp::lp_result_stat res = clp.solve(true); - CHECK(res[LPclp::NOTEMPTY]); - IntervalVector diff = x*clp.getFeasiblePoint()-r; - CHECK(diff.ub().maxCoeff()<0.0); - CHECK(res[LPclp::BOUNDED]); - IntervalRow dual = clp.getBoundedRow().head(x.rows()); - CHECK(dual.lb().maxCoeff()>=0.0); - CHECK((dual*x).mig().maxCoeff()<=1e-10); - - Row obj({1.0,1.0,1.0}); - clp.setObjective(obj); - res = clp.solve(false); - CHECK(res[LPclp::NOTEMPTY]); - diff = x*clp.getFeasiblePoint()-r; - CHECK(diff.ub().maxCoeff()<1e-8); /* can be >0 near the basis */ - CHECK(res[LPclp::BOUNDED]); - dual = clp.getBoundedRow().head(x.rows()); - CHECK(dual.lb().maxCoeff()>=0.0); - CHECK((dual*x-obj).mig().maxCoeff()<=1e-10); - } - - { - /* non empty, bounded polytope : deformed octahedron with // constraints */ - Matrix x({ - { 1.5, 1.5, 1.5 }, - { 0.5, 1.5, -0.5 }, - { 1.5, -0.5, 0.5 }, - { -0.5, 0.5, 1.5 }, - { 0.5, -0.5, -1.5 }, - { -0.5, -1.5, 0.5 }, - { -1.5, 0.5, -0.5 }, - { -1.5, -1.5, -1.5 } - }); - Vector r({3.0,3.0,-1.0,3.0,-1.0,-1.0,3.0,-1.0}); - - LPclp clp(x,r); - LPclp::lp_result_stat res = clp.solve(true); - CHECK(res[LPclp::NOTEMPTY]); - IntervalVector diff = x*clp.getFeasiblePoint()-r; - CHECK(diff.ub().maxCoeff()<0.0); - CHECK(res[LPclp::BOUNDED_APPROX]); - IntervalRow dual = clp.getBoundedRow().head(x.rows()); - CHECK(dual.lb().maxCoeff()>=0.0); - CHECK((dual*x).mig().maxCoeff()<=1e-10); - - Row obj({1.0,1.0,1.0}); - clp.setObjective(obj); - res = clp.solve(false); - CHECK(res[LPclp::NOTEMPTY]); - diff = x*clp.getFeasiblePoint()-r; - CHECK(diff.ub().maxCoeff()<0.0); - CHECK(res[LPclp::BOUNDED]); - dual = clp.getBoundedRow().head(x.rows()); - CHECK(dual.lb().maxCoeff()>=0.0); - CHECK((dual*x-obj).mig().maxCoeff()<=1e-10); - } - - { - /* empty polytope : deformed octahedron with non// constraints */ - Matrix x({ - { 1.5, 1.5, 1.4 }, - { 0.5, 1.4, -0.5 }, - { 1.4, -0.5, 0.5 }, - { -0.5, 0.5, 1.5 }, - { 0.5, -0.5, -1.5 }, - { -0.5, -1.5, 0.5 }, - { -1.5, 0.5, -0.5 }, - { -1.5, -1.5, -1.5 } - }); - Vector r({3.0,3.0,-1.0,0.9,-1.0,-1.0,-2.0,-1.0}); - - LPclp clp(x,r); - LPclp::lp_result_stat res = clp.solve(true); - CHECK(res[LPclp::EMPTY]); - IntervalRow dual = clp.getEmptyRow().head(x.rows()); - CHECK(dual.lb().minCoeff()>=0.0); - CHECK((dual*x).mig().maxCoeff()<=1e-10); - CHECK((dual.dot(r)).ub()<0.0); - } - - { - /* empty polytope : deformed octahedron with // constraints */ - Matrix x({ - { 1.5, 1.5, 1.5 }, - { 0.5, 1.5, -0.5 }, - { 1.5, -0.5, 0.5 }, - { -0.5, 0.5, 1.5 }, - { 0.5, -0.5, -1.5 }, - { -0.5, -1.5, 0.5 }, - { -1.5, 0.5, -0.5 }, - { -1.5, -1.5, -1.5 } - }); - Vector r({3.0,3.0,-1.0,0.9,-1.0,-1.0,-2.0,-1.0}); - - LPclp clp(x,r); - LPclp::lp_result_stat res = clp.solve(true); - CHECK(res[LPclp::EMPTY_APPROX]); /* TO CHANGE */ - IntervalRow dual = clp.getEmptyRow().head(x.rows()); -// CHECK(dual.lb().minCoeff()>=0.0); - CHECK((dual*x).mig().maxCoeff()<=1e-10); - CHECK((dual.dot(r)).ub()<0.0); - } - - { - /* unbounded polytope : deformed octahedron with non // constraints */ - Matrix x({ - { 1.5, 1.5, 1.6 }, - { 0.5, 1.6, -0.5 }, - { 1.6, -0.5, 0.5 }, - { -0.5, 0.5, 1.5 }, - { 0.5, -0.5, -1.6 }, - { -1.6, 0.5, -0.6 }, - }); - Vector r({3.0,3.0,-1.0,3.0,-1.0,3.0}); - - LPclp clp(x,r); - LPclp::lp_result_stat res = clp.solve(true); - CHECK(res[LPclp::NOTEMPTY]); - IntervalVector diff = x*clp.getFeasiblePoint()-r; - CHECK(diff.ub().maxCoeff()<0.0); - CHECK(res[LPclp::UNBOUNDED]); - IntervalVector vect = clp.getUnboundedVect(); - CHECK((x*vect).ub().maxCoeff()<=0.0); - - Row obj({0.0,-1.0,-1.0}); - clp.setObjective(obj); - res = clp.solve(false); - CHECK(res[LPclp::NOTEMPTY]); - diff = x*clp.getFeasiblePoint()-r; - CHECK(diff.ub().maxCoeff()<1e-10); - CHECK(res[LPclp::UNBOUNDED]); - diff = x*clp.getUnboundedVect(); - CHECK(diff.ub().maxCoeff()<=1e-10); - } - - { - /* unbounded polytope : deformed octahedron with // constraints */ - Matrix x({ - { 1.5, 1.5, 1.5 }, - { 0.5, 1.5, -0.5 }, - { 1.5, -0.5, 0.5 }, - { -0.5, 0.5, 1.5 }, - { 0.5, -0.5, -1.5 }, - { -1.5, 0.5, -0.5 }, - }); - Vector r({3.0,3.0,-1.0,3.0,-1.0,3.0}); - - LPclp clp(x,r); - LPclp::lp_result_stat res = clp.solve(true); - CHECK(res[LPclp::NOTEMPTY]); - IntervalVector diff = x*clp.getFeasiblePoint()-r; - CHECK(diff.ub().maxCoeff()<0.0); - CHECK(res[LPclp::BOUNDED_APPROX]); - /* note : polytope is unbounded, but - the emptiness solving did not gave - an infinite direction because of // of some - constraints */ - IntervalRow dual = clp.getBoundedRow().head(x.rows()); - CHECK(dual.lb().maxCoeff()>=0.0); - CHECK((dual*x).mig().maxCoeff()<=1e-10); - - Row obj({-1.0,-1.0,-1.0}); - clp.setObjective(obj); - res = clp.solve(false); - CHECK(res[LPclp::NOTEMPTY]); - diff = x*clp.getFeasiblePoint()-r; - CHECK(diff.ub().maxCoeff()<1e-10); - CHECK(res[LPclp::UNBOUNDED_APPROX]); - /* can't prove that it is unbounded, once again due to // - of some constraints */ - diff = x*clp.getUnboundedVect(); - CHECK(diff.ub().maxCoeff()<1e-10); - } -} - -TEST_CASE("Polytope-CLP-box") -{ - /* non empty, bounded polytope */ - { - Matrix x({ - { 1.5, 1.5, 1.5 }, - { 0.5, 1.5, -0.5 }, - { 1.5, -0.5, 0.5 }, - { -0.5, 0.5, 1.5 } - }); - Vector r({3.0,3.0,-1.0,3.0}); - IntervalVector b { { -5,5 } , {-5,5}, {-5,5 } }; - - LPclp clp(x,r); - clp.set_bbox(b); - LPclp::lp_result_stat res = clp.solve(true); - CHECK(res[LPclp::NOTEMPTY]); - IntervalVector diff = x*clp.getFeasiblePoint()-r; - CHECK(diff.ub().maxCoeff()<0.0); - CHECK(res[LPclp::BOUNDED]); - - Row obj({1.0,-1.0,1.0}); - clp.setObjective(obj); - res = clp.solve(false); - CHECK(res[LPclp::NOTEMPTY]); - diff = x*clp.getFeasiblePoint()-r; - CHECK(diff.ub().maxCoeff()<1e-8); /* can be >0 near the basis */ - CHECK(res[LPclp::BOUNDED]); - } - - /* empty polytope */ - { - Matrix x({ - { 1.5, 1.5, 1.4 }, - { 0.5, 1.4, -0.5 }, - { 1.4, -0.5, 0.5 }, - { -0.5, 0.5, 1.5 } - }); - Vector r({3.0,3.0,-1.0,3.0}); - IntervalVector b { { 3,6 } , {0,6}, {3,6 } }; - - LPclp clp(x,r); - clp.set_bbox(b); - LPclp::lp_result_stat res = clp.solve(true); - CHECK(res[LPclp::EMPTY]); - } - - -}