diff options
-rw-r--r-- | ob-spice.org | 520 |
1 files changed, 260 insertions, 260 deletions
diff --git a/ob-spice.org b/ob-spice.org index 514c55b..69c3b0e 100644 --- a/ob-spice.org +++ b/ob-spice.org @@ -163,66 +163,66 @@ use batch mode * Code #+BEGIN_SRC emacs-lisp :tangle no - (add-to-list 'load-path "~/.emacs.d/lisp/ob-spice") +(add-to-list 'load-path "~/.emacs.d/lisp/ob-spice") #+END_SRC ** vars #+BEGIN_SRC emacs-lisp - (defvar org-babel-spice-eoe-indicator ":org_babel_spice_eoe" - "String to indicate that evaluation has completed.") +(defvar org-babel-spice-eoe-indicator ":org_babel_spice_eoe" + "String to indicate that evaluation has completed.") #+END_SRC ** Session handling #+BEGIN_SRC emacs-lisp - (defvar org-babel-spice-command "ngspice" - "Name of command to use for executing ngspice.") - (defun org-babel-spice-initiate-session (&optional session dir _params) - "Initiate a ngspice session. - Create comint buffer SESSION running ngspice starting in - default-directory or DIR if specified." - (let* ((sessionname (if (or (not session) (string= session "none")) - "spice" session)) - (session (make-comint sessionname org-babel-spice-command))) - (if (and dir (string-match "^/" dir)) - ;; absolute dir - (comint-simple-send session (format "cd %s" dir)) - ;; relative dir - (comint-simple-send session (format "cd %s%s" default-directory (or dir "")))) - session - )) - (defun org-babel-prep-session:spice (session params) - "Prepare SESSION according to header arguments in PARAMS." - (let ((session (org-babel-spice-initiate-session session)) - (var-lines (org-babel-variable-assignments:spice params))) - (org-babel-comint-in-buffer session - (sit-for .5) (goto-char (point-max)) - (mapc (lambda (var) - (insert var) (comint-send-input nil t) - (org-babel-comint-wait-for-output session) - (sit-for .1) (goto-char (point-max))) var-lines)) - session)) - (defun org-babel-load-session:spice (session body params) - "Load BODY into SESSION." - (save-window-excursion - (let ((buffer (org-babel-prep-session:spice session params))) - (with-current-buffer buffer - (goto-char (process-mark (get-buffer-process (current-buffer)))) - (insert (org-babel-chomp body))) - buffer))) - - ;; helper - - (defun org-babel-variable-assignments:spice (params) - "Return a list of spice statements to set the variables in PARAMS." - (mapcar - (lambda (pair) - (format "set %s=%s" - (car pair) - (org-babel-spice-var-to-spice (cdr pair)))) - (org-babel--get-vars params))) - (defun org-babel-spice-var-to-spice (var) - "Convert VAR into a spice variable." - (if (listp var) - (concat "( " (mapconcat #'org-babel-spice-var-to-spice var " ") " )") - (format "%S" var))) +(defvar org-babel-spice-command "ngspice" + "Name of command to use for executing ngspice.") +(defun org-babel-spice-initiate-session (&optional session dir _params) + "Initiate a ngspice session. +Create comint buffer SESSION running ngspice starting in +default-directory or DIR if specified." + (let* ((sessionname (if (or (not session) (string= session "none")) + "spice" session)) + (session (make-comint sessionname org-babel-spice-command))) + (if (and dir (string-match "^/" dir)) + ;; absolute dir + (comint-simple-send session (format "cd %s" dir)) + ;; relative dir + (comint-simple-send session (format "cd %s%s" default-directory (or dir "")))) + session + )) +(defun org-babel-prep-session:spice (session params) + "Prepare SESSION according to header arguments in PARAMS." + (let ((session (org-babel-spice-initiate-session session)) + (var-lines (org-babel-variable-assignments:spice params))) + (org-babel-comint-in-buffer session + (sit-for .5) (goto-char (point-max)) + (mapc (lambda (var) + (insert var) (comint-send-input nil t) + (org-babel-comint-wait-for-output session) + (sit-for .1) (goto-char (point-max))) var-lines)) + session)) +(defun org-babel-load-session:spice (session body params) + "Load BODY into SESSION." + (save-window-excursion + (let ((buffer (org-babel-prep-session:spice session params))) + (with-current-buffer buffer + (goto-char (process-mark (get-buffer-process (current-buffer)))) + (insert (org-babel-chomp body))) + buffer))) + +;; helper + +(defun org-babel-variable-assignments:spice (params) + "Return a list of spice statements to set the variables in PARAMS." + (mapcar + (lambda (pair) + (format "set %s=%s" + (car pair) + (org-babel-spice-var-to-spice (cdr pair)))) + (org-babel--get-vars params))) +(defun org-babel-spice-var-to-spice (var) + "Convert VAR into a spice variable." + (if (listp var) + (concat "( " (mapconcat #'org-babel-spice-var-to-spice var " ") " )") + (format "%S" var))) #+END_SRC ** NEXT Variable handling & expand body Don't replace variable calls in body. Instead set them in the spice @@ -232,185 +232,185 @@ session: ~set x=4~\\ To set lists/arrays: ~set x=( 1 2 3 4 )~. Whitespace is important! Access with ~$x[0]~ or ~$x[2-len]~. #+BEGIN_SRC emacs-lisp - ;; (lambda (text) (setq body (concat text "\n" body))) - (defun org-babel-spice-vector-search (body vars) - "Replace first instance in BODY for all VARS." - (mapc (lambda (pair) - (if (string-match (format - "\\$%s\\[\\([0-9]\\)\\]" - (car pair)) - body) - (let ((replacement (nth - (string-to-number (match-string 1 body)) - (cadr pair)))) - (setq body(format "%s%s%s" - (substring body 0 (match-beginning - 0)) - replacement - (substring body (match-end 0))))))) - vars) - body - ) +;; (lambda (text) (setq body (concat text "\n" body))) +(defun org-babel-spice-vector-search (body vars) + "Replace first instance in BODY for all VARS." + (mapc (lambda (pair) + (if (string-match (format + "\\$%s\\[\\([0-9]\\)\\]" + (car pair)) + body) + (let ((replacement (nth + (string-to-number (match-string 1 body)) + (cadr pair)))) + (setq body(format "%s%s%s" + (substring body 0 (match-beginning + 0)) + replacement + (substring body (match-end 0))))))) + vars) + body + ) #+END_SRC #+BEGIN_SRC emacs-lisp - (defun org-babel-spice-replace-vars (body vars) - "Expand BODY according to VARS." - (let ((old-body "")) - ;; replace vector variables preceded by '$' and followed by the - ;; index in square brackets starting at 0. Matches without - ;; preceding or succeeding spaces. - (while (not (string= old-body body)) - (setq old-body body) - (setq body (org-babel-spice-vector-search body vars)) - ) - ;; replace any variable names preceded by '$' with the actual - ;; value of the variable. Matches only with succeeding space or - ;; end of line to prevent namespace limitations. - (mapc (lambda (pair) - (setq body (replace-regexp-in-string - (format "\\$%s\\( \\)\\|\\$%s$" (car pair) - (car pair)) - (format "%s\1" (cdr pair)) - body))) - vars) - body)) - (defun org-babel-expand-body:spice (body params) - "Expand BODY according to PARAMS, return the expanded body." - (let ((vars (org-babel--get-vars params)) - (prologue (cdr (assq :prologue params))) - (epilogue (cdr (assq :epilogue params))) - (file (cdr (assq :file params)))) - (setq body (org-babel-spice-replace-vars body vars)) - ;; TODO :file stuff .... - - ;; add prologue/epilogue - (when prologue (setq body (concat prologue "\n" body))) - (when epilogue (setq body (concat body "\n" epilogue))) - body)) +(defun org-babel-spice-replace-vars (body vars) + "Expand BODY according to VARS." + (let ((old-body "")) + ;; replace vector variables preceded by '$' and followed by the + ;; index in square brackets starting at 0. Matches without + ;; preceding or succeeding spaces. + (while (not (string= old-body body)) + (setq old-body body) + (setq body (org-babel-spice-vector-search body vars)) + ) + ;; replace any variable names preceded by '$' with the actual + ;; value of the variable. Matches only with succeeding space or + ;; end of line to prevent namespace limitations. + (mapc (lambda (pair) + (setq body (replace-regexp-in-string + (format "\\$%s\\( \\)\\|\\$%s$" (car pair) + (car pair)) + (format "%s\1" (cdr pair)) + body))) + vars) + body)) +(defun org-babel-expand-body:spice (body params) + "Expand BODY according to PARAMS, return the expanded body." + (let ((vars (org-babel--get-vars params)) + (prologue (cdr (assq :prologue params))) + (epilogue (cdr (assq :epilogue params))) + (file (cdr (assq :file params)))) + (setq body (org-babel-spice-replace-vars body vars)) + ;; TODO :file stuff .... + + ;; add prologue/epilogue + (when prologue (setq body (concat prologue "\n" body))) + (when epilogue (setq body (concat body "\n" epilogue))) + body)) #+END_SRC ** ob-execute #+BEGIN_SRC emacs-lisp - (defun org-babel-spice-trim-body (body) - "Prepare BODY to be used in interactive ngspice session." - ;; random control codes after $var inserts +(defun org-babel-spice-trim-body (body) + "Prepare BODY to be used in interactive ngspice session." + ;; random control codes after $var inserts + (replace-regexp-in-string + "" " " + ;; .control .endc lines + (replace-regexp-in-string + "^ *\\.\\(control\\|endc\\) *$" "" + ;; comment lines (replace-regexp-in-string - "" " " - ;; .control .endc lines - (replace-regexp-in-string - "^ *\\.\\(control\\|endc\\) *$" "" - ;; comment lines - (replace-regexp-in-string - "^ *\\*.*$" "" body)))) - (defun org-babel-execute:spice (body params) - "Execute a block of Spice code with Babel. - This function is called by `org-babel-execute-src-block'." - (let* (;(body (org-babel-expand-body:spice body params)) - (gnuplot (cdr (assq :gnuplot params))) - (result-params (cdr (assq :result-params params))) - (result-type (cdr (assq :result-type params))) - (session (org-babel-spice-initiate-session - (cdr (assq :session params)) - (cdr (assq :dir params)))) - (vars (org-babel--get-vars params)) - (no-source (cdr (assq :no-source params))) - (break-index (if (string-match "^ *\.end *$" body) - (match-end 0) 0)) - ;;vars need to be replaced as they don't work when using source - (circuit-body (org-babel-expand-body:spice - (substring body 0 break-index) - (assq-delete-all :epilogue (copy-alist params)))) - ;; todo: replace vars. :-( → set vars break when doing something like $file.txt - (control-body (org-babel-spice-trim-body (substring body break-index))) - (full-control-body (if control-body - (org-babel-expand-body:generic - control-body - (assq-delete-all :prologue (copy-alist params)) - (org-babel-variable-assignments:spice params)))) - (circuit-file (if circuit-body (org-babel-temp-file "spice-body-" ".cir"))) - (result)) - - - (message (concat "circuit:\n" circuit-body)) - (message (concat "\n-----\ncontrol:\n" control-body)) - - ;; Source circuit-body - (with-temp-file circuit-file (insert circuit-body)) - (org-babel-spice-source session circuit-file) - ;; Run control-body - (setq result (org-babel-spice-evaluate session full-control-body result-type result-params)) - - - ;; TODO deal with temporary files - - ;;(org-babel-eval "ngspice -b " body) - ;; Write body to temp file & execute with ngspice comint buffer and ~source file~ - - - ;; TODO read outputs from files - - ;; TODO gnuplot options - (if (string= "yes" gnuplot) - nil ;return content(!) of gnuplot.plt for :post processing with gnuplot block? - nil ;return normal spice output - ) - result - )) - (defun org-babel-spice-source (buffer file) - "Source FILE in ngspice process running in BUFFER and return results." - (let ((body (concat "source " file))) - (org-babel-spice-evaluate buffer body 'value))) - (defun org-babel-spice-evaluate (buffer body result-type &optional result-params) - "Use BUFFER running ngspice process to eval BODY and return results. - If RESULT-TYPE equals `output' return all outputs, if it equals - `value' return only value of last statement." - (let ((eoe-string (format "echo \"%s\"" org-babel-spice-eoe-indicator))) - (pcase result-type - (`output - ;; Force session to be ready - ;;(org-babel-comint-with-output - ;; (buffer org-babel-spice-eoe-indicator t eoe-string) - ;; (insert eoe-string) (comint-send-input nil t)) - ;; Eval body - (replace-regexp-in-string - "^\\(ngspice [0-9]+ -> \\)*" "" - (mapconcat - #'identity - (butlast - (cdr - (split-string - (mapconcat - #'org-trim - (org-babel-comint-with-output (buffer org-babel-spice-eoe-indicator t body) - (mapcar (lambda (line) - (insert (org-babel-chomp line)) (comint-send-input nil t)) - (list body - eoe-string - "\n"))) - "\n") "[\r\n]")) 2) "\n")) - ) - (`value - (let ((tmp-file (org-babel-temp-file "spice-"))) - (org-babel-comint-with-output - (buffer org-babel-spice-eoe-indicator t body) - (mapcar - (lambda (line) - (insert (org-babel-chomp line)) (comint-send-input nil t)) - (append (list body) - (list (format "!! > %s" tmp-file) - (format "echo \"%s\"" org-babel-spice-eoe-indicator) - ))) - (comint-send-input nil t)) - ;; split result to output multiple comma separated vars as table - (let ((result (split-string (org-babel-chomp (org-babel-eval-read-file tmp-file)) ","))) - (if (cdr result) - result - (car result)) - ))) - ;;todo: add "smart" result type to display measurements (or echos?) & plot filenames - ))) - - (provide 'ob-spice) - ;;; ob-spice.el ends here + "^ *\\*.*$" "" body)))) +(defun org-babel-execute:spice (body params) + "Execute a block of Spice code with Babel. +This function is called by `org-babel-execute-src-block'." + (let* (;(body (org-babel-expand-body:spice body params)) + (gnuplot (cdr (assq :gnuplot params))) + (result-params (cdr (assq :result-params params))) + (result-type (cdr (assq :result-type params))) + (session (org-babel-spice-initiate-session + (cdr (assq :session params)) + (cdr (assq :dir params)))) + (vars (org-babel--get-vars params)) + (no-source (cdr (assq :no-source params))) + (break-index (if (string-match "^ *\.end *$" body) + (match-end 0) 0)) + ;;vars need to be replaced as they don't work when using source + (circuit-body (org-babel-expand-body:spice + (substring body 0 break-index) + (assq-delete-all :epilogue (copy-alist params)))) + ;; todo: replace vars. :-( → set vars break when doing something like $file.txt + (control-body (org-babel-spice-trim-body (substring body break-index))) + (full-control-body (if control-body + (org-babel-expand-body:generic + control-body + (assq-delete-all :prologue (copy-alist params)) + (org-babel-variable-assignments:spice params)))) + (circuit-file (if circuit-body (org-babel-temp-file "spice-body-" ".cir"))) + (result)) + + + (message (concat "circuit:\n" circuit-body)) + (message (concat "\n-----\ncontrol:\n" control-body)) + + ;; Source circuit-body + (with-temp-file circuit-file (insert circuit-body)) + (org-babel-spice-source session circuit-file) + ;; Run control-body + (setq result (org-babel-spice-evaluate session full-control-body result-type result-params)) + + + ;; TODO deal with temporary files + + ;;(org-babel-eval "ngspice -b " body) + ;; Write body to temp file & execute with ngspice comint buffer and ~source file~ + + + ;; TODO read outputs from files + + ;; TODO gnuplot options + (if (string= "yes" gnuplot) + nil ;return content(!) of gnuplot.plt for :post processing with gnuplot block? + nil ;return normal spice output + ) + result + )) +(defun org-babel-spice-source (buffer file) + "Source FILE in ngspice process running in BUFFER and return results." + (let ((body (concat "source " file))) + (org-babel-spice-evaluate buffer body 'value))) +(defun org-babel-spice-evaluate (buffer body result-type &optional result-params) + "Use BUFFER running ngspice process to eval BODY and return results. + If RESULT-TYPE equals `output' return all outputs, if it equals + `value' return only value of last statement." + (let ((eoe-string (format "echo \"%s\"" org-babel-spice-eoe-indicator))) + (pcase result-type + (`output + ;; Force session to be ready + ;;(org-babel-comint-with-output + ;; (buffer org-babel-spice-eoe-indicator t eoe-string) + ;; (insert eoe-string) (comint-send-input nil t)) + ;; Eval body + (replace-regexp-in-string + "^\\(ngspice [0-9]+ -> \\)*" "" + (mapconcat + #'identity + (butlast + (cdr + (split-string + (mapconcat + #'org-trim + (org-babel-comint-with-output (buffer org-babel-spice-eoe-indicator t body) + (mapcar (lambda (line) + (insert (org-babel-chomp line)) (comint-send-input nil t)) + (list body + eoe-string + "\n"))) + "\n") "[\r\n]")) 2) "\n")) + ) + (`value + (let ((tmp-file (org-babel-temp-file "spice-"))) + (org-babel-comint-with-output + (buffer org-babel-spice-eoe-indicator t body) + (mapcar + (lambda (line) + (insert (org-babel-chomp line)) (comint-send-input nil t)) + (append (list body) + (list (format "!! > %s" tmp-file) + (format "echo \"%s\"" org-babel-spice-eoe-indicator) + ))) + (comint-send-input nil t)) + ;; split result to output multiple comma separated vars as table + (let ((result (split-string (org-babel-chomp (org-babel-eval-read-file tmp-file)) ","))) + (if (cdr result) + result + (car result)) + ))) + ;;todo: add "smart" result type to display measurements (or echos?) & plot filenames + ))) + +(provide 'ob-spice) +;;; ob-spice.el ends here #+END_SRC * Tests @@ -423,45 +423,45 @@ echo "Hello World" #+BEGIN_SRC spice :var file="/tmp/spice_test" :session spicetest :results value - ,*Time Constant Measurement - r1 1 0 10k - c1 1 0 1p - - .IC V(1)=1 - .tran 1n 0.1u - .print tran v(1) - .end - - .control - run - set gnuplot_terminal=png - ,*gnuplot $file v(1) - meas tran value_at_tau find V(1) at=1e-8 - meas tran value_at_five_tau find V(1) at=5e-8 - echo value_at_tau = "$&value_at_tau" > $file.txt - ,* Any better way to write one value of vector to a file?? - echo $&value_at_tau ,$&value_at_five_tau - .endc +,*Time Constant Measurement +r1 1 0 10k +c1 1 0 1p + +.IC V(1)=1 +.tran 1n 0.1u +.print tran v(1) +.end + +.control +run +set gnuplot_terminal=png +,*gnuplot $file v(1) +meas tran value_at_tau find V(1) at=1e-8 +meas tran value_at_five_tau find V(1) at=5e-8 +echo value_at_tau = "$&value_at_tau" > $file.txt +,* Any better way to write one value of vector to a file?? +echo $&value_at_tau ,$&value_at_five_tau +.endc #+END_SRC #+RESULTS: | 0.36798 | 0.00671732 | #+BEGIN_SRC spice :var file="/tmp/spice_test2" :session spicetest :results output - ,*Virtual Ground Test: opamp gain = 1000 - vin in 0 dc 0V sin(0 .1 100Hz) - r1 in inn 10k - r2 inn out 10k - EOpamp out 0 0 inn 1000 - .tran 0.1ms 0.05s - .print tran v(in) - .meas tran vtest find v(in) at=0.04e-3 - .end - .control - run - set gnuplot_terminal=png - gnuplot $file v(in) v(out) v(inn) - .endc +,*Virtual Ground Test: opamp gain = 1000 +vin in 0 dc 0V sin(0 .1 100Hz) +r1 in inn 10k +r2 inn out 10k +EOpamp out 0 0 inn 1000 +.tran 0.1ms 0.05s +.print tran v(in) +.meas tran vtest find v(in) at=0.04e-3 +.end +.control +run +set gnuplot_terminal=png +gnuplot $file v(in) v(out) v(inn) +.endc #+END_SRC #+RESULTS: |