From 223882bcd6fa70ee1ae93064670e6ffe011d413d Mon Sep 17 00:00:00 2001
From: Phillip Berndt <phillip.berndt@googlemail.com>
Date: Wed, 29 Jul 2015 17:44:52 +0200
Subject: [PATCH] Redirect all error output into the Python exception

---
 lib/radau.f | 70 ++++++++++++++++++++++++++++++-----------------------
 pyradau13.c | 17 ++++++++++---
 test.py     |  5 ++++
 3 files changed, 58 insertions(+), 34 deletions(-)

diff --git a/lib/radau.f b/lib/radau.f
index f2203e0..17e2745 100644
--- a/lib/radau.f
+++ b/lib/radau.f
@@ -3,7 +3,8 @@
      &                  JAC ,IJAC,MLJAC,MUJAC,
      &                  MAS ,IMAS,MLMAS,MUMAS,
      &                  SOLOUT,IOUT,
-     &                  WORK,LWORK,IWORK,LIWORK,RPAR,IPAR,IDID)
+     &                  WORK,LWORK,IWORK,LIWORK,RPAR,IPAR,IDID,
+     &                  ERRBUF)
 C ----------------------------------------------------------
 C     NUMERICAL SOLUTION OF A STIFF (OR DIFFERENTIAL ALGEBRAIC)
 C     SYSTEM OF FIRST 0RDER ORDINARY DIFFERENTIAL EQUATIONS
@@ -394,6 +395,8 @@ C *** *** *** *** *** *** *** *** *** *** *** *** ***
       DIMENSION RPAR(*),IPAR(*)
       LOGICAL IMPLCT,JBAND,ARRET,STARTN,PRED
       EXTERNAL FCN,JAC,MAS,SOLOUT
+C -- pberndt XXX Added ERRBUF parameter to store WRITE output
+      CHARACTER(1024) ERRBUF
 C *** *** *** *** *** *** ***
 C        SETTING THE PARAMETERS 
 C *** *** *** *** *** *** ***
@@ -428,7 +431,7 @@ C -------- NUMBER MAXIMAL AND MINIMAL OF STAGES  NS
       ELSE
          NSUS=IWORK(13)
          IF (NSUS.LE.0.OR.NS.GE.8.OR.NS.EQ.2.OR.NS.EQ.4.OR.NS.EQ.6) THEN
-            WRITE(6,*)' WRONG INPUT IWORK(13)=',IWORK(13)
+            WRITE(ERRBUF,*)' WRONG INPUT IWORK(13)=',IWORK(13)
             ARRET=.TRUE.
          END IF
       END IF
@@ -438,7 +441,7 @@ C -------- NMAX , THE MAXIMAL NUMBER OF STEPS -----
       ELSE
          NMAX=IWORK(2)
          IF (NMAX.LE.0) THEN
-            WRITE(6,*)' WRONG INPUT IWORK(2)=',IWORK(2)
+            WRITE(ERRBUF,*)' WRONG INPUT IWORK(2)=',IWORK(2)
             ARRET=.TRUE.
          END IF
       END IF
@@ -448,7 +451,7 @@ C -------- NIT    MAXIMAL NUMBER OF NEWTON ITERATIONS
       ELSE
          NIT=IWORK(3)
          IF (NIT.LE.0.OR.NIT.GT.50) THEN
-            WRITE(6,*)' CURIOUS INPUT IWORK(3)=',IWORK(3)
+            WRITE(ERRBUF,*)' CURIOUS INPUT IWORK(3)=',IWORK(3)
             ARRET=.TRUE.
          END IF
       END IF
@@ -464,7 +467,8 @@ C -------- PARAMETER FOR DIFFERENTIAL-ALGEBRAIC COMPONENTS
       NIND3=IWORK(7)
       IF (NIND1.EQ.0) NIND1=N
       IF (NIND1+NIND2+NIND3.NE.N) THEN
