/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*                                                                           */
/*               This file is part of the program and library                */
/*    PaPILO --- Parallel Presolve for Integer and Linear Optimization       */
/*                                                                           */
/* Copyright (C) 2020-2025 Zuse Institute Berlin (ZIB)                       */
/*                                                                           */
/* Licensed under the Apache License, Version 2.0 (the "License");           */
/* you may not use this file except in compliance with the License.          */
/* You may obtain a copy of the License at                                   */
/*                                                                           */
/*     http://www.apache.org/licenses/LICENSE-2.0                            */
/*                                                                           */
/* Unless required by applicable law or agreed to in writing, software       */
/* distributed under the License is distributed on an "AS IS" BASIS,         */
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  */
/* See the License for the specific language governing permissions and       */
/* limitations under the License.                                            */
/*                                                                           */
/* You should have received a copy of the Apache-2.0 license                 */
/* along with PaPILO; see the file LICENSE. If not visit scipopt.org.        */
/*                                                                           */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#ifndef _PAPILO_PRESOLVERS_SIMPLE_PROBING_HPP_
#define _PAPILO_PRESOLVERS_SIMPLE_PROBING_HPP_

#include "papilo/Config.hpp"
#include "papilo/core/PresolveMethod.hpp"
#include "papilo/core/Problem.hpp"
#include "papilo/core/ProblemUpdate.hpp"
#include "papilo/io/Message.hpp"

namespace papilo
{

template <typename REAL>
class SimpleProbing : public PresolveMethod<REAL>
{
 public:
   SimpleProbing() : PresolveMethod<REAL>()
   {
      this->setName( "simpleprobing" );
      this->setType( PresolverType::kIntegralCols );
      this->setTiming( PresolverTiming::kMedium );
   }

   PresolveStatus
   execute( const Problem<REAL>& problem,
            const ProblemUpdate<REAL>& problemUpdate,
            const Num<REAL>& num, Reductions<REAL>& reductions,
            const Timer& timer, int& reason_of_infeasibility) override;

   void
   calculateReductionsForSimpleProbing( const Num<REAL>& num, Reductions<REAL>& reductions,
       const VariableDomains<REAL>& domains, const REAL* rowvals, const int* rowcols, int rowlen, int bincol,
       REAL binary_coeff );

