From fd637479c82ab04763fb9649b3e80269c4321e84 Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Sun, 30 Aug 2026 09:49:23 +0200 Subject: [PATCH 1/5] support reading SDPA files with multiple LP blocks Multiple LP blocks (indicated by negative block sizes) are supported by the SDPA file format but not the reader. This change modifies the reader such that multiple LP blocks are read as if they were a single block: each element "v b i j x" is interpreted as "v b (i + blockoffsets[b]) (j + blockoffsets[b]) x" where "blockoffsets[b]" is the sum of the block sizes of all preceding LP blocks. This effectively reads LP blocks (i.e. diagonal block) as one single diagonal block. --- src/scipsdp/reader_sdpa.c | 103 ++++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 50 deletions(-) diff --git a/src/scipsdp/reader_sdpa.c b/src/scipsdp/reader_sdpa.c index 6e7d4152..32cd0dd9 100644 --- a/src/scipsdp/reader_sdpa.c +++ b/src/scipsdp/reader_sdpa.c @@ -91,7 +91,8 @@ struct SDPA_Data int nconsblocks; /**< number of constraint blocks specified in the input file */ int* sdpmemsize; /**< size of memory allocated for the nonconstant part of each SDP constraint */ int* sdpconstmemsize; /**< size of memory allocated for the constant part of each SDP constraint */ - int idxlinconsblock; /**< the index of the linear constraint block */ + int* blockoffsets; /**< row and columns offsets for merged LP blocks (0 for SDP blocks) */ + int* newblockidx; /**< mapping of file block index to new internal index, skipping LP blocks */ char* buffer; /**< input buffer */ int bufferlen; /**< length of buffer */ }; @@ -483,6 +484,8 @@ SCIP_RETCODE SDPAfreeData( SCIPfreeBlockMemoryArrayNull(scip, &data->createdconss, data->nlinconss); SCIPfreeBlockMemoryArrayNull(scip, &data->sdpblockrank1, data->nsdpblocks); SCIPfreeBlockMemoryArrayNull(scip, &data->sdpblocksizes, data->nsdpblocks); + SCIPfreeBlockMemoryArrayNull(scip, &data->blockoffsets, data->nconsblocks); + SCIPfreeBlockMemoryArrayNull(scip, &data->newblockidx, data->nconsblocks); SCIPfreeBlockMemoryArrayNull(scip, &data->createdvars, data->nvars); SCIPfreeBufferNull(scip, &data); @@ -632,6 +635,9 @@ SCIP_RETCODE SDPAreadBlockSize( int* blocksizes; int nsdpblocks = 0; int nblocks; + int* blockoffsets; + int* newblockidx; + int lpoffset = 0; /* accumulator for the offset of the next lp-block */ int cnt = 0; int b; int c; @@ -643,6 +649,8 @@ SCIP_RETCODE SDPAreadBlockSize( SCIP_CALL( SCIPallocBufferArray(scip, &blocksizes, data->nconsblocks) ); SCIP_CALL( SCIPallocBufferArray(scip, &sdpblocksizes, data->nconsblocks) ); + SCIP_CALL( SCIPallocBufferArray(scip, &blockoffsets, data->nconsblocks) ); + SCIP_CALL( SCIPallocBufferArray(scip, &newblockidx, data->nconsblocks) ); assert( scip != NULL ); assert( file != NULL ); @@ -667,15 +675,10 @@ SCIP_RETCODE SDPAreadBlockSize( /* if the entry is less than zero it describes the LP blocks */ if ( *(blocksizes + i) < 0 ) { - if ( data->idxlinconsblock == -1 ) - data->idxlinconsblock = i; - else - { - SCIPerrorMessage("Only one LP block can be defined in line %" SCIP_LONGINT_FORMAT - " but at least two blocksizes are negative.\n", *linecount); - goto TERMINATE; - } - data->nlinconss = - *(blocksizes + i); + data->nlinconss += - *(blocksizes + i); + newblockidx[i] = -1; + blockoffsets[i] = lpoffset; + lpoffset += - *(blocksizes + i); } else { @@ -685,13 +688,15 @@ SCIP_RETCODE SDPAreadBlockSize( *linecount); goto TERMINATE; } + newblockidx[i] = nsdpblocks; + blockoffsets[i] = 0; *(sdpblocksizes + nsdpblocks) = *(blocksizes + i); ++nsdpblocks; } } - assert( data->idxlinconsblock < 0 || data->nlinconss > 0 ); - assert( data->idxlinconsblock >= 0 || data->nlinconss == 0 ); + assert( lpoffset == 0 || data->nlinconss > 0 ); + assert( lpoffset > 0 || data->nlinconss == 0 ); if ( data->nlinconss < 0 ) { @@ -708,6 +713,8 @@ SCIP_RETCODE SDPAreadBlockSize( SCIP_CALL( SCIPallocBlockMemoryArray(scip, &(data->sdpblocksizes), data->nsdpblocks) ); SCIP_CALL( SCIPallocBlockMemoryArray(scip, &(data->sdpblockrank1), data->nsdpblocks) ); SCIP_CALL( SCIPallocBlockMemoryArray(scip, &(data->createdconss), data->nlinconss) ); + SCIP_CALL( SCIPallocBlockMemoryArray(scip, &(data->blockoffsets), data->nconsblocks) ); + SCIP_CALL( SCIPallocBlockMemoryArray(scip, &(data->newblockidx), data->nconsblocks) ); for (b = 0; b < nsdpblocks; b++) { @@ -719,6 +726,11 @@ SCIP_RETCODE SDPAreadBlockSize( data->sdpblockrank1[b] = FALSE; } + for (b = 0; b < data->nconsblocks; b++) { + data->blockoffsets[b] = *(blockoffsets + b); + data->newblockidx[b] = *(newblockidx + b); + } + /* create corresponding constraints */ for (c = 0; c < data->nlinconss; c++) { @@ -743,12 +755,16 @@ SCIP_RETCODE SDPAreadBlockSize( SCIPfreeBufferArray(scip, &sdpblocksizes); SCIPfreeBufferArray(scip, &blocksizes); + SCIPfreeBufferArray(scip, &blockoffsets); + SCIPfreeBufferArray(scip, &newblockidx); return SCIP_OKAY; TERMINATE: SCIPfreeBufferArray(scip, &sdpblocksizes); SCIPfreeBufferArray(scip, &blocksizes); + SCIPfreeBufferArray(scip, &blockoffsets); + SCIPfreeBufferArray(scip, &newblockidx); SCIP_CALL( SDPAfreeData(scip, file, data) ); return SCIP_READERROR; @@ -865,9 +881,10 @@ SCIP_RETCODE SDPAreadBlocks( int emptysdpblocks = 0; int emptylinconsblocks = 0; int nindcons = 0; - int blockidxoffset = 0; int row; int col; + int file_b; /* block index of file */ + int file_row; /* row index of file */ int b; /* current block */ int v; /* current variable */ int c; /* current linear constraint */ @@ -977,23 +994,18 @@ SCIP_RETCODE SDPAreadBlocks( } /* switch from SDPA counting (starting from 1) to SCIP counting (starting from 0) */ + file_b = b; /* the original block index of error messages */ + file_row = row; --v; --b; --row; --col; - /* reset LP block offset */ - blockidxoffset = 0; - - /* check if this entry belongs to the LP block (FALSE) or to an SDP block (TRUE)*/ - if ( b != data->idxlinconsblock ) + /* check if this entry belongs to an LP block (FALSE) or to an SDP block (TRUE)*/ + if ( data->newblockidx[b] >= 0 ) { /* check if the LP block was already read and adjust the counter as well as the offset for error messages */ - if ( b > data->idxlinconsblock && data->idxlinconsblock >= 0 ) - { - --b; - blockidxoffset = 1; - } + b = data->newblockidx[b]; if ( v < - 1 || v >= data->nvars ) { @@ -1005,7 +1017,7 @@ SCIP_RETCODE SDPAreadBlocks( if ( b < 0 || b >= data->nsdpblocks ) { SCIPerrorMessage("Given coefficient in line %" SCIP_LONGINT_FORMAT " for SDP block %d which does not exist!\n", - *linecount, b + 1 + blockidxoffset); + *linecount, file_b); goto TERMINATE; } assert( 0 <= b && b < data->nsdpblocks ); @@ -1106,7 +1118,7 @@ SCIP_RETCODE SDPAreadBlocks( if ( SCIPisInfinity(scip, val) || SCIPisInfinity(scip, -val) ) { SCIPerrorMessage("Given constant part in line %" SCIP_LONGINT_FORMAT " of block %d is infinity, which is not allowed.\n", - *linecount, b+1); + *linecount, file_b); goto TERMINATE; } @@ -1144,6 +1156,9 @@ SCIP_RETCODE SDPAreadBlocks( } else /* LP block */ { + /* add the row/column offset for this block */ + row += data->blockoffsets[b]; + col += data->blockoffsets[b]; /* indicator variables have a negative variable index */ if ( v >= data->nvars ) { @@ -1165,7 +1180,7 @@ SCIP_RETCODE SDPAreadBlocks( if ( row < 0 || row >= data->nlinconss ) { SCIPerrorMessage("Given linear coefficient in line %" SCIP_LONGINT_FORMAT " for linear constraint %d which does not exist!\n", - *linecount, row + 1); + *linecount, file_row); goto TERMINATE; } @@ -1254,7 +1269,7 @@ SCIP_RETCODE SDPAreadBlocks( if ( SCIPisInfinity(scip, val) || SCIPisInfinity(scip, -val)) { SCIPerrorMessage("Given constant part in line %" SCIP_LONGINT_FORMAT " of block %d is infinity, which is not allowed.\n", - *linecount, b+1); + *linecount, file_b); goto TERMINATE; } @@ -1280,20 +1295,14 @@ SCIP_RETCODE SDPAreadBlocks( SCIP_CALL( readNextLine(scip, file, &data->buffer, &data->bufferlen, linecount, &success) ); } - /* reset LP block offset */ - blockidxoffset = 0; - - for (b = 0; b < data->nsdpblocks; b++) + for (file_b = 1; file_b <= data->nconsblocks; file_b++) { - if ( nentriessdp[b] == 0 ) + b = data->newblockidx[file_b - 1]; + if ( b >= 0 && nentriessdp[b] == 0 ) { emptysdpblocks++; - /* account for a possible LP block */ - if ( data->idxlinconsblock >= 0 && b >= data->idxlinconsblock ) - blockidxoffset = 1; - - SCIPerrorMessage("SDP block number %d does not contain any nonzero entries!\n", b + 1 + blockidxoffset); + SCIPerrorMessage("SDP block number %d does not contain any nonzero entries!\n", file_b); } } @@ -1582,7 +1591,7 @@ SCIP_RETCODE SDPAreadRank1( ) { /*lint --e{818}*/ SCIP_Bool success; - int blockidxoffset = 0; + int file_v; int v; assert( scip != NULL ); @@ -1627,29 +1636,22 @@ SCIP_RETCODE SDPAreadRank1( } /* switch from SDPA counting (starting from 1) to SCIP counting (starting from 0) */ + file_v = v; --v; - /* reset LP block offset */ - blockidxoffset = 0; - - if ( v == data->idxlinconsblock ) + if ( data->newblockidx[v] < 0 ) { SCIPerrorMessage("Given rank1 in line %" SCIP_LONGINT_FORMAT " for the LP block which is not valid.\n", *linecount); goto TERMINATE; } - /* check if the LP block was already read and adjust the counter as well as the offset for error messages */ - if ( data->idxlinconsblock >= 0 && v > data->idxlinconsblock ) - { - v -= 1; - blockidxoffset = 1; - } + v = data->newblockidx[v]; if ( v < 0 || v >= data->nsdpblocks ) { SCIPerrorMessage("Given rank1 in line %" SCIP_LONGINT_FORMAT " for SDP block %d which does not exist!\n", - *linecount, v + 1 + blockidxoffset); + *linecount, file_v); goto TERMINATE; } @@ -1711,7 +1713,8 @@ SCIP_DECL_READERREAD(readerReadSdpa) data->nlinconss = 0; data->nvars = -1; data->nconsblocks = -1; - data->idxlinconsblock = -1; + data->blockoffsets = NULL; + data->newblockidx = NULL; data->bufferlen = 0; data->sdpblockrank1 = NULL; data->createdvars = NULL; From 1e570b27ba3e4fc07fb60184ed5cf832b77c1afd Mon Sep 17 00:00:00 2001 From: Frank Fischer Date: Sun, 30 Aug 2026 14:37:20 +0200 Subject: [PATCH 2/5] add test for SDPA with multiple LP blocks --- CMakeLists.txt | 1 + instances/example_small_multiLP.dat-s | 36 +++++++++++++++++++++++++++ unittests/src/readwrite.c | 6 +++++ 3 files changed, 43 insertions(+) create mode 100644 instances/example_small_multiLP.dat-s diff --git a/CMakeLists.txt b/CMakeLists.txt index 32775a49..9f3d4b81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,6 +184,7 @@ set_tests_properties(applications-${EXECUTABLE_NAME}-build # set(instances example_small.dat-s + example_small_multiLP.dat-s example_small_cbf.cbf example_inf.dat-s example_TT.dat-s.gz diff --git a/instances/example_small_multiLP.dat-s b/instances/example_small_multiLP.dat-s new file mode 100644 index 00000000..f945cf83 --- /dev/null +++ b/instances/example_small_multiLP.dat-s @@ -0,0 +1,36 @@ +3 = number of variables +4 = number of blocks +2 -4 2 -4 = blocksizes (negative sign for LP-block, size of LP-block equals the number of LP-constraints) +* the next line gives the objective values in the order of the variables +1 -2 -1 +* the remaining lines give the nonzeroes of the constraints with variable (0 meaning the constant part) block row column value +1 1 1 1 1 * first variable in block one, row one, column one has coefficient one +2 1 1 2 1 * variable two in block one, row one, column two has coefficient one (note that because we expect the matrix to be symmetric, we don't need to give the entry for row two and column one) +3 1 2 2 1 +1 3 1 2 1 +3 3 1 1 1 +0 3 2 2 -2.1 * the constant part (variable zero) in block two, row two, column two equals -2.1 (which we are substracting from the A_i, so in the combined matrix it will have a positive sign) +1 2 1 1 1 * block three is the LP block, the LP constraints appear as diagonal entries in this block +2 2 1 1 1 +3 2 1 1 1 +0 2 1 1 1 +1 2 2 2 -1 +2 2 2 2 -1 +3 2 2 2 -1 +0 2 2 2 -8 +1 2 3 3 1 +0 2 3 3 -10 +1 2 4 4 -1 +0 2 4 4 -10 +2 4 1 1 1 +0 4 1 1 -10 +2 4 2 2 -1 +0 4 2 2 -10 +3 4 3 3 1 +0 4 3 3 -10 +3 4 4 4 -1 +0 4 4 4 -10 +*INTEGER +*1 +*2 +*3 diff --git a/unittests/src/readwrite.c b/unittests/src/readwrite.c index 1b4c35bd..d17de440 100644 --- a/unittests/src/readwrite.c +++ b/unittests/src/readwrite.c @@ -362,3 +362,9 @@ Test(readwrite, sign) cr_assert_float_eq(obj1, obj2, EPS, "Optimal values differ: %g (SDPA from CBF with L-) != %g (SDPA from CBF with L+)\n", obj1, obj2); } + +/** Test 20 */ +Test(readwrite, test20) +{ + SCIP_CALL_STOP( runTests("../instances", "example_small_multiLP", "dat-s", 1, EPS, 1, TRUE) ); +} From 418eed0aff3ce6b1e2c1d088d5ee89bf75a5bc1e Mon Sep 17 00:00:00 2001 From: Marc Pfetsch Date: Mon, 31 Aug 2026 19:16:09 +0200 Subject: [PATCH 3/5] add tests for blocks outside of range --- src/scipsdp/reader_sdpa.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/scipsdp/reader_sdpa.c b/src/scipsdp/reader_sdpa.c index 4dfd66d9..7d040101 100644 --- a/src/scipsdp/reader_sdpa.c +++ b/src/scipsdp/reader_sdpa.c @@ -1001,7 +1001,15 @@ SCIP_RETCODE SDPAreadBlocks( --row; --col; - /* check if this entry belongs to an LP block (FALSE) or to an SDP block (TRUE)*/ + if ( b < 0 || b >= data->nconsblocks ) + { + SCIPerrorMessage("Given coefficient in line %" SCIP_LONGINT_FORMAT " for block %d which does not exist!\n", + *linecount, file_b); + goto TERMINATE; + } + assert( 0 <= b && b < data->nconsblocks ); + + /* check if this entry belongs to an LP block (FALSE) or to an SDP block (TRUE) */ if ( data->newblockidx[b] >= 0 ) { /* check if the LP block was already read and adjust the counter as well as the offset for error messages */ @@ -1645,6 +1653,12 @@ SCIP_RETCODE SDPAreadRank1( *linecount); goto TERMINATE; } + if ( v >= data->nconsblocks ) + { + SCIPerrorMessage("Given rank1 in line %" SCIP_LONGINT_FORMAT " for block %d which does not exist!\n", + *linecount, file_v); + goto TERMINATE; + } v = data->newblockidx[v]; From 29a67ef6a1a9f7e4414291daa6c8842975e98aa6 Mon Sep 17 00:00:00 2001 From: Marc Pfetsch Date: Mon, 31 Aug 2026 19:19:15 +0200 Subject: [PATCH 4/5] directly allocate/write to blockoffsets/newblockidx --- src/scipsdp/reader_sdpa.c | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/src/scipsdp/reader_sdpa.c b/src/scipsdp/reader_sdpa.c index 7d040101..a44e1c2d 100644 --- a/src/scipsdp/reader_sdpa.c +++ b/src/scipsdp/reader_sdpa.c @@ -635,8 +635,6 @@ SCIP_RETCODE SDPAreadBlockSize( int* blocksizes; int nsdpblocks = 0; int nblocks; - int* blockoffsets; - int* newblockidx; int lpoffset = 0; /* accumulator for the offset of the next lp-block */ int cnt = 0; int b; @@ -649,8 +647,9 @@ SCIP_RETCODE SDPAreadBlockSize( SCIP_CALL( SCIPallocBufferArray(scip, &blocksizes, data->nconsblocks) ); SCIP_CALL( SCIPallocBufferArray(scip, &sdpblocksizes, data->nconsblocks) ); - SCIP_CALL( SCIPallocBufferArray(scip, &blockoffsets, data->nconsblocks) ); - SCIP_CALL( SCIPallocBufferArray(scip, &newblockidx, data->nconsblocks) ); + + SCIP_CALL( SCIPallocBlockMemoryArray(scip, &(data->blockoffsets), data->nconsblocks) ); + SCIP_CALL( SCIPallocBlockMemoryArray(scip, &(data->newblockidx), data->nconsblocks) ); assert( scip != NULL ); assert( file != NULL ); @@ -676,8 +675,8 @@ SCIP_RETCODE SDPAreadBlockSize( if ( *(blocksizes + i) < 0 ) { data->nlinconss += - *(blocksizes + i); - newblockidx[i] = -1; - blockoffsets[i] = lpoffset; + data->newblockidx[i] = -1; + data->blockoffsets[i] = lpoffset; lpoffset += - *(blocksizes + i); } else @@ -688,8 +687,8 @@ SCIP_RETCODE SDPAreadBlockSize( *linecount); goto TERMINATE; } - newblockidx[i] = nsdpblocks; - blockoffsets[i] = 0; + data->newblockidx[i] = nsdpblocks; + data->blockoffsets[i] = 0; *(sdpblocksizes + nsdpblocks) = *(blocksizes + i); ++nsdpblocks; } @@ -713,8 +712,6 @@ SCIP_RETCODE SDPAreadBlockSize( SCIP_CALL( SCIPallocBlockMemoryArray(scip, &(data->sdpblocksizes), data->nsdpblocks) ); SCIP_CALL( SCIPallocBlockMemoryArray(scip, &(data->sdpblockrank1), data->nsdpblocks) ); SCIP_CALL( SCIPallocBlockMemoryArray(scip, &(data->createdconss), data->nlinconss) ); - SCIP_CALL( SCIPallocBlockMemoryArray(scip, &(data->blockoffsets), data->nconsblocks) ); - SCIP_CALL( SCIPallocBlockMemoryArray(scip, &(data->newblockidx), data->nconsblocks) ); for (b = 0; b < nsdpblocks; b++) { @@ -726,11 +723,6 @@ SCIP_RETCODE SDPAreadBlockSize( data->sdpblockrank1[b] = FALSE; } - for (b = 0; b < data->nconsblocks; b++) { - data->blockoffsets[b] = *(blockoffsets + b); - data->newblockidx[b] = *(newblockidx + b); - } - /* create corresponding constraints */ for (c = 0; c < data->nlinconss; c++) { @@ -755,16 +747,12 @@ SCIP_RETCODE SDPAreadBlockSize( SCIPfreeBufferArray(scip, &sdpblocksizes); SCIPfreeBufferArray(scip, &blocksizes); - SCIPfreeBufferArray(scip, &blockoffsets); - SCIPfreeBufferArray(scip, &newblockidx); return SCIP_OKAY; TERMINATE: SCIPfreeBufferArray(scip, &sdpblocksizes); SCIPfreeBufferArray(scip, &blocksizes); - SCIPfreeBufferArray(scip, &blockoffsets); - SCIPfreeBufferArray(scip, &newblockidx); SCIP_CALL( SDPAfreeData(scip, file, data) ); return SCIP_READERROR; From b9fbd034d08b552795e0e2fbd9c427f880da2684 Mon Sep 17 00:00:00 2001 From: Marc Pfetsch Date: Tue, 1 Sep 2026 22:21:44 +0200 Subject: [PATCH 5/5] catch return code to avoid memory leak --- src/scipsdp/reader_sdpa.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/scipsdp/reader_sdpa.c b/src/scipsdp/reader_sdpa.c index a44e1c2d..5fd42a31 100644 --- a/src/scipsdp/reader_sdpa.c +++ b/src/scipsdp/reader_sdpa.c @@ -769,6 +769,7 @@ SCIP_RETCODE SDPAreadObjVals( SDPA_DATA* data /**< data pointer to save the results in */ ) { /*lint --e{818}*/ + SCIP_RETCODE retcode; SCIP_Real* objvals; int v; int nreadvals; @@ -789,9 +790,8 @@ SCIP_RETCODE SDPAreadObjVals( } assert( data->nvars >= 0 ); - SCIP_CALL( readLineDoubles(scip, file, &data->buffer, &data->bufferlen, linecount, data->nvars, objvals, &nreadvals) ); - - if ( nreadvals == -1 ) + retcode = readLineDoubles(scip, file, &data->buffer, &data->bufferlen, linecount, data->nvars, objvals, &nreadvals); + if ( retcode == SCIP_READERROR || nreadvals == -1 ) goto TERMINATE; else if ( nreadvals != data->nvars ) {