diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..29f5863 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,51 @@ +name: CI + +on: + push: + pull_request: + branches: [ master ] + +jobs: + test: + name: ${{ matrix.lisp }} on ${{ matrix.os }} + strategy: + matrix: + lisp: [sbcl-bin, ccl-bin] + os: [ ubuntu-latest ] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v2 + + - name: cache .roswell + id: cache-dot-roswell + uses: actions/cache@v1 + with: + path: ~/.roswell + key: ${{ runner.os }}-dot-roswell-${{ matrix.lisp }}-${{ hashFiles('**/*.asd') }} + restore-keys: | + ${{ runner.os }}-dot-roswell-${{ matrix.lisp }}- + ${{ runner.os }}-dot-roswell- + + - name: install roswell + shell: bash + # always run install, since it does some global installs and setup that isn't cached + env: + LISP: ${{ matrix.lisp }} + run: curl -L https://raw.githubusercontent.com/roswell/roswell/master/scripts/install-for-ci.sh | sh -x + - name: run lisp + continue-on-error: true + shell: bash + run: | + ros -e '(format t "~a:~a on ~a~%...~%~%" (lisp-implementation-type) (lisp-implementation-version) (machine-type))' + ros -e '(format t " fixnum bits:~a~%" (integer-length most-positive-fixnum))' + ros -e "(ql:quickload 'trivial-features)" -e '(format t "features = ~s~%" *features*)' + - name: update ql dist if we have one cached + shell: bash + run: ros -e "(ql:update-all-dists :prompt nil)" + + - name: load code and run tests + shell: bash + run: | + ros -e '(handler-bind (#+asdf3.2(asdf:bad-SYSTEM-NAME (function MUFFLE-WARNING))) (handler-case (ql:quickload :numpy-file-format/tests) (error (a) (format t "caught error ~s~%~a~%" a a) (uiop:quit 123))))' -e '(numpy-file-format/tests:run)' \ No newline at end of file diff --git a/code/load-array.lisp b/code/load-array.lisp index 81e175e..6849369 100644 --- a/code/load-array.lisp +++ b/code/load-array.lisp @@ -60,9 +60,11 @@ (/ (dtype-size dtype) 2) (dtype-size dtype))) (stream-element-type - (if (typep array '(array (signed-byte *))) - `(signed-byte ,chunk-size) - `(unsigned-byte ,chunk-size)))) + (if (or (eq element-type 'double-float) + (eq element-type 'single-float) + (subtypep element-type '(unsigned-byte *))) + `(unsigned-byte ,chunk-size) + `(signed-byte ,chunk-size)))) (unless (not fortran-order) (error "Reading arrays in Fortran order is not yet supported.")) (unless (eq (dtype-endianness dtype) +endianness+) diff --git a/code/numpy-file-format.asd b/code/numpy-file-format.asd index 0fc8650..155f533 100644 --- a/code/numpy-file-format.asd +++ b/code/numpy-file-format.asd @@ -12,4 +12,11 @@ (:file "dtypes") (:file "python-parser") (:file "load-array") - (:file "store-array"))) + (:file "store-array")) + + :in-order-to ((test-op (test-op :numpy-file-format/tests)))) + +(defsystem :numpy-file-format/tests + :depends-on ("numpy-file-format") + :components ((:file "tests")) + :perform (test-op (o c) (uiop:symbol-call :numpy-file-format/tests '#:run))) diff --git a/code/store-array.lisp b/code/store-array.lisp index df514b5..151b9c3 100644 --- a/code/store-array.lisp +++ b/code/store-array.lisp @@ -33,13 +33,16 @@ (write-byte (char-code #\space) stream)) (write-byte (char-code #\newline) stream)) ; Finish with a newline. ;; Now, open the file a second time to write the array contents. - (let* ((chunk-size (if (subtypep (array-element-type array) 'complex) + (let* ((element-type (array-element-type array)) + (chunk-size (if (subtypep element-type 'complex) (/ (dtype-size dtype) 2) (dtype-size dtype))) (stream-element-type - (if (subtypep (array-element-type array) '(signed-byte *)) - `(signed-byte ,chunk-size) - `(unsigned-byte ,chunk-size))) + (if (or (eq element-type 'double-float) + (eq element-type 'single-float) + (subtypep element-type '(unsigned-byte *))) + `(unsigned-byte ,chunk-size) + `(signed-byte ,chunk-size))) (total-size (array-total-size array))) (with-open-file (stream filename :direction :output :element-type stream-element-type diff --git a/code/tests.lisp b/code/tests.lisp new file mode 100644 index 0000000..0370530 --- /dev/null +++ b/code/tests.lisp @@ -0,0 +1,60 @@ +(defpackage :numpy-file-format/tests + (:use :cl :numpy-file-format) + (:export :run)) + +(in-package :numpy-file-format/tests) + +(defun array= (a b) + (and (eq (array-element-type a) + (array-element-type b)) + (= (array-total-size a) + (array-total-size b)) + (let ((= t)) + (loop :for i :below (array-total-size a) + :while = + :if (/= (row-major-aref a i) + (row-major-aref b i)) + :do (setq = nil)) + =))) + +(defun random-array (dimensions element-type random-generator) + (let ((array (make-array dimensions + :element-type element-type))) + (loop :for i :below (array-total-size array) + :do (setf (row-major-aref array i) + (funcall random-generator))) + array)) + +(defun run () + (let ((*print-length* 3)) + (handler-case + (loop :for (elt-type . random-generator) + :in (nconc (mapcar (lambda (size) + (cons (list 'signed-byte size) + (lambda () + (- (random (expt 2 size)) + (expt 2 (1- size)))))) + '(08 16 32 64)) + (mapcar (lambda (size) + (cons (list 'unsigned-byte size) + (lambda () + (random (expt 2 size))))) + '(08 16 32 64)) + (list (cons 'single-float (lambda () (random 1.0))) + (cons 'double-float (lambda () (random 1.0d0))) + (cons '(complex single-float) + (lambda () (complex (random 1.0) (random 1.0)))) + (cons '(complex double-float) + (lambda () (complex (random 1.0d0) (random 1.0d0)))))) + :do (let* ((array (random-array '(10 10 10) elt-type random-generator)) + (loaded-array (progn + (store-array array "/tmp/numpy-file-format.npy") + (load-array "/tmp/numpy-file-format.npy")))) + (assert (array= array loaded-array) + () + "Case: ~S~%ARRAYS~% ~A~%and~% ~A~%are not equal" + elt-type + array loaded-array))) + (error (condition) + (format t "~A~%" condition) + (uiop:quit 1)))))