   PresolveStatus
   perform_simple_probing_step(
       const Num<REAL>& num, Reductions<REAL>& reductions,
       const VariableDomains<REAL>& domains, const Vec<ColFlags>& cflags,
       const Vec<RowActivity<REAL>>& activities,
       const ConstraintMatrix<REAL>& constMatrix, const Vec<REAL>& rhs_values,
       const Vec<int>& rowsize, const Vec<RowFlags>& rflags, int i );
};

#ifdef PAPILO_USE_EXTERN_TEMPLATES
extern template class SimpleProbing<double>;
extern template class SimpleProbing<Quad>;
extern template class SimpleProbing<Rational>;
#endif

template <typename REAL>
PresolveStatus
SimpleProbing<REAL>::execute( const Problem<REAL>& problem,
                              const ProblemUpdate<REAL>& problemUpdate,
                              const Num<REAL>& num, Reductions<REAL>& reductions,
                              const Timer& timer, int& reason_of_infeasibility){
   assert( problem.getNumIntegralCols() != 0 );

   PresolveStatus status = PresolveStatus::kUnchanged;
   const auto& domains = problem.getVariableDomains();
   const auto& cflags = domains.flags;
   const auto& activities = problem.getRowActivities();

   const auto& rowsize = problem.getRowSizes();

   const auto& constMatrix = problem.getConstraintMatrix();
   const auto& rhs_values = constMatrix.getRightHandSides();
   const auto& rflags = constMatrix.getRowFlags();

   int nrows = problem.getNRows();

#ifndef PAPILO_TBB
   assert( problemUpdate.getPresolveOptions().runs_sequential() );
#endif

   if( problemUpdate.getPresolveOptions().runs_sequential() ||
       !problemUpdate.getPresolveOptions().simple_probing_parallel )
   {
      for( int i = 0; i < nrows; ++i )
      {
         if( reductions.size() >= problemUpdate.getPresolveOptions().max_reduction_seq )
            break;
         if( perform_simple_probing_step(
                 num, reductions, domains, cflags, activities, constMatrix,
                 rhs_values, rowsize, rflags, i ) == PresolveStatus::kReduced )
            status = PresolveStatus::kReduced;
      }
   }
#ifdef PAPILO_TBB
   else
   {
      Vec<Reductions<REAL>> stored_reductions( nrows );
      tbb::parallel_for( tbb::blocked_range<int>( 0, nrows ),
                         [&]( const tbb::blocked_range<int>& r ) {
                            for( int j = r.begin(); j < r.end(); ++j )
                            {
                               PresolveStatus s = perform_simple_probing_step(
                                   num, stored_reductions[j], domains, cflags,
                                   activities, constMatrix, rhs_values, rowsize,
                                   rflags, j );
                               assert( s == PresolveStatus::kUnchanged ||
                                       s == PresolveStatus::kReduced );
                               if( s == PresolveStatus::kReduced )
                                  status = s;
                            }
                         } );

      if( status == PresolveStatus::kUnchanged )
         return PresolveStatus::kUnchanged;

      for( int i = 0; i < (int) stored_reductions.size(); ++i )
      {
         Reductions<REAL> reds = stored_reductions[i];
         if( reds.size() > 0 )
         {
            for( const auto& transaction : reds.getTransactions() )
            {
               int start = transaction.start;
               int end = transaction.end;
               TransactionGuard<REAL> guard{ reductions };
               for( int c = start; c < end; c++ )
               {
                  Reduction<REAL>& reduction = reds.getReduction( c );
                  reductions.add_reduction( reduction.row,
                                            reduction.col, reduction.newval );
               }
            }
         }
      }
   }

#endif
   return status;
}

template <typename REAL>
PresolveStatus
SimpleProbing<REAL>::perform_simple_probing_step(
    const Num<REAL>& num, Reductions<REAL>& reductions,
    const VariableDomains<REAL>& domains, const Vec<ColFlags>& cflags,
    const Vec<RowActivity<REAL>>& activities,
    const ConstraintMatrix<REAL>& constMatrix, const Vec<REAL>& rhs_values,
    const Vec<int>& rowsize, const Vec<RowFlags>& rflags, int i )
{
   PresolveStatus status = PresolveStatus::kUnchanged;
   if( !rflags[i].test( RowFlag::kEquation ) || rowsize[i] <= 2 ||
       activities[i].ninfmin != 0 || activities[i].ninfmax != 0 ||
       !num.isEq( activities[i].min + activities[i].max, 2 * rhs_values[i] ) )
      return PresolveStatus::kUnchanged;

   assert( rflags[i].test( RowFlag::kEquation ) );
   assert( activities[i].ninfmin == 0 && activities[i].ninfmax == 0 );
   assert(
       num.isEq( activities[i].min + activities[i].max, 2 * rhs_values[i] ) );

   auto rowvec = constMatrix.getRowCoefficients( i );
   const REAL* rowvals = rowvec.getValues();
   const int* rowcols = rowvec.getIndices();
   const int rowlen = rowvec.getLength();

   REAL bincoef = activities[i].max - rhs_values[i];

   for( int k = 0; k != rowlen; ++k )
   {
      int col = rowcols[k];
      assert( !cflags[col].test( ColFlag::kUnbounded ) );
      if( !cflags[col].test( ColFlag::kIntegral ) ||
          domains.lower_bounds[col] != 0 || domains.upper_bounds[col] != 1 ||
          !num.isEq( abs( rowvals[k] ), bincoef ) )
         continue;

      assert( num.isEq( abs( bincoef ), activities[i].max - rhs_values[i] ) );
      assert( domains.lower_bounds[col] == 0 );
      assert( domains.upper_bounds[col] == 1 );
      assert( cflags[col].test( ColFlag::kIntegral ) );

      calculateReductionsForSimpleProbing( num, reductions, domains, rowvals, rowcols, rowlen, col, rowvals[k] );
      status = PresolveStatus::kReduced;
   }
   return status;
}
template <typename REAL>
void
SimpleProbing<REAL>::calculateReductionsForSimpleProbing( const Num<REAL>& num, Reductions<REAL>& reductions,
    const VariableDomains<REAL>& domains, const REAL* rowvals, const int* rowcols, int rowlen, int bincol,
    REAL binary_coeff )
{
   for( int k = 0; k != rowlen; ++k )
   {
      int col = rowcols[k];

      // for stability only replace column with sufficient range
      if( col == bincol || num.isFeasEq(domains.lower_bounds[col], domains.upper_bounds[col]) )
         continue;

      REAL factor;
      REAL offset;
      if( ( rowvals[k] > 0 && binary_coeff > 0 ) ||
          ( rowvals[k] < 0 && binary_coeff < 0 ) )
      {
         factor = domains.lower_bounds[col] - domains.upper_bounds[col];
         offset = domains.upper_bounds[col];
      }
      else
      {
         factor = domains.upper_bounds[col] - domains.lower_bounds[col];
         offset = domains.lower_bounds[col];
      }

      reductions.replaceCol( col, bincol, factor, offset );
   }
}

} // namespace papilo

#endif
