OMCompiler/SimulationRuntime/c/simulation/solver/linearSolverKlu.c
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file belongs to the OpenModelica Run-Time System | ||
| 3 | * | ||
| 4 | * Copyright (c) 1998-2026, Open Source Modelica Consortium (OSMC), c/o Linköpings | ||
| 5 | * universitet, Department of Computer and Information Science, SE-58183 Linköping, Sweden. All rights | ||
| 6 | * reserved. | ||
| 7 | * | ||
| 8 | * THIS PROGRAM IS PROVIDED UNDER THE TERMS OF THE BSD NEW LICENSE OR THE | ||
| 9 | * AGPL VERSION 3 LICENSE OR THE OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.8. ANY | ||
| 10 | * USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES RECIPIENT'S | ||
| 11 | * ACCEPTANCE OF THE BSD NEW LICENSE OR THE OSMC PUBLIC LICENSE OR THE AGPL | ||
| 12 | * VERSION 3, ACCORDING TO RECIPIENTS CHOICE. | ||
| 13 | * | ||
| 14 | * The OpenModelica software and the OSMC (Open Source Modelica Consortium) Public License | ||
| 15 | * (OSMC-PL) are obtained from OSMC, either from the above address, from the URLs: | ||
| 16 | * http://www.openmodelica.org or https://github.com/OpenModelica/ or | ||
| 17 | * http://www.ida.liu.se/projects/OpenModelica, and in the OpenModelica distribution. GNU | ||
| 18 | * AGPL version 3 is obtained from: https://www.gnu.org/licenses/licenses.html#GPL. The BSD NEW | ||
| 19 | * License is obtained from: http://www.opensource.org/licenses/BSD-3-Clause. | ||
| 20 | * | ||
| 21 | * This program is distributed WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY | ||
| 23 | * SET FORTH IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF | ||
| 24 | * OSMC-PL. | ||
| 25 | * | ||
| 26 | */ | ||
| 27 | |||
| 28 | /*! \file linearSolverKlu.c | ||
| 29 | */ | ||
| 30 | |||
| 31 | #include "omc_config.h" | ||
| 32 | |||
| 33 | #ifdef WITH_SUITESPARSE | ||
| 34 | #include <math.h> | ||
| 35 | #include <stdlib.h> | ||
| 36 | #include <string.h> | ||
| 37 | |||
| 38 | #include "simulation_data.h" | ||
| 39 | #include "simulation/simulation_info_json.h" | ||
| 40 | #include "util/omc_error.h" | ||
| 41 | #include "omc_math.h" | ||
| 42 | #include "util/varinfo.h" | ||
| 43 | #include "model_help.h" | ||
| 44 | |||
| 45 | #include "linearSystem.h" | ||
| 46 | #include "linearSolverKlu.h" | ||
| 47 | |||
| 48 | static void printMatrixCSC(int* Ap, int* Ai, double* Ax, int n); | ||
| 49 | static void printMatrixCSR(int* Ap, int* Ai, double* Ax, int n); | ||
| 50 | |||
| 51 | /*! \fn allocate memory for linear system solver Klu | ||
| 52 | * | ||
| 53 | */ | ||
| 54 | ✗ | int allocateKluData(int n_row, int n_col, int nz, void** voiddata) | |
| 55 | { | ||
| 56 | ✗ | DATA_KLU* data = (DATA_KLU*) malloc(sizeof(DATA_KLU)); | |
| 57 | ✗ | assertStreamPrint(NULL, 0 != data, "Could not allocate data for linear solver Klu."); | |
| 58 | |||
| 59 | ✗ | data->symbolic = NULL; | |
| 60 | ✗ | data->numeric = NULL; | |
| 61 | |||
| 62 | ✗ | data->n_col = n_col; | |
| 63 | ✗ | data->n_row = n_row; | |
| 64 | ✗ | data->nnz = nz; | |
| 65 | |||
| 66 | ✗ | data->Ap = (int*) calloc((n_row+1),sizeof(int)); | |
| 67 | ✗ | data->Ai = (int*) calloc(nz,sizeof(int)); | |
| 68 | ✗ | data->Ax = (double*) calloc(nz,sizeof(double)); | |
| 69 | ✗ | data->work = (double*) calloc(n_col,sizeof(double)); | |
| 70 | |||
| 71 | ✗ | data->numberSolving = 0; | |
| 72 | ✗ | klu_defaults(&(data->common)); | |
| 73 | |||
| 74 | ✗ | *voiddata = (void*)data; | |
| 75 | |||
| 76 | ✗ | return 0; | |
| 77 | } | ||
| 78 | |||
| 79 | |||
| 80 | /*! \fn free memory for linear system solver Klu | ||
| 81 | * | ||
| 82 | */ | ||
| 83 | ✗ | int freeKluData(void **voiddata) | |
| 84 | { | ||
| 85 | ✗ | DATA_KLU* data = (DATA_KLU*) *voiddata; | |
| 86 | |||
| 87 | ✗ | free(data->Ap); | |
| 88 | ✗ | free(data->Ai); | |
| 89 | ✗ | free(data->Ax); | |
| 90 | ✗ | free(data->work); | |
| 91 | |||
| 92 | |||
| 93 | ✗ | if(data->symbolic) | |
| 94 | ✗ | klu_free_symbolic(&data->symbolic, &data->common); | |
| 95 | ✗ | if(data->numeric) | |
| 96 | ✗ | klu_free_numeric(&data->numeric, &data->common); | |
| 97 | |||
| 98 | ✗ | return 0; | |
| 99 | } | ||
| 100 | |||
| 101 | /*! \fn getAnalyticalJacobian | ||
| 102 | * | ||
| 103 | * function calculates analytical jacobian | ||
| 104 | * | ||
| 105 | * \param [ref] [data] | ||
| 106 | * \param [in] [sysNumber] | ||
| 107 | * | ||
| 108 | * \author wbraun | ||
| 109 | * | ||
| 110 | */ | ||
| 111 | ✗ | static void getAnalyticalJacobian(DATA* data, threadData_t *threadData, | |
| 112 | LINEAR_SYSTEM_DATA* systemData) | ||
| 113 | { | ||
| 114 | int i,j,l,nth; | ||
| 115 | ✗ | JACOBIAN* jacobian = systemData->jacobian; | |
| 116 | ✗ | JACOBIAN* parentJacobian = systemData->parentJacobian; | |
| 117 | ✗ | const SPARSE_PATTERN* sp = jacobian->sparsePattern; | |
| 118 | |||
| 119 | /* evaluate constant equations of Jacobian */ | ||
| 120 | ✗ | if (jacobian->constantEqns != NULL) { | |
| 121 | ✗ | jacobian->constantEqns(data, threadData, jacobian, parentJacobian); | |
| 122 | } | ||
| 123 | |||
| 124 | /* evaluate Jacobian */ | ||
| 125 | ✗ | for (i = 0; i < sp->maxColors; i++) { | |
| 126 | /* activate seed variable for the corresponding color */ | ||
| 127 | ✗ | for (j = 0; j < jacobian->sizeCols; j++) | |
| 128 | ✗ | if (sp->colorCols[j]-1 == i) | |
| 129 | ✗ | jacobian->seedVars[j] = 1.0; | |
| 130 | |||
| 131 | /* Evaluate Jacobian column */ | ||
| 132 | ✗ | jacobian->evalColumn(data, threadData, jacobian, parentJacobian); | |
| 133 | |||
| 134 | ✗ | for (j = 0; j < jacobian->sizeCols; j++) { | |
| 135 | ✗ | if (sp->colorCols[j]-1 == i) { | |
| 136 | ✗ | for (nth = sp->leadindex[j]; nth < sp->leadindex[j+1]; nth++) { | |
| 137 | ✗ | l = sp->index[nth]; | |
| 138 | ✗ | systemData->setAElement(j, l, -jacobian->resultVars[l], nth, systemData, threadData); | |
| 139 | } | ||
| 140 | /* de-activate seed variable for the corresponding color */ | ||
| 141 | ✗ | jacobian->seedVars[j] = 0.0; | |
| 142 | } | ||
| 143 | } | ||
| 144 | } | ||
| 145 | ✗ | } | |
| 146 | |||
| 147 | /*! \fn residual_wrapper for the residual function | ||
| 148 | * | ||
| 149 | */ | ||
| 150 | static int residual_wrapper(double* x, double* f, RESIDUAL_USERDATA* userData, int sysNumber) | ||
| 151 | { | ||
| 152 | ✗ | int iflag = 0; | |
| 153 | ✗ | userData->data->simulationInfo->linearSystemData[sysNumber].residualFunc(userData, x, f, &iflag); | |
| 154 | ✗ | return 0; | |
| 155 | } | ||
| 156 | |||
| 157 | /*! \fn solve linear system with Klu method | ||
| 158 | * | ||
| 159 | * \param [in] [data] | ||
| 160 | * [sysNumber] index of the corresponding linear system | ||
| 161 | * | ||
| 162 | * | ||
| 163 | * author: wbraun | ||
| 164 | */ | ||
| 165 | ✗ | int solveKlu(DATA *data, threadData_t *threadData, int sysNumber, double* aux_x) | |
| 166 | { | ||
| 167 | ✗ | RESIDUAL_USERDATA resUserData = {.data=data, .threadData=threadData, .solverData=NULL}; | |
| 168 | ✗ | LINEAR_SYSTEM_DATA* systemData = &(data->simulationInfo->linearSystemData[sysNumber]); | |
| 169 | ✗ | DATA_KLU* solverData = (DATA_KLU*)systemData->solverData[0]; | |
| 170 | _omc_scalar residualNorm = 0; | ||
| 171 | |||
| 172 | ✗ | int i, j, status = 0, success = 0, n = systemData->size, eqSystemNumber = systemData->equationIndex, indexes[2] = {1,eqSystemNumber}; | |
| 173 | double tmpJacEvalTime; | ||
| 174 | ✗ | int reuseMatrixJac = (data->simulationInfo->currentContext == CONTEXT_SYM_JACOBIAN && data->simulationInfo->currentJacobianEval > 0); | |
| 175 | |||
| 176 | ✗ | infoStreamPrintWithEquationIndexes(OMC_LOG_LS, omc_dummyFileInfo, 0, indexes, | |
| 177 | "Start solving Linear System %d (size %d) at time %g with Klu Solver", | ||
| 178 | ✗ | eqSystemNumber, (int) systemData->size, data->localData[0]->timeValue); | |
| 179 | |||
| 180 | ✗ | rt_ext_tp_tick(&(solverData->timeClock)); | |
| 181 | ✗ | if (0 == systemData->method) | |
| 182 | { | ||
| 183 | ✗ | if (!reuseMatrixJac){ | |
| 184 | /* set A matrix */ | ||
| 185 | ✗ | solverData->Ap[0] = 0; | |
| 186 | ✗ | systemData->setA(data, threadData, systemData); | |
| 187 | ✗ | solverData->Ap[solverData->n_row] = solverData->nnz; | |
| 188 | } | ||
| 189 | |||
| 190 | /* set b vector */ | ||
| 191 | ✗ | systemData->setb(data, threadData, systemData); | |
| 192 | } else { | ||
| 193 | |||
| 194 | ✗ | if (!reuseMatrixJac){ | |
| 195 | ✗ | solverData->Ap[0] = 0; | |
| 196 | /* calculate jacobian -> matrix A*/ | ||
| 197 | ✗ | if(systemData->jacobianIndex != -1){ | |
| 198 | ✗ | getAnalyticalJacobian(data, threadData, systemData); | |
| 199 | } else { | ||
| 200 | assertStreamPrint(threadData, 1, "jacobian function pointer is invalid" ); | ||
| 201 | } | ||
| 202 | ✗ | solverData->Ap[solverData->n_row] = solverData->nnz; | |
| 203 | } | ||
| 204 | |||
| 205 | /* calculate vector b (rhs) */ | ||
| 206 | ✗ | memcpy(solverData->work, aux_x, sizeof(double)*solverData->n_row); | |
| 207 | |||
| 208 | ✗ | residual_wrapper(solverData->work, systemData->b, &resUserData, sysNumber); | |
| 209 | } | ||
| 210 | ✗ | tmpJacEvalTime = rt_ext_tp_tock(&(solverData->timeClock)); | |
| 211 | ✗ | systemData->jacobianTime += tmpJacEvalTime; | |
| 212 | ✗ | infoStreamPrint(OMC_LOG_LS_V, 0, "### %f time to set Matrix A and vector b.", tmpJacEvalTime); | |
| 213 | |||
| 214 | ✗ | if (OMC_ACTIVE_STREAM(OMC_LOG_LS_V)) | |
| 215 | { | ||
| 216 | ✗ | infoStreamPrint(OMC_LOG_LS_V, 1, "Old solution x:"); | |
| 217 | ✗ | for(i = 0; i < solverData->n_row; ++i) | |
| 218 | ✗ | infoStreamPrint(OMC_LOG_LS_V, 0, "[%d] %s = %g", i+1, modelInfoGetEquation(&data->modelData->modelDataXml,eqSystemNumber).vars[i], aux_x[i]); | |
| 219 | ✗ | messageClose(OMC_LOG_LS_V); | |
| 220 | |||
| 221 | ✗ | infoStreamPrint(OMC_LOG_LS_V, 1, "Matrix A n_rows = %d", solverData->n_row); | |
| 222 | ✗ | for (i=0; i<solverData->n_row; i++){ | |
| 223 | ✗ | infoStreamPrint(OMC_LOG_LS_V, 0, "%d. Ap => %d -> %d", i, solverData->Ap[i], solverData->Ap[i+1]); | |
| 224 | ✗ | for (j=solverData->Ap[i]; j<solverData->Ap[i+1]; j++){ | |
| 225 | ✗ | infoStreamPrint(OMC_LOG_LS_V, 0, "A[%d,%d] = %f", i, solverData->Ai[j], solverData->Ax[j]); | |
| 226 | } | ||
| 227 | } | ||
| 228 | ✗ | messageClose(OMC_LOG_LS_V); | |
| 229 | |||
| 230 | ✗ | for (i=0; i<solverData->n_row; i++) | |
| 231 | { | ||
| 232 | ✗ | infoStreamPrint(OMC_LOG_LS_V, 0, "b[%d] = %e", i, systemData->b[i]); | |
| 233 | } | ||
| 234 | } | ||
| 235 | ✗ | rt_ext_tp_tick(&(solverData->timeClock)); | |
| 236 | |||
| 237 | /* symbolic pre-ordering of A to reduce fill-in of L and U */ | ||
| 238 | ✗ | if (0 == solverData->numberSolving) | |
| 239 | { | ||
| 240 | ✗ | infoStreamPrint(OMC_LOG_LS_V, 0, "Perform analyze settings:\n - ordering used: %d\n - current status: %d", solverData->common.ordering, solverData->common.status); | |
| 241 | ✗ | solverData->symbolic = klu_analyze(solverData->n_col, solverData->Ap, solverData->Ai, &solverData->common); | |
| 242 | } | ||
| 243 | |||
| 244 | /* if reuseMatrixJac use also previous factorization */ | ||
| 245 | ✗ | if (!reuseMatrixJac) | |
| 246 | { | ||
| 247 | /* compute the LU factorization of A */ | ||
| 248 | ✗ | if (0 == solverData->common.status){ | |
| 249 | ✗ | if(solverData->numeric){ | |
| 250 | /* Just refactor using the same pivots, but check that the refactor is still accurate */ | ||
| 251 | ✗ | klu_refactor(solverData->Ap, solverData->Ai, solverData->Ax, solverData->symbolic, solverData->numeric, &solverData->common); | |
| 252 | ✗ | klu_rgrowth(solverData->Ap, solverData->Ai, solverData->Ax, solverData->symbolic, solverData->numeric, &solverData->common); | |
| 253 | ✗ | infoStreamPrint(OMC_LOG_LS_V, 0, "Klu rgrowth after refactor: %f", solverData->common.rgrowth); | |
| 254 | /* If rgrowth is small then do a whole factorization with new pivots (What should this tolerance be?) */ | ||
| 255 | ✗ | if (solverData->common.rgrowth < 1e-3){ | |
| 256 | ✗ | klu_free_numeric(&solverData->numeric, &solverData->common); | |
| 257 | ✗ | solverData->numeric = klu_factor(solverData->Ap, solverData->Ai, solverData->Ax, solverData->symbolic, &solverData->common); | |
| 258 | ✗ | infoStreamPrint(OMC_LOG_LS_V, 0, "Klu new factorization performed."); | |
| 259 | } | ||
| 260 | } else { | ||
| 261 | ✗ | solverData->numeric = klu_factor(solverData->Ap, solverData->Ai, solverData->Ax, solverData->symbolic, &solverData->common); | |
| 262 | } | ||
| 263 | } | ||
| 264 | } | ||
| 265 | |||
| 266 | ✗ | if (0 == solverData->common.status){ | |
| 267 | ✗ | if (1 == systemData->method){ | |
| 268 | ✗ | if (klu_solve(solverData->symbolic, solverData->numeric, solverData->n_col, 1, systemData->b, &solverData->common)){ | |
| 269 | success = 1; | ||
| 270 | } | ||
| 271 | } else { | ||
| 272 | ✗ | if (klu_tsolve(solverData->symbolic, solverData->numeric, solverData->n_col, 1, systemData->b, &solverData->common)){ | |
| 273 | success = 1; | ||
| 274 | } | ||
| 275 | } | ||
| 276 | } | ||
| 277 | |||
| 278 | ✗ | infoStreamPrint(OMC_LOG_LS_V, 0, "Solve System: %f", rt_ext_tp_tock(&(solverData->timeClock))); | |
| 279 | |||
| 280 | /* print solution */ | ||
| 281 | ✗ | if (1 == success){ | |
| 282 | |||
| 283 | ✗ | if (1 == systemData->method){ | |
| 284 | /* take the solution */ | ||
| 285 | ✗ | for(i = 0; i < solverData->n_row; ++i) | |
| 286 | ✗ | aux_x[i] += systemData->b[i]; | |
| 287 | |||
| 288 | /* update inner equations */ | ||
| 289 | ✗ | residual_wrapper(aux_x, solverData->work, &resUserData, sysNumber); | |
| 290 | ✗ | residualNorm = _omc_gen_euclideanVectorNorm(solverData->work, solverData->n_row); | |
| 291 | |||
| 292 | ✗ | if ((isnan(residualNorm)) || (residualNorm>1e-4)) { | |
| 293 | ✗ | warningStreamPrintWithLimit(OMC_LOG_LS, 0, ++(systemData->numberOfFailures) /* Update counter */, data->simulationInfo->maxWarnDisplays, | |
| 294 | "Failed to solve linear system of equations (no. %d) at time %f. Residual norm is %.15g.", | ||
| 295 | ✗ | (int)systemData->equationIndex, data->localData[0]->timeValue, residualNorm); | |
| 296 | success = 0; | ||
| 297 | } | ||
| 298 | } else { | ||
| 299 | /* the solution is automatically in x */ | ||
| 300 | ✗ | memcpy(aux_x, systemData->b, sizeof(double)*systemData->size); | |
| 301 | } | ||
| 302 | |||
| 303 | ✗ | if (OMC_ACTIVE_STREAM(OMC_LOG_LS_V)) | |
| 304 | { | ||
| 305 | ✗ | if (1 == systemData->method) { | |
| 306 | ✗ | infoStreamPrint(OMC_LOG_LS_V, 1, "Residual Norm %.15g of solution x:", residualNorm); | |
| 307 | } else { | ||
| 308 | ✗ | infoStreamPrint(OMC_LOG_LS_V, 1, "Solution x:"); | |
| 309 | } | ||
| 310 | ✗ | infoStreamPrint(OMC_LOG_LS_V, 0, "System %d numVars %d.", eqSystemNumber, modelInfoGetEquation(&data->modelData->modelDataXml,eqSystemNumber).numVar); | |
| 311 | |||
| 312 | ✗ | for(i = 0; i < systemData->size; ++i) | |
| 313 | ✗ | infoStreamPrint(OMC_LOG_LS_V, 0, "[%d] %s = %g", i+1, modelInfoGetEquation(&data->modelData->modelDataXml,eqSystemNumber).vars[i], aux_x[i]); | |
| 314 | |||
| 315 | ✗ | messageClose(OMC_LOG_LS_V); | |
| 316 | } | ||
| 317 | } | ||
| 318 | else | ||
| 319 | { | ||
| 320 | ✗ | warningStreamPrintWithLimit(OMC_LOG_STDOUT, 0, ++(systemData->numberOfFailures) /* Update counter */, data->simulationInfo->maxWarnDisplays, | |
| 321 | "Failed to solve linear system of equations (no. %d) at time %f, system status %d.", | ||
| 322 | ✗ | (int)systemData->equationIndex, data->localData[0]->timeValue, status); | |
| 323 | } | ||
| 324 | ✗ | solverData->numberSolving += 1; | |
| 325 | |||
| 326 | ✗ | return success; | |
| 327 | } | ||
| 328 | |||
| 329 | static | ||
| 330 | void printMatrixCSC(int* Ap, int* Ai, double* Ax, int n) | ||
| 331 | { | ||
| 332 | int i, j, k, l; | ||
| 333 | |||
| 334 | char **buffer = (char**)malloc(sizeof(char*)*n); | ||
| 335 | for (l=0; l<n; l++) | ||
| 336 | { | ||
| 337 | buffer[l] = (char*)malloc(sizeof(char)*n*20); | ||
| 338 | buffer[l][0] = 0; | ||
| 339 | } | ||
| 340 | |||
| 341 | char **p = (char**)malloc(sizeof(char*)*n); | ||
| 342 | for (l=0; l<n; l++) | ||
| 343 | p[l] = buffer[l]; | ||
| 344 | |||
| 345 | k = 0; | ||
| 346 | for (i = 0; i < n; i++) | ||
| 347 | { | ||
| 348 | for (j = 0; j < n; j++) | ||
| 349 | { | ||
| 350 | if ((k < Ap[i + 1]) && (Ai[k] == j)) | ||
| 351 | { | ||
| 352 | p[j] += sprintf(p[j], " %5g ", Ax[k]); | ||
| 353 | k++; | ||
| 354 | } | ||
| 355 | else | ||
| 356 | { | ||
| 357 | p[j] += sprintf(p[j], " %5g ", 0.0); | ||
| 358 | } | ||
| 359 | } | ||
| 360 | } | ||
| 361 | for (l = 0; l < n; l++) | ||
| 362 | { | ||
| 363 | infoStreamPrint(OMC_LOG_LS_V, 0, "%s", buffer[l]); | ||
| 364 | free(buffer[l]); | ||
| 365 | } | ||
| 366 | free(p); | ||
| 367 | free(buffer); | ||
| 368 | } | ||
| 369 | |||
| 370 | static | ||
| 371 | void printMatrixCSR(int* Ap, int* Ai, double* Ax, int n) | ||
| 372 | { | ||
| 373 | int i, j, k; | ||
| 374 | char *buffer = (char*)malloc(sizeof(char)*n*15); | ||
| 375 | char *q; | ||
| 376 | k = 0; | ||
| 377 | for (i = 0; i < n; i++) | ||
| 378 | { | ||
| 379 | q = buffer; | ||
| 380 | for (j = 0; j < n; j++) | ||
| 381 | { | ||
| 382 | if ((k < Ap[i + 1]) && (Ai[k] == j)) | ||
| 383 | { | ||
| 384 | q += sprintf(q, " %5.2g ", Ax[k]); | ||
| 385 | k++; | ||
| 386 | } | ||
| 387 | else | ||
| 388 | { | ||
| 389 | q += sprintf(q, " %5.2g ", 0.0); | ||
| 390 | } | ||
| 391 | } | ||
| 392 | infoStreamPrint(OMC_LOG_LS_V, 0, "%s", buffer); | ||
| 393 | } | ||
| 394 | free(buffer); | ||
| 395 | } | ||
| 396 | |||
| 397 | #endif | ||
| 398 |