-       WRITE(6,*)' CURIOUS INPUT FOR IWORK(5,6,7)=',NIND1,NIND2,NIND3
+       WRITE(ERRBUF,*)' CURIOUS INPUT FOR IWORK(5,6,7)=',
+     &                NIND1,NIND2,NIND3
        ARRET=.TRUE.
       END IF
 C -------- PRED   STEP SIZE CONTROL
@@ -480,7 +484,7 @@ C -------- PARAMETER FOR SECOND ORDER EQUATIONS
       IF (M1.EQ.0) M2=N
       IF (M2.EQ.0) M2=M1
       IF (M1.LT.0.OR.M2.LT.0.OR.M1+M2.GT.N) THEN
-       WRITE(6,*)' CURIOUS INPUT FOR IWORK(9,10)=',M1,M2
+       WRITE(ERRBUF,*)' CURIOUS INPUT FOR IWORK(9,10)=',M1,M2
        ARRET=.TRUE.
       END IF
 C -------- UROUND   SMALLEST NUMBER SATISFYING 1.0D0+UROUND>1.0D0  
@@ -489,20 +493,21 @@ C -------- UROUND   SMALLEST NUMBER SATISFYING 1.0D0+UROUND>1.0D0
       ELSE
          UROUND=WORK(1)
          IF (UROUND.LE.1.0D-19.OR.UROUND.GE.1.0D0) THEN
-            WRITE(6,*)' COEFFICIENTS HAVE 20 DIGITS, UROUND=',WORK(1)
+            WRITE(ERRBUF,*)' COEFFICIENTS HAVE 20 DIGITS, UROUND=',
+     &          WORK(1)
             ARRET=.TRUE.
          END IF
       END IF
 C --------- CHECK IF TOLERANCES ARE O.K.
       IF (ITOL.EQ.0) THEN
           IF (ATOL(1).LE.0.D0.OR.RTOL(1).LE.10.D0*UROUND) THEN
-              WRITE (6,*) ' TOLERANCES ARE TOO SMALL'
+              WRITE(ERRBUF,*) ' TOLERANCES ARE TOO SMALL'
               ARRET=.TRUE.
           END IF
       ELSE
           DO I=1,N
           IF (ATOL(I).LE.0.D0.OR.RTOL(I).LE.10.D0*UROUND) THEN
-              WRITE (6,*) ' TOLERANCES(',I,') ARE TOO SMALL'
+              WRITE(ERRBUF,*) ' TOLERANCES(',I,') ARE TOO SMALL'
               ARRET=.TRUE.
           END IF
           END DO
@@ -513,7 +518,7 @@ C --------- SAFE     SAFETY FACTOR IN STEP SIZE PREDICTION
       ELSE
          SAFE=WORK(2)
          IF (SAFE.LE.0.001D0.OR.SAFE.GE.1.0D0) THEN
-            WRITE(6,*)' CURIOUS INPUT FOR WORK(2)=',WORK(2)
+            WRITE(ERRBUF,*)' CURIOUS INPUT FOR WORK(2)=',WORK(2)
             ARRET=.TRUE.
          END IF
       END IF
@@ -523,7 +528,7 @@ C ------ THET     DECIDES WHETHER THE JACOBIAN SHOULD BE RECOMPUTED;
       ELSE
          THET=WORK(3)
          IF (THET.GE.1.0D0) THEN
-            WRITE(6,*)' CURIOUS INPUT FOR WORK(3)=',WORK(3)
+            WRITE(ERRBUF,*)' CURIOUS INPUT FOR WORK(3)=',WORK(3)
             ARRET=.TRUE.
          END IF
       END IF
@@ -539,7 +544,7 @@ C --- QUOT1 AND QUOT2: IF QUOT1 < HNEW/HOLD < QUOT2, STEP SIZE = CONST.
          QUOT2=WORK(6)
       END IF
       IF (QUOT1.GT.1.0D0.OR.QUOT2.LT.1.0D0) THEN
-         WRITE(6,*)' CURIOUS INPUT FOR WORK(5,6)=',QUOT1,QUOT2
+         WRITE(ERRBUF,*)' CURIOUS INPUT FOR WORK(5,6)=',QUOT1,QUOT2
          ARRET=.TRUE.
       END IF
 C -------- MAXIMAL STEP SIZE
