pymbd.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. #!/usr/bin/env python3
  2. #######################################################################
  3. #
  4. # this file provides a simple class wrapper around the libmbd
  5. # library for reading binary mcell data output.
  6. #
  7. # NOTE: In order to avoid memory leaks, instances of MBDIO should
  8. # always be invoked within a context manager, i.e.
  9. #
  10. # with MBDIO("some binary output file") as obj:
  11. # .... do something with obj ...
  12. #
  13. # (C) 2013 Markus Dittrich
  14. #
  15. #######################################################################
  16. import ctypes as ct
  17. import os
  18. # main handle to libmbd functions
  19. MBD = ct.CDLL("libmbd.so")
  20. class mbdio:
  21. """ Main wrapper class for libmbd functionality """
  22. # maximum allowed length for block names - hardcoded for now
  23. max_block_name_length = 256
  24. # dictionary mapping output types to string representations
  25. output_type_map = {1 : "step", 2 : "time_list", 3 : "iteration_list"}
  26. # dictionary mapping data types to string representations
  27. data_type_map = {0 : "int", 1 : "double"}
  28. def __init__(self, filename):
  29. """ open the binary mcell file. """
  30. # check that file exists
  31. if not os.path.exists(filename):
  32. raise IOError(1, filename + " does not exist")
  33. # open and initialize
  34. MBD.mbd_new.restype = ct.c_void_p
  35. self.obj = MBD.mbd_new(str.encode(filename))
  36. if MBD.mbd_initialize(self.obj) != 1:
  37. raise RuntimeError(1, "Failed to inialize " + filename)
  38. # retrieve basic properties
  39. MBD.mbd_get_num_datablocks.restype = ct.c_long
  40. MBD.mbd_get_num_datablocks.argtypes = [ct.c_void_p]
  41. self.__num_datablocks = MBD.mbd_get_num_datablocks(self.obj)
  42. MBD.mbd_get_stepsize.restype = ct.c_double
  43. MBD.mbd_get_stepsize.argtype = [ct.c_void_p]
  44. self.__step_size = MBD.mbd_get_stepsize(self.obj)
  45. MBD.mbd_get_output_type.restype = ct.c_int
  46. MBD.mbd_get_output_type.argtype = [ct.c_void_p]
  47. out_type = MBD.mbd_get_output_type(self.obj)
  48. self.__output_type = mbdio.output_type_map[out_type]
  49. MBD.mbd_get_blocksize.restype = ct.c_long
  50. MBD.mbd_get_blocksize.argtype = [ct.c_void_p]
  51. self.__block_size = MBD.mbd_get_blocksize(self.obj)
  52. def __enter__(self):
  53. """ needed to support with context manager """
  54. return self
  55. def __exit__(self, atype, value, traceback):
  56. """ needed to support with context manager
  57. The exit method makes sure to properly release
  58. the underlying C++ object to avoid memory leaks.
  59. """
  60. MBD.mbd_delete(self.obj)
  61. @property
  62. def num_datablocks(self):
  63. """ return the number of datablocks contained in data file. """
  64. return self.__num_datablocks
  65. @property
  66. def step_size(self):
  67. """ return the underlying stepsize for output type STEP
  68. or 0 otherwise
  69. """
  70. return self.__step_size
  71. @property
  72. def output_type(self):
  73. """ returns the ouput type
  74. which is either STEP, TIME_LIST, or ITERATION_LIST.
  75. """
  76. return self.__output_type
  77. @property
  78. def block_size(self):
  79. """ return the size of the stored data blocks. """
  80. return self.__block_size
  81. def id_from_name(self, name):
  82. """ returns the data set id corresponding to name
  83. or -1 if the name does not exist.
  84. """
  85. MBD.mbd_get_data_by_name.restype = ct.c_int
  86. MBD.mbd_get_data_by_name.argtype = [ct.c_void_p, ct.c_char_p]
  87. return MBD.mbd_get_id_from_name(self.obj, str.encode(name))
  88. @property
  89. def block_names(self):
  90. """ return the names of all available data blocks.
  91. NOTE: there are of course num_datablocks of them.
  92. """
  93. MBD.mbd_get_blocknames.restype = ct.c_void_p
  94. MBD.mbd_get_blocknames.argtype = [ct.c_void_p,
  95. ct.POINTER(ct.c_char_p),
  96. ct.c_size_t]
  97. num_blocks = self.__num_datablocks
  98. s = [ct.create_string_buffer(mbdio.max_block_name_length) for
  99. i in range(num_blocks)]
  100. blocknames = (ct.c_char_p * num_blocks)(*map(ct.addressof, s))
  101. MBD.mbd_get_blocknames(self.obj, blocknames,
  102. mbdio.max_block_name_length)
  103. return list(blocknames)
  104. @property
  105. def iteration_list(self):
  106. """ returns list with time or iteration values
  107. for output type TIME_LIST or ITERATION_LIST or an empty
  108. list otherwise.
  109. """
  110. if self.__output_type == "step":
  111. return []
  112. MBD.mbd_get_iteration_list.restype = ct.c_void_p
  113. MBD.mbd_get_iteration_list.argtype = [ct.c_void_p,
  114. ct.POINTER(ct.c_double)]
  115. size = self.__block_size
  116. iter_items = (ct.c_double * size)()
  117. MBD.mbd_get_iteration_list(self.obj, iter_items)
  118. if self.__output_type == "iteration_list":
  119. return list(map(int, iter_items))
  120. else:
  121. return list(iter_items)
  122. @property
  123. def time_list(self):
  124. """ this is a convenience function returning the times
  125. for output type STEP.
  126. """
  127. if not self.__output_type == "step":
  128. return []
  129. else:
  130. step_size = self.__step_size
  131. block_size = self.__block_size
  132. return [i*step_size for i in range(block_size)]
  133. def num_columns_by_id(self, data_id):
  134. """ returns the number of data columns present in dataset with id """
  135. MBD.mbd_get_num_columns_by_id.restype = ct.c_long
  136. MBD.mbd_get_num_columns_by_id.argtype = [ct.c_void_p, ct.c_int]
  137. return MBD.mbd_get_num_columns_by_id(self.obj, data_id)
  138. def data_by_id(self, data_id):
  139. """ returns the data for data set with id data_id
  140. NOTE: This function returns a list of tuples of doubles
  141. for each of the data columns of the data set.
  142. """
  143. MBD.mbd_get_data_by_id.restype = ct.c_int
  144. MBD.mbd_get_data_by_id.argtype = [ct.c_void_p,
  145. ct.POINTER(ct.POINTER(ct.c_double)),
  146. ct.POINTER(ct.c_int), ct.c_int]
  147. block_size = self.__block_size
  148. num_columns = self.num_columns_by_id(data_id)
  149. column_array = ct.c_double * block_size
  150. data_items = (ct.POINTER(ct.c_double) * num_columns)()
  151. for i in range(num_columns):
  152. data_items[i] = column_array()
  153. type_items = (ct.c_int * num_columns)()
  154. MBD.mbd_get_data_by_id(self.obj, data_items, type_items, data_id)
  155. out_list = []
  156. for col, data_col in enumerate(data_items):
  157. current_row = []
  158. for row in range(block_size):
  159. current_row.append(data_col[row])
  160. # cast to the proper data type; data is double by default
  161. if mbdio.data_type_map[type_items[col]] == "int":
  162. out_list.append(tuple(map(int, current_row)))
  163. else:
  164. out_list.append(tuple(current_row))
  165. return out_list
  166. def data_by_name(self, name):
  167. """ returns the data for data set with the given name
  168. NOTE: This function returns a list of tuples of doubles
  169. for each of the data columns of the data set.
  170. """
  171. data_id = self.id_from_name(name)
  172. if data_id == -1:
  173. return []
  174. else:
  175. return self.data_by_id(data_id)