Skip to content

Combination of transformations and reductions leads to model data corruption #1177

Description

@bbbccd

issue was opened in dkpy(decargroup/dkpy#48), but does not appear to be dkpy specific.

in particular note that @sdahdah created a more minimal example that does not use dkpy at all

Activity

  1. roryyorke commented on Aug 2, 2025

    @roryyorke
    Contributor

    I can reproduce the fault, but it's non-deterministic. This keeps trying until it breaks:

    import time
    
    import control
    
    def main():
        i = 1
        while True:
            print(f'\riter {i} ...', end='')
            K_dk = control.rss(21, 1, 2, strictly_proper=True)
            K_arduino = reduce(K_dk,100,6)
            print(' done', end='')
            #time.sleep(0.1)
            i += 1
    
    def reduce(K,limit,order):
        K,_ = control.modal_form(K)
        fast_modes = list()
        n = K.A.shape[0]
        for i in range(n):
            fast = False
            for j,k in [(i,i-1),(i,i),(i,i+1),(i-1,i),(i+1,i)]:
                if j>=0 and k>=0 and j<n and k<n and abs(K.A[j,k]) > limit: fast = True
            if fast: fast_modes.append(i)
        return control.balanced_reduction(control.model_reduction(K,fast_modes),order,method="matchdc")
    
    if __name__ == "__main__":
        main()

    Sample output:

    work) rory@rory-latitude:~/projects/pycontrol/gh1177$ python ex1.py 
    iter 184 ...Segmentation fault (core dumped)
    (work) rory@rory-latitude:~/projects/pycontrol/gh1177$ python ex1.py 
    iter 44 ...corrupted size vs. prev_size
    Aborted (core dumped)
    (work) rory@rory-latitude:~/projects/pycontrol/gh1177$ python ex1.py 
    iter 44 ...corrupted double-linked list
    Aborted (core dumped)
    (work) rory@rory-latitude:~/projects/pycontrol/gh1177$ python ex1.py 
    iter 170 ...Segmentation fault (core dumped)
    (work) rory@rory-latitude:~/projects/pycontrol/gh1177$ python ex1.py 
    iter 9 ...Segmentation fault (core dumped)
    (work) rory@rory-latitude:~/projects/pycontrol/gh1177$ python ex1.py 
    iter 9 ...corrupted size vs. prev_size
    Aborted (core dumped)
    (work) rory@rory-latitude:~/projects/pycontrol/gh1177$ python ex1.py 
    iter 184 ...Segmentation fault (core dumped)
    

    I've attached the Conda environment config file, whence likely important versions:

      - control=0.10.2=pyhd8ed1ab_0
      - cpython=3.12.11=py312hd8ed1ab_0
      - libblas=3.9.0=32_h59b9bed_openblas
      - libcblas=3.9.0=32_he106b2a_openblas
      - liblapack=3.9.0=32_h7ac8fdf_openblas
      - libopenblas=0.3.30=pthreads_h94d23a6_1
      - numpy=2.3.2=py312h33ff503_0
      - slycot=0.6.0=py312hb6c5a0b_2
    

    My system:

    (work) rory@rory-latitude:~/projects/pycontrol/gh1177$ uname -a
    Linux rory-latitude 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
    

    work-env.yaml.zip

  2. roryyorke commented on Aug 2, 2025

    @roryyorke
    Contributor

    running a simpler, single-invocation of the test case under valgrind gives the output below. Command valgrind --log-file=valgrind2.out python ex2.py; ex2.py is at the end.

    I'm not sure where the first problem ("Conditional jump...") is, but it looks like the next one ("Invalid write ...") happens starting from ab09nd which is in SLICOT, so called via Slycot.

    ==120208== Memcheck, a memory error detector
    ==120208== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==120208== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
    ==120208== Command: python ex2.py
    ==120208== Parent PID: 20410
    ==120208== 
    ==120208== Conditional jump or move depends on uninitialised value(s)
    ==120208==    at 0x383D0B: PyUnicode_Decode (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x377EDE: unicode_new (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x314C7C: _PyObject_MakeTpCall (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x21BF1F: _PyEval_EvalFrameDefault.cold (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x317719: _PyObject_FastCallDictTstate (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x342C52: slot_tp_init (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x314D5B: _PyObject_MakeTpCall (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x21BF1F: _PyEval_EvalFrameDefault.cold (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x3CF6FD: PyEval_EvalCode (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x3EA9CC: builtin_exec (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x329F15: cfunction_vectorcall_FASTCALL_KEYWORDS (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x21CC53: _PyEval_EvalFrameDefault.cold (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208== 
    ==120208== Invalid write of size 4
    ==120208==    at 0x69D3F2F: dgetf2_k (in /home/rory/miniforge3/envs/work/lib/libopenblasp-r0.3.30.so)
    ==120208==    by 0x6651130: dgetrf_ (in /home/rory/miniforge3/envs/work/lib/libopenblasp-r0.3.30.so)
    ==120208==    by 0x3349A655: ab09dd_ (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/slycot/_wrapper.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x33499DBF: ab09bx_ (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/slycot/_wrapper.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x3349BB90: ab09nd_ (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/slycot/_wrapper.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x334605CA: f2py_rout__wrapper_ab09nd (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/slycot/_wrapper.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x314D8A: _PyObject_MakeTpCall (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x21BF1F: _PyEval_EvalFrameDefault.cold (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x3CF6FD: PyEval_EvalCode (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x3F37B9: run_eval_code_obj (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x3EEA9A: run_mod (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x4077AF: pyrun_file (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==  Address 0x2c0a2558 is 0 bytes after a block of size 8 alloc'd
    ==120208==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==120208==    by 0x5D8F00E: PyDataMem_UserNEW (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/numpy/_core/_multiarray_umath.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x5DAEA84: PyArray_NewFromDescr_int (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/numpy/_core/_multiarray_umath.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x5DB2D55: PyArray_NewLikeArrayWithShape (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/numpy/_core/_multiarray_umath.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x5DB30BD: PyArray_NewLikeArray (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/numpy/_core/_multiarray_umath.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x5DF4C5F: array_astype (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/numpy/_core/_multiarray_umath.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x329F15: cfunction_vectorcall_FASTCALL_KEYWORDS (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x5D1B363: gentype_generic_method (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/numpy/_core/_multiarray_umath.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x3376A0: method_vectorcall_VARARGS_KEYWORDS (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x329C9E: PyObject_Vectorcall (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x21BF1F: _PyEval_EvalFrameDefault.cold (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x3CF6FD: PyEval_EvalCode (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208== 
    ==120208== Invalid read of size 4
    ==120208==    at 0x69D3E05: dgetf2_k (in /home/rory/miniforge3/envs/work/lib/libopenblasp-r0.3.30.so)
    ==120208==    by 0x6651130: dgetrf_ (in /home/rory/miniforge3/envs/work/lib/libopenblasp-r0.3.30.so)
    ==120208==    by 0x3349A655: ab09dd_ (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/slycot/_wrapper.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x33499DBF: ab09bx_ (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/slycot/_wrapper.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x3349BB90: ab09nd_ (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/slycot/_wrapper.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x334605CA: f2py_rout__wrapper_ab09nd (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/slycot/_wrapper.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x314D8A: _PyObject_MakeTpCall (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x21BF1F: _PyEval_EvalFrameDefault.cold (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x3CF6FD: PyEval_EvalCode (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x3F37B9: run_eval_code_obj (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x3EEA9A: run_mod (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x4077AF: pyrun_file (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==  Address 0x2c0a2558 is 0 bytes after a block of size 8 alloc'd
    ==120208==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==120208==    by 0x5D8F00E: PyDataMem_UserNEW (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/numpy/_core/_multiarray_umath.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x5DAEA84: PyArray_NewFromDescr_int (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/numpy/_core/_multiarray_umath.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x5DB2D55: PyArray_NewLikeArrayWithShape (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/numpy/_core/_multiarray_umath.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x5DB30BD: PyArray_NewLikeArray (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/numpy/_core/_multiarray_umath.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x5DF4C5F: array_astype (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/numpy/_core/_multiarray_umath.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x329F15: cfunction_vectorcall_FASTCALL_KEYWORDS (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x5D1B363: gentype_generic_method (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/numpy/_core/_multiarray_umath.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x3376A0: method_vectorcall_VARARGS_KEYWORDS (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x329C9E: PyObject_Vectorcall (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x21BF1F: _PyEval_EvalFrameDefault.cold (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x3CF6FD: PyEval_EvalCode (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208== 
    ==120208==    at 0x58046FFA: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/memcheck-amd64-linux)
    ==120208==    by 0x58047127: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/memcheck-amd64-linux)
    ==120208==    by 0x580472CB: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/memcheck-amd64-linux)
    ==120208==    by 0x580514B4: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/memcheck-amd64-linux)
    ==120208==    by 0x5803DE9A: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/memcheck-amd64-linux)
    ==120208==    by 0x5803CD9F: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/memcheck-amd64-linux)
    ==120208==    by 0x58041F04: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/memcheck-amd64-linux)
    ==120208==    by 0x5803C1D8: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/memcheck-amd64-linux)
    ==120208==    by 0x101022653A: ???
    ==120208==    by 0x1002EB9F2F: ???
    ==120208==    by 0x1002EB9F17: ???
    ==120208==    by 0x1002EB9F2F: ???
    ==120208==    by 0x1002EB9F3F: ???
    ==120208==    at 0x81C9661: dlacn2_ (in /home/rory/miniforge3/envs/work/lib/libopenblasp-r0.3.30.so)
    ==120208==    by 0x818112C: dgecon_ (in /home/rory/miniforge3/envs/work/lib/libopenblasp-r0.3.30.so)
    ==120208==    by 0x3349A6BA: ab09dd_ (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/slycot/_wrapper.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x33499DBF: ab09bx_ (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/slycot/_wrapper.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x3349BB90: ab09nd_ (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/slycot/_wrapper.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x334605CA: f2py_rout__wrapper_ab09nd (in /home/rory/miniforge3/envs/work/lib/python3.12/site-packages/slycot/_wrapper.cpython-312-x86_64-linux-gnu.so)
    ==120208==    by 0x314D8A: _PyObject_MakeTpCall (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x21BF1F: _PyEval_EvalFrameDefault.cold (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x3CF6FD: PyEval_EvalCode (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x3F37B9: run_eval_code_obj (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x3EEA9A: run_mod (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x4077AF: pyrun_file (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x4072FD: _PyRun_SimpleFileObject (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x406A43: _PyRun_AnyFileObject (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x3FF1E1: Py_RunMain (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x3B7FA6: Py_BytesMain (in /home/rory/miniforge3/envs/work/bin/python3.12)
    ==120208==    by 0x4A13082: (below main) (libc-start.c:308)
    ==120208==    at 0x69C545C: blas_thread_server (in /home/rory/miniforge3/envs/work/lib/libopenblasp-r0.3.30.so)
    ==120208==    by 0x4878608: start_thread (pthread_create.c:477)
    ==120208==    by 0x4B0E352: clone (clone.S:95)
    ==120208==    at 0x487F376: futex_wait_cancelable (futex-internal.h:183)
    ==120208==    by 0x487F376: __pthread_cond_wait_common (pthread_cond_wait.c:508)
    ==120208==    by 0x487F376: pthread_cond_wait@@GLIBC_2.3.2 (pthread_cond_wait.c:647)
    ==120208==    by 0x69C54AB: blas_thread_server (in /home/rory/miniforge3/envs/work/lib/libopenblasp-r0.3.30.so)
    ==120208==    by 0x4878608: start_thread (pthread_create.c:477)
    ==120208==    by 0x4B0E352: clone (clone.S:95)
    ==120208==    at 0x487F376: futex_wait_cancelable (futex-internal.h:183)
    ==120208==    by 0x487F376: __pthread_cond_wait_common (pthread_cond_wait.c:508)
    ==120208==    by 0x487F376: pthread_cond_wait@@GLIBC_2.3.2 (pthread_cond_wait.c:647)
    ==120208==    by 0x69C54AB: blas_thread_server (in /home/rory/miniforge3/envs/work/lib/libopenblasp-r0.3.30.so)
    ==120208==    by 0x4878608: start_thread (pthread_create.c:477)
    ==120208==    by 0x4B0E352: clone (clone.S:95)
    
    import control
    
    
    def reduce(K,limit,order):
        K,_ = control.modal_form(K)
        fast_modes = list()
        n = K.A.shape[0]
        for i in range(n):
            fast = False
            for j,k in [(i,i-1),(i,i),(i,i+1),(i-1,i),(i+1,i)]:
                if j>=0 and k>=0 and j<n and k<n and abs(K.A[j,k]) > limit: fast = True
            if fast: fast_modes.append(i)
        return control.balanced_reduction(control.model_reduction(K,fast_modes),order,method="matchdc")
    
    K_dk = control.rss(21, 1, 2, strictly_proper=True)
    K_arduino = reduce(K_dk,100,6)
  3. roryyorke commented on Aug 2, 2025

    @roryyorke
    Contributor

    think this is a Slycot bug; see python-control/Slycot#245 for Slycot-only segfault-producing code.

  4. roryyorke commented on Aug 2, 2025

    @roryyorke
    Contributor

    It turns out this was fixed in python-control/Slycot#243, but we haven't made a Slycot release in a while.

    If I run tests with python-control 0.10.2 and Slycot master, I can't reproduce this.

    This issue can be closed, and I'll open a Slycot issue to discuss making a release.

  5. bbbccd commented on Aug 2, 2025

    @bbbccd
    Author

    I can reproduce the fault, but it's non-deterministic.

    I don't know exactly what you mean by deterministic, but the original long code that produced this bug fails everytime (albeit with different errors).

    This issue can be closed, and I'll open a Slycot issue to discuss making a release.

    is there a workaround in the meantime?

  6. murrayrm commented on Aug 2, 2025

    @murrayrm
    Member

    is there a workaround in the meantime?

    If you have a FORTRAN compiler on your system, you could check out the development version of slycot from github and do a local installation.

  7. bbbccd commented on Aug 2, 2025

    @bbbccd
    Author

    If you have a FORTRAN compiler on your system, you could check out the development version of slycot from github and do a local installation.

    :) ok so nothing easy then, thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions