About

本文将会是一个简单的踩坑介绍.

Corsika Install and Run

发邮件, 下载程序, 使用 ./coconut 进行安装, 最后 run 目录下开始运行.

Corsika Output

Corsika 程序的输出 DAT<run-number> 是一个 Fortran 的二进制结构. 本来还有一个比较友好的数据读取和处理程序 COAST. 但是一个比较尴尬的事情是貌似它有些问题无法正常读取数据 (学长说的), 在我的电脑上无法正常编译 (configure 文件没了, make 爆了). 那么与其去修代码解决依赖问题, 我决定听从学长的建议, 写一个程序自己读.

找到官方的文档 (CORSIKA_GUIDE), 然后找到 Output 部分. 嗯, 接下来就是一些肮脏的过程了.

也不算坑的一些东西

Corsika 的安装和使用

二进制数据读取

我用的是 lisp-binary 来帮助我从二进制文件里面读取数据. 毕竟不是严格意义上搞这些的人, 啥 IEEE 的浮点数定义和实现也不是很懂. 总之能读就好了吧.

按照 Corsika Guide 里面定义的块结构, 可以如下定义如何读取数据:

(defbinary particle-data (:export t :byte-order :little-endian)
  (description                 0d0    :type single-float)
  (p-x                         0d0    :type single-float)
  (p-y                         0d0    :type single-float)
  (p-z                         0d0    :type single-float)
  (x                           0d0    :type single-float)
  (y                           0d0    :type single-float)
  (time                        0d0    :type single-float))