@@ -560,7 +565,7 @@ C -------  FACL,FACR     PARAMETERS FOR STEP SIZE SELECTION
          FACR=1.D0/WORK(9)
       END IF
       IF (FACL.LT.1.0D0.OR.FACR.GT.1.0D0) THEN
-            WRITE(6,*)' CURIOUS INPUT WORK(8,9)=',WORK(8),WORK(9)
+            WRITE(ERRBUF,*)' CURIOUS INPUT WORK(8,9)=',WORK(8),WORK(9)
             ARRET=.TRUE.
          END IF
 C -------- PARAMETERS FOR ORDER SELECTION STRATEGY
@@ -616,8 +621,8 @@ C -- MASS MATRIX
           END IF
 C ------ BANDWITH OF "MAS" NOT SMALLER THAN BANDWITH OF "JAC"
           IF (MLMAS.GT.MLJAC.OR.MUMAS.GT.MUJAC) THEN
-             WRITE (6,*) 'BANDWITH OF "MAS" NOT SMALLER THAN BANDWITH OF
-     & "JAC"'
+             WRITE(ERRBUF,*) 'BANDWITH OF "MAS" NOT SMALLER THAN 
+     & BANDWITH OF "JAC"'
             ARRET=.TRUE.
           END IF
       ELSE
@@ -632,8 +637,8 @@ C ------ BANDWITH OF "MAS" NOT SMALLER THAN BANDWITH OF "JAC"
       LDMAS2=MAX(1,LDMAS)
 C ------ HESSENBERG OPTION ONLY FOR EXPLICIT EQU. WITH FULL JACOBIAN
       IF ((IMPLCT.OR.JBAND).AND.IJOB.EQ.7) THEN
-         WRITE(6,*)' HESSENBERG OPTION ONLY FOR EXPLICIT EQUATIONS WITH 
-     &FULL JACOBIAN'
+         WRITE(ERRBUF,*)' HESSENBERG OPTION ONLY FOR EXPLICIT EQUATIONS
+     &WITH FULL JACOBIAN'
          ARRET=.TRUE.
       END IF
 C ------- PREPARE THE ENTRY-POINTS FOR THE ARRAYS IN WORK -----
@@ -652,7 +657,8 @@ C ------- PREPARE THE ENTRY-POINTS FOR THE ARRAYS IN WORK -----
 C ------ TOTAL STORAGE REQUIREMENT -----------
       ISTORE=IEE+NMEE*LDE1-1
       IF(ISTORE.GT.LWORK)THEN
-         WRITE(6,*)' INSUFFICIENT STORAGE FOR WORK, MIN. LWORK=',ISTORE
+         WRITE(ERRBUF,*)' INSUFFICIENT STORAGE FOR WORK, MIN. LWORK=',
+     &          ISTORE
          ARRET=.TRUE.
       END IF
 C ------- ENTRY POINTS FOR INTEGER WORKSPACE -----
@@ -662,7 +668,8 @@ C ------- ENTRY POINTS FOR INTEGER WORKSPACE -----
 C --------- TOTAL REQUIREMENT ---------------
       ISTORE=IEIPH+NM1-1
       IF (ISTORE.GT.LIWORK) THEN
-         WRITE(6,*)' INSUFF. STORAGE FOR IWORK, MIN. LIWORK=',ISTORE
+         WRITE(ERRBUF,*)' INSUFF. STORAGE FOR IWORK, MIN. LIWORK=',
+     &       ISTORE
          ARRET=.TRUE.
       END IF
 C ------ WHEN A FAIL HAS OCCURED, WE RETURN WITH IDID=-1
@@ -679,7 +686,7 @@ C -------- CALL TO CORE INTEGRATOR ------------
      &   WORK(IESCAL),WORK(IEFF),WORK(IEJAC),WORK(IEE1),WORK(IEE),
      &   WORK(IEMAS),WORK(IECON),IWORK(IEIP1),IWORK(IEIP2),
      &   IWORK(IEIPH),VITU,VITD,HHOU,HHOD,NFCN,NJAC,NSTEP,NACCPT,
