/openmdao.lib/src/openmdao/lib/datatypes/domain/domain.py

https://github.com/thearn/OpenMDAO-Framework
Python | 284 lines | 176 code | 18 blank | 90 comment | 22 complexity | f27b9ddaa7558e3caa5db80925a468f2 MD5 | raw file
  1. import copy
  2. from openmdao.util.log import NullLogger
  3. class DomainObj(object):
  4. """
  5. A :class:`DomainObj` represents a (possibly multi-zoned) mesh and
  6. data related to that mesh.
  7. """
  8. def __init__(self):
  9. self.reference_state = None
  10. # Zones are kept in an explicit list to retain the order
  11. # that they were added.
  12. self.zones = []
  13. @property
  14. def shape(self):
  15. """ List of coordinate index limits for each zone. """
  16. return [zone.shape for zone in self.zones]
  17. @property
  18. def extent(self):
  19. """ List of coordinate ranges for each zone. """
  20. return [zone.extent for zone in self.zones]
  21. def add_domain(self, other, prefix='', make_copy=False):
  22. """
  23. Add zones from `other` to self, retaining names where possible.
  24. other: :class:`DomainObj`
  25. Source for new zone data.
  26. prefix: string
  27. String prepended to zone names.
  28. make_copy: bool
  29. If True, then a deep copy of each zone is made rather than just
  30. referring to a shared instance.
  31. """
  32. for zone in other.zones:
  33. name = other.zone_name(zone)
  34. if hasattr(self, name):
  35. name = ''
  36. self.add_zone(name, zone, prefix, make_copy)
  37. def add_zone(self, name, zone, prefix='', make_copy=False):
  38. """
  39. Add a :class:`Zone`. Returns the added zone.
  40. name: string
  41. Name for the zone. If None or blank, then a default of the form
  42. ``zone_N`` is used.
  43. zone: :class:`Zone`
  44. Zone to be added.
  45. prefix: string
  46. String prepended to the zone name.
  47. make_copy: bool
  48. If True, then a deep copy of `zone` is made rather than just
  49. referring to a shared instance.
  50. """
  51. if not name:
  52. name = 'zone_%d' % (len(self.zones) + 1)
  53. name = prefix+name
  54. if hasattr(self, name):
  55. raise ValueError('name %r is already bound' % name)
  56. if make_copy:
  57. zone = copy.deepcopy(zone)
  58. setattr(self, name, zone)
  59. self.zones.append(zone)
  60. return zone
  61. def remove_zone(self, zone):
  62. """
  63. Remove a zone. Returns the removed zone.
  64. zone: string or :class:`Zone`
  65. Zone to be removed.
  66. """
  67. if isinstance(zone, basestring):
  68. name = zone
  69. zone = getattr(self, zone)
  70. else:
  71. name = self.zone_name(zone)
  72. delattr(self, name)
  73. self.zones.remove(zone)
  74. return zone
  75. def rename_zone(self, name, zone):
  76. """ Rename a zone.
  77. name: string
  78. New name for the zone.
  79. zone: :class:`Zone`
  80. Zone to be renamed.
  81. """
  82. if hasattr(self, name):
  83. raise ValueError('name %r is already bound' % name)
  84. current = self.zone_name(zone)
  85. delattr(self, current)
  86. setattr(self, name, zone)
  87. def zone_name(self, zone):
  88. """
  89. Return name that a zone is bound to.
  90. zone: :class:`Zone`
  91. Zone whose name is to be returned.
  92. """
  93. for name, value in self.__dict__.items():
  94. if value is zone:
  95. return name
  96. raise ValueError('cannot find zone!')
  97. def copy(self):
  98. """ Returns a deep copy of self. """
  99. return copy.deepcopy(self)
  100. def deallocate(self):
  101. """ Deallocate resources. """
  102. for zone in self.zones:
  103. delattr(self, self.zone_name(zone))
  104. self.zones = []
  105. def is_equivalent(self, other, logger=None, tolerance=0.):
  106. """
  107. Test if self and `other` are equivalent.
  108. other: :class:`DomainObj`
  109. The domain to check against.
  110. logger: Logger or None
  111. Used to log debug messages that will indicate what, if anything,
  112. is not equivalent.
  113. tolerance: float
  114. The maximum relative difference in array values to be considered
  115. equivalent.
  116. """
  117. logger = logger or NullLogger()
  118. if not isinstance(other, DomainObj):
  119. logger.debug('other is not a DomainObj object.')
  120. return False
  121. if len(self.zones) != len(other.zones):
  122. logger.debug('zone count mismatch.')
  123. return False
  124. for zone in self.zones:
  125. name = self.zone_name(zone)
  126. try:
  127. other_zone = getattr(other, name)
  128. except AttributeError:
  129. logger.debug('other is missing zone %r.', name)
  130. return False
  131. if not zone.is_equivalent(other_zone, logger, tolerance):
  132. logger.debug('zone %r equivalence failed.', name)
  133. return False
  134. return True
  135. def extract(self, zone_args):
  136. """
  137. Construct a new :class:`DomainObj` from grid and flow data extracted
  138. from the specified regions of each zone. Existing zone names are used
  139. for the new domain's zones.
  140. zone_args: sequence
  141. Sequence of argument tuples to be used to extract data from each
  142. zone. If an argument tuple is empty or ``None`` then that zone
  143. is skipped.
  144. """
  145. domain = DomainObj()
  146. for i, args in enumerate(zone_args):
  147. if args:
  148. zone = self.zones[i]
  149. name = self.zone_name(zone)
  150. domain.add_zone(name, zone.extract(*args))
  151. if self.reference_state is not None:
  152. domain.reference_state = self.reference_state.copy()
  153. return domain
  154. def extend(self, zone_args):
  155. """
  156. Construct a new :class:`DomainObj` from zones extended according to
  157. `zone_args`. Existing zone names are used for the new domain's zones.
  158. zone_args: sequence
  159. Sequence of argument tuples to be used to extend each zone.
  160. If an argument tuple is empty or ``None``, then that zone
  161. is skipped.
  162. """
  163. domain = DomainObj()
  164. for i, args in enumerate(zone_args):
  165. if args:
  166. zone = self.zones[i]
  167. name = self.zone_name(zone)
  168. domain.add_zone(name, zone.extend(*args))
  169. return domain
  170. def make_cartesian(self, axis='z'):
  171. """
  172. Convert to Cartesian coordinate system.
  173. axis: string
  174. Specifies which is the cylinder axis ('z' or 'x').
  175. """
  176. for zone in self.zones:
  177. zone.make_cartesian(axis)
  178. def make_cylindrical(self, axis='z'):
  179. """
  180. Convert to cylindrical coordinate system.
  181. axis: string
  182. Specifies which is the cylinder axis ('z' or 'x').
  183. """
  184. for zone in self.zones:
  185. zone.make_cylindrical(axis)
  186. def make_left_handed(self):
  187. """ Convert to left-handed coordinate system. """
  188. for zone in self.zones:
  189. zone.make_left_handed()
  190. def make_right_handed(self):
  191. """ Convert to right-handed coordinate system. """
  192. for zone in self.zones:
  193. zone.make_right_handed()
  194. def translate(self, delta_x, delta_y, delta_z):
  195. """
  196. Translate coordinates.
  197. delta_x, delta_y, delta_z: float
  198. Amount of translation along the corresponding axis.
  199. """
  200. for zone in self.zones:
  201. zone.translate(delta_x, delta_y, delta_z)
  202. def rotate_about_x(self, deg):
  203. """
  204. Rotate about the X axis.
  205. deg: float (degrees)
  206. Amount of rotation.
  207. """
  208. for zone in self.zones:
  209. zone.rotate_about_x(deg)
  210. def rotate_about_y(self, deg):
  211. """
  212. Rotate about the Y axis.
  213. deg: float (degrees)
  214. Amount of rotation.
  215. """
  216. for zone in self.zones:
  217. zone.rotate_about_y(deg)
  218. def rotate_about_z(self, deg):
  219. """
  220. Rotate about the Z axis.
  221. deg: float (degrees)
  222. Amount of rotation.
  223. """
  224. for zone in self.zones:
  225. zone.rotate_about_z(deg)
  226. def promote(self):
  227. """ Promote from N-dimensional to N+1 dimensional index space. """
  228. for zone in self.zones:
  229. zone.promote()
  230. def demote(self):
  231. """ Demote from N-dimensional to N-1 dimensional index space. """
  232. for zone in self.zones:
  233. zone.demote()