(defbinary particle-data-block (:export t :byte-order :little-endian)
  (particles                   #()    :type (simple-array particle-data (38))))

然后通过 (read-binary 'particle-data-block stream) 从输入流中读取信息. 当然, 这里有一些小小的 Trick, 在之后会介绍.

Sub Block Read and Unread

一个简单的读取想法就是读数, 判断类型, 然后根据类型返回结构. 道理是这个样子, 但是有一个小小问题, 因为 lisp-binary 是用 stream 的方式进行数据读取的, 读完了之后, 已经读过的东西是不能再读的.

除非你把它放到一个缓冲块里面. (注: 我没有学过更加高深的数据读取方法, 所以我也说不好这样的操作是否最优, 至少当前我觉得能用就好了)

于是我写了这样的一个宏来帮助我实现 unread 这样的操作:

(defmacro with-stream-block ((stream block) &body body)
  (alexandria:with-gensyms (in out)
    `(flexi-streams:with-input-from-sequence
         (,in (flexi-streams:with-output-to-sequence (,out)
                (write-binary ,block ,out)))
       (with-wrapped-in-bit-stream (,stream ,in)
         ,@body))))

(defun read-block (corsika)
  (let* ((dummy (read-binary 'dummy-sub-block corsika))
         (type  (dummy-sub-block-type dummy)))
    (values
     (with-stream-block (stream dummy)
       (cond ((string= "RUNH" type) (read-binary 'run-header stream))
             ((string= "EVTH" type) (read-binary 'event-header stream))
             ((string= "LONG" type) (read-binary 'longitudinal-block stream))
             ((string= "EVTE" type) (read-binary 'event-end stream))
             ((string= "RUNE" type) (read-binary 'run-end stream))
             (T (read-binary 'particle-data-block stream))))
     type)))

这里默认 DATA BLOCK 全部都是粒子数据块.

并且会发现这里是一口气读取整个 sub-block, 并且只使用头部一个很小的部分进行类型判断, 这样有点慢. 最好是能够根据头部信息进行动态判断, 但是这个 particle-data-block 的结构, 和其他的 sub-block 的结构并不一样, 所以还是有点坑.

Data Block

首先到网上找了一个讲解 Corsika 的教学 PPT (Corsika_Installation_Physics.pdf), 然后里面的一节介绍了 Corsika 的二进制数据结构:

/_img/corsika/structure-of-corsika-binary-files.jpg

(当然, 更加详细的可以看 Corsika Guide 在第 10 节的 Output 里面的说明)

对于 THIN 参数, 可以跳过不理会先 (我应该没有在安装的过程中选择这个模式). 每个块的大小和作用都讲得挺清楚的, 所以看看图应该就能够理解. 这里有一个让人一开始比较容易误会的地方就是:

1 block = 5733(6552) words (4 bytes) = 21 sub-blocks of 273(312) words

并且在 Corsika Guide 里面也是这样画的:

/_img/corsika/corsika-output-block-strcutre.png

看起来一个 Block 的结构里面就是类似于:

Run Header
  Event Header
    Data Block
    ...
    Long Block
  Event End
  ...
Run End

这样的东西, 那么如果真就这样去读取数据的话, 那么就进入了一个比较坑爹的大坑了. 尤其是当你用二进制读取程序去看这个最后输出的文件的话, 发现好像这个 RUN HEADERRUN END 只有一对 (对于一次的 RUN), 但是不同事件数 EVENT 却有不同的长度, 那么这个 1 block = 21 sub-blocks 究竟是啥?

中间我试过去掉开头的 32 bytes (学长建议), 后来发现开头的 32 bytes 在无符号整数下代表 block 的大小 (对于我手上的, 就是 22932), 紧跟着的就是子块结构, 所以我的做法就是:

;;; This is not TRUE!!!!
(defun parse-corsika (corsika)
  ;; how to read block shall depend on output type
  (read-corsika-type corsika)           
  (loop for sub-block = (read-block corsika)
        while (not (eq 'run-end (type-of sub-block)))
        collect sub-block))

这样读到的结果是什么呢? 答案是前几个可以正常地读出来, 而之后的几个就开始出现乱码了. (其实不是乱码, 而是输出很明显不符合物理), 并且一只读下去会卡死并报出文件空的读取错误. 显然事情并没有那么简单.

那么最终的答案是什么呢?

答案是, 这个 block 就是个没有任何意义, 也没有啥结构的, 强行对输出文件每 21 个 sub-block 进行一划分的一个东西. (可能来源于历史上的卡片输出, 但是对于现在的这个, 真的是, 没啥必要). 也就是真实的数据如下:

block marker (22932)
  RUN HEADER
    EVENT HEADER
      DATA BLOCK
      ... (21 subblocks)
block marker (22932)

block marker (22932)
      DATA BLOCK
      LONG BLOCK
      ...
    EVENT END
    ... (21 sub-blocks)
block marker (22932)

block marker (22932)
  ...
  RUN END
  ... (empty with 0)
block marker (22932)

如果你用下面的程序来读取的话:

(with-open-binary-file (corsika output-data-path)
  (apply #'append
         (loop for i from 0 below 5
               do (read-corsika-type corsika)
               collect (loop for j below 21
                             for sub-block = (read-block corsika)
                             collect sub-block)
               do (read-corsika-type corsika))))
那么就能合理的看到最终的输出了
(#S(RUN-HEADER
    :TYPE "RUNH"
    :RUN-NUMBER 69.0
    :START-DATE 231009.0
    :PROGRAM-VERSION 7.75
    ;;; 略
    :NFLCHE+100NFRAGM 200.0)
 #S(EVENT-HEADER
    :TYPE "EVTH"
    :EVENT-NUMBER 1.0
    :PARTICLE-ID 5626.0
    ;;; 略
    :NO-USE #(0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
              0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
              0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0))
 #S(PARTICLE-DATA-BLOCK
    :PARTICLES #(#S(PARTICLE-DATA
                    :DESCRIPTION 6561.0
                    :P-X -2.552703
                    :P-Y 0.38220033
                    :P-Z 1.9585683
                    :X -152077.31
                    :Y 41590.297
                    :TIME 176972.77)
    ;;; 略
)))

Parse Data Structure

一个简单的数据读取程序如下:

(defun parse-corsika (file)
  (with-open-binary-file (corsika file)
    (let ((eof NIL))
      (apply #'nconc
             (loop do (read-corsika-type corsika)
                   collect (loop for counter below 21
                                 for sub-block = (read-block corsika)
                                 collect sub-block
                                 while (not (eq 'run-end (type-of sub-block)))
                                 finally (if (eq 'run-end (type-of sub-block))
                                             (setf eof T)))
                   while (not eof)
                   do (read-corsika-type corsika))))))

缺点就是有点太慢了, 并且出来的东西还不是有层次结构的东西, 需要能够更快一点, 更好一点…

写了一个简单的 parser, 看看能不能读出结构:

(defun parse-corsika-structure (corsika)
  (parse-run-block corsika))

(defun parse-run-block (corsika)
  (let ((header (car corsika))
        (remain (cdr corsika)))
    (when (typep header 'run-header)
      (list :header header
            :events (loop for event = (multiple-value-bind (event rest)
                                          (parse-event-block remain)
                                        (setf remain rest)
                                        event)
                          while event
                          collect event)
            :end (car remain)))))

(defun parse-event-block (corsika)
  (let ((header (car corsika))
        (remain (cdr corsika)))
    (if (typep header 'event-header)
        (values
         (list :header header
               :data-block (loop for data-block = (car remain)
                                 while (typep data-block 'particle-data-block)
                                 do (setf remain (cdr remain))
                                 collect data-block)
               :long-block (loop for long-block = (car remain)
                                 while (typep long-block 'longitudinal-block)
                                 do (setf remain (cdr remain))
                                 collect long-block)
               :end (car remain))
         (cdr remain))
        (values NIL corsika))))

用一个简单的函数来处理一下最终的结果, 防止出来的数据太多把屏幕给挤爆:

(defun map-tree (function tree)
    (if (listp tree)
        (loop for elem in tree
              collect (map-tree function elem))
        (funcall function tree)))

(map-tree (lambda (elem)
            (if (keywordp elem) elem (type-of elem)))
          (parse-corsika-structure
           (parse-corsika file)))
结果速览
(:HEADER RUN-HEADER :EVENTS
 ((:HEADER EVENT-HEADER :DATA-BLOCK
   (PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK
    PARTICLE-DATA-BLOCK PARTICLE-DATA-BLOCK)
   :LONG-BLOCK
   (LONGITUDINAL-BLOCK LONGITUDINAL-BLOCK LONGITUDINAL-BLOCK
    LONGITUDINAL-BLOCK)
   :END EVENT-END))
 :END RUN-END)

又: 好像也没有那么慢, 因为之前读的是一个产生了 1000 个 EVENT 的模拟结果, 所以会读得慢一点, 对于一个 EVENT 的结果用时大约是 0.117 秒, 还算可以接受, 那么就不管了. 对于 19 个 EVENT 的结果, 大约是 0.647 秒, 也不是不行.

整理一下进入下一个阶段.

End

总之目前先这样, 之后再想想看有没有更加好的方法来解决这个问题吧… 还是先着重处理物理上的问题.