-     &   NREJCT,NDEC,NSOL,RPAR,IPAR)
+     &   NREJCT,NDEC,NSOL,RPAR,IPAR,ERRBUF)
       IWORK(13)=NSUS
       IWORK(14)=NFCN
       IWORK(15)=NJAC
@@ -702,7 +709,7 @@ C
      &   NIND2,NIND3,PRED,FACL,FACR,M1,M2,NM1,NSMIN,NSMAX,NNMS,NM1NS,
      &   NMEE,IMPLCT,BANDED,LDJAC,LDE1,LDMAS,ZZ,Y0,SCAL,FF,FJAC,
      &   E1,EE2,FMAS,CONT,IP1,IP2,IPHES,VITU,VITD,HHOU,HHOD,
-     &   NFCN,NJAC,NSTEP,NACCPT,NREJCT,NDEC,NSOL,RPAR,IPAR)
+     &   NFCN,NJAC,NSTEP,NACCPT,NREJCT,NDEC,NSOL,RPAR,IPAR,ERRBUF)
 C ----------------------------------------------------------
 C     CORE INTEGRATOR FOR RADAU
 C     PARAMETERS SAME AS IN RADAU WITH WORKSPACE ADDED 
@@ -750,6 +757,8 @@ C --- THIS PARAMETER HAS TO BE CHANGED IF NUMBER OF STAGES IS >=7
       LOGICAL REJECT,FIRST,IMPLCT,BANDED,CALJAC,STARTN,CALHES
       LOGICAL INDEX1,INDEX2,INDEX3,LAST,PRED,CHANGE,UNEXP,UNEXN,VARIAB
       EXTERNAL FCN,JAC
+C -- pberndt XXX Added ERRBUF parameter to store WRITE output
+      CHARACTER(1024) ERRBUF
 C *** *** *** *** *** *** ***
 C  INITIALISATIONS
 C *** *** *** *** *** *** ***
@@ -1701,29 +1710,30 @@ C --- UNEXPECTED STEP-REJECTION
       IF (CALJAC) GOTO 20
       GOTO 10
 C --- FAIL EXIT
+C --- pberndt XXX Replaced the two lines from previously with a single
+C                 one for the messages
  175  CONTINUE
-      WRITE(9,979)X
-      WRITE(6,*) ' RHS EVALUATES TO NAN'
+      WRITE(ERRBUF,*) 'EXIT OF RADAU AT X=', X, ', RHS EVALUATES TO NAN'
       IDID=-42
       RETURN
  176  CONTINUE
-      WRITE(6,979)X   
-      WRITE(6,*) ' MATRIX IS REPEATEDLY SINGULAR, IER=',IER
+      WRITE(ERRBUF,*) 'EXIT OF RADAU AT X=', X, ', MATRIX IS REPEATEDLY
+     & SINGULAR, IER=',IER
       IDID=-4
       RETURN
  177  CONTINUE
-      WRITE(6,979)X   
-      WRITE(6,*) ' STEP SIZE T0O SMALL, H=',H
+      WRITE(ERRBUF,*) 'EXIT OF RADAU AT X=', X, ', STEP SIZE T0O SMALL,
+     & H=',H
       IDID=-3
       RETURN
  178  CONTINUE
-      WRITE(6,979)X   
-      WRITE(6,*) ' MORE THAN NMAX =',NMAX,'STEPS ARE NEEDED' 
+      WRITE(ERRBUF,*) 'EXIT OF RADAU AT X=', X, ', MORE THAN NMAX = ',
+     & NMAX,'STEPS ARE NEEDED' 
       IDID=-2
       RETURN
 C --- EXIT CAUSED BY SOLOUT
  179  CONTINUE
-C      WRITE(6,979)X
+C      WRITE(ERRBUF,979)X
  979  FORMAT(' EXIT OF RADAU AT X=',E18.4) 
       IDID=2
       RETURN
diff --git a/pyradau13.c b/pyradau13.c
index 2224e68..1d926de 100644
--- a/pyradau13.c
+++ b/pyradau13.c
@@ -41,13 +41,14 @@ extern "C" {
 		int    *LIWORK,  // (2+(NSMAX-1)/2)*N+20
 		float  *RPAR,    // User-supplied RHS arguments
 		int    *IPAR,    // See RPAR
-		int    *IDID     // Return value
+		int    *IDID,    // Return value
 						 //  IDID= 1  COMPUTATION SUCCESSFUL,
 						 //  IDID= 2  COMPUT. SUCCESSFUL (INTERRUPTED BY SOLOUT)
 						 //  IDID=-1  INPUT IS NOT CONSISTENT,
 						 //  IDID=-2  LARGER NMAX IS NEEDED,
 						 //  IDID=-3  STEP SIZE BECOMES TOO SMALL,
 						 //  IDID=-4  MATRIX IS REPEATEDLY SINGULAR.
+		char *ERRBUF     // Buffer to hold error messages, at least 1024 bytes
 	);
 
 	double contra_(
@@ -344,6 +345,9 @@ static PyObject *radau13(PyObject *self, PyObject *args, PyObject *kwargs) {
 
 	PyObject *list_retval = PyList_New(0);
 
+	char errbuf[1024];
+	memset(errbuf, 0, sizeof(errbuf));
+
 	for(current_level = 0; current_level < time_levels; current_level++) {
 		t_final = ((double *)PyArray_DATA(times_array))[current_level];
 
@@ -380,13 +384,14 @@ static PyObject *radau13(PyObject *self, PyObject *args, PyObject *kwargs) {
 			&liwork,                           // (2+(NSMAX-1)/2)*N+20
 			NULL,                              // User-supplied RHS arguments
 			(int*)&options,                    // See RPAR
-			&idid                              // Return value
+			&idid,                             // Return value
 											   // IDID= 1  COMPUTATION SUCCESSFUL,
 											   // IDID= 2  COMPUT. SUCCESSFUL (INTERRUPTED BY SOLOUT)
 											   // IDID=-1  INPUT IS NOT CONSISTENT,
 											   // IDID=-2  LARGER NMAX IS NEEDED,
 											   // IDID=-3  STEP SIZE BECOMES TOO SMALL,
 											   // IDID=-4  MATRIX IS REPEATEDLY SINGULAR.
+			errbuf
 		);
 		t_0 = t_final;
 
@@ -407,8 +412,12 @@ static PyObject *radau13(PyObject *self, PyObject *args, PyObject *kwargs) {
 
 	if(idid != 1 || y_out == NULL) {
 		if(PyErr_Occurred() == NULL) {
-			char err[255];
-			sprintf(err, "radau failed with idid = %d (%s)", idid, idid_error_strings[2 - idid]);
+			int i;
+			for(i=sizeof(errbuf)-1; i>0 && (errbuf[i] == ' ' || errbuf[i] == '\n' || errbuf[i] == 0 || errbuf[i] == '\t'); i--) {
+				errbuf[i] = 0;
+			}
+			char err[1024];
+			snprintf(err, sizeof(err), "radau failed with idid = %d (%s)%s%s", idid, idid_error_strings[2 - idid], errbuf ? "\n" : "", errbuf);
 			PyErr_SetString(PyExc_RuntimeError, err);
 		}
 		if(time_levels > 1) {
diff --git a/test.py b/test.py
index f255280..fae9025 100755
--- a/test.py
+++ b/test.py
@@ -90,6 +90,11 @@ class TestIntegration(unittest.TestCase):
         retval = radau13(rhs, [ 1, 2 ], 10, mass_matrix=mass_matrix)[0]
         self.assertAlmostEqual(retval, np.exp(2 * 10), 2)
 
+    def test_failure(self):
+        "This problem *is* unsolvable"
+        with self.assertRaises(RuntimeError):
+            radau13(lambda t, x: x**2, 1, 1)
+
 
 if __name__ == '__main__':
     unittest.main()
-- 
GitLab