3.4. Traits: 対話ダイアログを作る

著者: Didrik Pinte

Traits プロジェクトを利用することで Python のオブジェクトの属性に対する、検証、初期化、移譲、通知、グラフィカルなユーザインターフェースを簡単に追加できます

ちなみに

このチュートリアルでは、Traits ツールセットを探索し、ありきたりなコードの量を劇的に減らす方法を学び、素早く GUI アプリケーションを開発し、Enthought Tool Suite の他の部分の背後にあるアイデアを理解します。

Traits と Enthought Tool Suite は BSD スタイルライセンスに従うオープンソースプレオジェクトです。

想定読者

Python の進んだ使い方をある程度知っているプログラマ

必要なもの

3.4.1. 導入

ちなみに

Enthought Tool Suite はデータ分析のための洗練されたアプリケーションフレームワークを構築できるようにしてくれます、2次元プロットや3次元可視化。これらの強力で再利用可能な要素が慣用的な BSD-スタイルのライセンスでリリースされています。

../../_images/ETS.jpg

Enthought Tool Suite の主要なパッケージは:

  • Traits - 要素ベースにアプリケーションを構築するアプローチ。

  • Kiva - 基本的な2次元サポート、パスベースレンダリング、アフィン変換、アルファ合成などなど。

  • Enable - 2次元キャンバス描画をベースとするオブジェクト。

  • Chaco - プロットツールキットで複雑な2次元プロット操作を構築

  • Mayavi - VTK ベースの科学技術データの3次元可視化。

  • Envisage - アプリケーションプラグインフレームワークでスクリプト化できて拡張可能なアプリケーションの構築に利用

このチュートリアルでは Traits に焦点をあてます。

3.4.2. 例

このチュートリアルを通して、単純な水資源管理の例について扱います。ダムと貯水システムをモデル化してみましょう。貯水槽とダムは以下のパラメータ一式を持っています:

  • 名前

  • 貯水槽の最小と最大要領 [hm3]

  • ダムの高さと長さ [m]

  • 貯水池の面積 [km2]

  • 水頭 [m]

  • タービンの電力 [MW]

  • 最大、最小放流 [m3/s]

  • タービンの効率

貯水槽は決まった動きをします。1つは水を放流する際の電力生成に関連部分です。水力発電所での電力生成を近似する単純な公式は \(P = \rho hrgk\) で、ここで:

  • \(P\) はワット単位での電力、

  • \(\rho\) は水の密度 (~1000 kg/m3),

  • \(h\) は高さで単位はメートル、

  • \(r\) は流量で単位は単位秒あたりの立方メートル、

  • \(g\) は重力加速度で 9.8m/s2

  • \(k\) は効率を示す係数でで 0 から 1 の範囲をとります。

ちなみに

年間の電力エネルギー生成は利用できる水の貯蓄に依存します。いくつかの施設では水の流量の割合は年間で 10:1 程度で変化します。

2つめは貯水状態の振舞いに関する部分で、制御できるパラメーターと制御できないパラメータに依存します:

\(storage_{t+1} = storage_t + inflows - release - spillage - irrigation\)

警告

このチュートリアルで利用するデータは現実のものでなく、現実には意味がないものでしょう。

3.4.3. Traits とは

trait は型定義で、通常の Python オブジェクトの属性として利用することができ、いくつかの追加の性質を持つ属性を提供します:

  • 標準化:
    • 初期化

    • 検証

    • 遅延

  • 通知

  • 可視化

  • ドキュメント

クラスには trait の属性と通常の Pythonの属性を混在させることができます、あるいは特定の属性あるいは不特定の属性を利用するよクラス内で許可するよう選びだすことができます。クラスによって定義された Trait 属性は自動的に派生クラスにも継承されます。

traits クラスを作るのによく使う方法は HasTraits 基底クラスを拡張してクラス traits を定義することです:

from traits.api import HasTraits, Str, Float
class Reservoir(HasTraits):
name = Str
max_storage = Float

警告

Traits 3.x ユーザは

Traits 3.x を利用している場合、traits パッケージの名前空間を適用する必要があります:

  • traits.api は enthought.traits.api とする必要があります

  • traitsui.api は enthought.traits.ui.api とする必要があります

trait クラスを利用することは他の Python クラスと同様に簡単です。trait の値はキーワード引数で渡すことができることは覚えておいてください。

reservoir = Reservoir(name='Lac de Vouglans', max_storage=605)

3.4.3.1. 初期化

traits は全て値を初期化するデフォルト値を持ちます。例えば、python の基本型に以下のように対応する trait があります:

Trait

Python の型

組込みのデフォルト値

Bool

BooleanBool

False
Complex Complex number 0+0j
Float Floating point number 0.0
Int Plain integer 0
Long Long integer 0L
Str String ‘’
Unicode Unicode u’‘

他にも定義済みの trait 型がいくつもあります: Array, Enum, Range, Event, Dict, List, Color, Set, Expression, Code, Callable, Type, Tuple, など

デフォルト値はコード上で定義してカスタムできます:

from traits.api import HasTraits, Str, Float
class Reservoir(HasTraits):
name = Str
max_storage = Float(100)
reservoir = Reservoir(name='Lac de Vouglans')

複雑な初期化

trait に複雑な初期化が必要な場合は _XXX_default マジックメソッドを実装して利用できます。このメソッドは XXX trait にアクセスしようとするときに遅延呼び出しされます。例えば:

def _name_default(self):
""" Complex initialisation of the reservoir name. """
return 'Undefined'

3.4.3.2. 検証

全ての trait はユーザが内容を設定する際に検証します:

reservoir = Reservoir(name='Lac de Vouglans', max_storage=605)
reservoir.max_storage = '230'
---------------------------------------------------------------------------
TraitError Traceback (most recent call last)
.../scipy-lecture-notes/advanced/traits/<ipython-input-7-979bdff9974a> in <module>()
----> 1 reservoir.max_storage = '230'
.../traits/trait_handlers.pyc in error(self, object, name, value)
166 """
167 raise TraitError( object, name, self.full_info( object, name, value ),
--> 168 value )
169
170 def arg_error ( self, method, arg_num, object, name, value ):
TraitError: The 'max_storage' trait of a Reservoir instance must be a float, but a value of '23' <type 'str'> was specified.

3.4.3.3. ドキュメント

本質的に全ての traits はモデル地震についてのドキュメントを提供しています。宣言的なアプロートでクラスを作ることで、自己記述的にできます:

from traits.api import HasTraits, Str, Float
class Reservoir(HasTraits):
name = Str
max_storage = Float(100)

traits の desc メタデータは trait を記述的な情報を提供するのに利用できます:

from traits.api import HasTraits, Str, Float
class Reservoir(HasTraits):
name = Str
max_storage = Float(100, desc='Maximal storage [hm3]')

貯水槽クラスの定義しましょう:

from traits.api import HasTraits, Str, Float, Range
class Reservoir(HasTraits):
name = Str
max_storage = Float(1e6, desc='Maximal storage [hm3]')
max_release = Float(10, desc='Maximal release [m3/s]')
head = Float(10, desc='Hydraulic head [m]')
efficiency = Range(0, 1.)
def energy_production(self, release):
''' Returns the energy production [Wh] for the given release [m3/s]
'''
power = 1000 * 9.81 * self.head * release * self.efficiency
return power * 3600
if __name__ == '__main__':
reservoir = Reservoir(
name = 'Project A',
max_storage = 30,
max_release = 100.0,
head = 60,
efficiency = 0.8
)
release = 80
print 'Releasing {} m3/s produces {} kWh'.format(
release, reservoir.energy_production(release)
)

3.4.3.4. 可視化: ダイアログを開く

Traits ライブラリはユーザーインターフェースにも意識を向けており、貯水槽クラスに対するデフォルトビューをポップアップできます:

reservoir1 = Reservoir()
reservoir1.edit_traits()
../../_images/reservoir_default_view.png

TraitsUI はユーザインターフェースの作成方法を単純化します。HasTraits クラスの上の全ての trai はデフォルトのエディタを持ち、エディタは画面上に表示された trait (例えば Range trait をスライダーとして表示される、など)を管理します。

Traits がクラスを作成するのに宣言的な方法をとるのと同じように TraitsUI はユーザーインターフェースコードを作るための宣言的なインターフェースを提供しています:

from traits.api import HasTraits, Str, Float, Range
from traitsui.api import View
class Reservoir(HasTraits):
name = Str
max_storage = Float(1e6, desc='Maximal storage [hm3]')
max_release = Float(10, desc='Maximal release [m3/s]')
head = Float(10, desc='Hydraulic head [m]')
efficiency = Range(0, 1.)
traits_view = View(
'name', 'max_storage', 'max_release', 'head', 'efficiency',
title = 'Reservoir',
resizable = True,
)
def energy_production(self, release):
''' Returns the energy production [Wh] for the given release [m3/s]
'''
power = 1000 * 9.81 * self.head * release * self.efficiency
return power * 3600
if __name__ == '__main__':
reservoir = Reservoir(
name = 'Project A',
max_storage = 30,
max_release = 100.0,
head = 60,
efficiency = 0.8
)
reservoir.configure_traits()
../../_images/reservoir_view.png

3.4.3.5. 遅延

trait の定義や trait の値を他のオブジェクトに対して遅延させられることは Traits の強力な機能です。

from traits.api import HasTraits, Instance, DelegatesTo, Float, Range
from reservoir import Reservoir
class ReservoirState(HasTraits):
"""Keeps track of the reservoir state given the initial storage.
"""
reservoir = Instance(Reservoir, ())
min_storage = Float
max_storage = DelegatesTo('reservoir')
min_release = Float
max_release = DelegatesTo('reservoir')
# state attributes
storage = Range(low='min_storage', high='max_storage')
# control attributes
inflows = Float(desc='Inflows [hm3]')
release = Range(low='min_release', high='max_release')
spillage = Float(desc='Spillage [hm3]')
def print_state(self):
print 'Storage\tRelease\tInflows\tSpillage'
str_format = '\t'.join(['{:7.2f}'for i in range(4)])
print str_format.format(self.storage, self.release, self.inflows,
self.spillage)
print '-' * 79
if __name__ == '__main__':
projectA = Reservoir(
name = 'Project A',
max_storage = 30,
max_release = 100.0,
hydraulic_head = 60,
efficiency = 0.8
)
state = ReservoirState(reservoir=projectA, storage=10)
state.release = 90
state.inflows = 0
state.print_state()
print 'How do we update the current storage ?'

_xxxx_fired マジックメソッドを使う特殊な trait を使うことでイベントを管理し、関数呼び出しをトリガーすることができます:

from traits.api import HasTraits, Instance, DelegatesTo, Float, Range, Event
from reservoir import Reservoir
class ReservoirState(HasTraits):
"""Keeps track of the reservoir state given the initial storage.
For the simplicity of the example, the release is considered in
hm3/timestep and not in m3/s.
"""
reservoir = Instance(Reservoir, ())
min_storage = Float
max_storage = DelegatesTo('reservoir')
min_release = Float
max_release = DelegatesTo('reservoir')
# state attributes
storage = Range(low='min_storage', high='max_storage')
# control attributes
inflows = Float(desc='Inflows [hm3]')
release = Range(low='min_release', high='max_release')
spillage = Float(desc='Spillage [hm3]')
update_storage = Event(desc='Updates the storage to the next time step')
def _update_storage_fired(self):
# update storage state
new_storage = self.storage - self.release + self.inflows
self.storage = min(new_storage, self.max_storage)
overflow = new_storage - self.max_storage
self.spillage = max(overflow, 0)
def print_state(self):
print 'Storage\tRelease\tInflows\tSpillage'
str_format = '\t'.join(['{:7.2f}'for i in range(4)])
print str_format.format(self.storage, self.release, self.inflows,
self.spillage)
print '-' * 79
if __name__ == '__main__':
projectA = Reservoir(
name = 'Project A',
max_storage = 30,
max_release = 5.0,
hydraulic_head = 60,
efficiency = 0.8
)
state = ReservoirState(reservoir=projectA, storage=15)
state.release = 5
state.inflows = 0
# release the maximum amount of water during 3 time steps
state.update_storage = True
state.print_state()
state.update_storage = True
state.print_state()
state.update_storage = True
state.print_state()

オブジェクトの依存関係は trait の Property を利用して自動的に作られます。 depende_on 属性は Property と他の traits の依存を表現します。他の traits が変更されたときに property が無効化されます。そして traits は property のマジックメソッドを利用します:

  • _get_XXX は XXX Property trait の getter です

  • _set_get_XXX は XXX Property trait の setter です

from traits.api import HasTraits, Instance, DelegatesTo, Float, Range
from traits.api import Property
from reservoir import Reservoir
class ReservoirState(HasTraits):
"""Keeps track of the reservoir state given the initial storage.
For the simplicity of the example, the release is considered in
hm3/timestep and not in m3/s.
"""
reservoir = Instance(Reservoir, ())
max_storage = DelegatesTo('reservoir')
min_release = Float
max_release = DelegatesTo('reservoir')
# state attributes
storage = Property(depends_on='inflows, release')
# control attributes
inflows = Float(desc='Inflows [hm3]')
release = Range(low='min_release', high='max_release')
spillage = Property(
desc='Spillage [hm3]', depends_on=['storage', 'inflows', 'release']
)
### Private traits.
_storage = Float
### Traits property implementation.
def _get_storage(self):
new_storage = self._storage - self.release + self.inflows
return min(new_storage, self.max_storage)
def _set_storage(self, storage_value):
self._storage = storage_value
def _get_spillage(self):
new_storage = self._storage - self.release + self.inflows
overflow = new_storage - self.max_storage
return max(overflow, 0)
def print_state(self):
print 'Storage\tRelease\tInflows\tSpillage'
str_format = '\t'.join(['{:7.2f}'for i in range(4)])
print str_format.format(self.storage, self.release, self.inflows,
self.spillage)
print '-' * 79
if __name__ == '__main__':
projectA = Reservoir(
name = 'Project A',
max_storage = 30,
max_release = 5,
hydraulic_head = 60,
efficiency = 0.8
)
state = ReservoirState(reservoir=projectA, storage=25)
state.release = 4
state.inflows = 0
state.print_state()

注釈

property のキャッシュ

重い計算や長時間かかる計算が入力が変化していない状況で property にアクセスする際、問題になるかもしれません。 @cached_property デコレータを値のキャッシュと無効化された際の再計算に利用できます。

ReservoirState の例による TraitsUI の導入部分を拡張してみましょう。

from traits.api import HasTraits, Instance, DelegatesTo, Float, Range, Property
from traitsui.api import View, Item, Group, VGroup
from reservoir import Reservoir
class ReservoirState(HasTraits):
"""Keeps track of the reservoir state given the initial storage.
For the simplicity of the example, the release is considered in
hm3/timestep and not in m3/s.
"""
reservoir = Instance(Reservoir, ())
name = DelegatesTo('reservoir')
max_storage = DelegatesTo('reservoir')
max_release = DelegatesTo('reservoir')
min_release = Float
# state attributes
storage = Property(depends_on='inflows, release')
# control attributes
inflows = Float(desc='Inflows [hm3]')
release = Range(low='min_release', high='max_release')
spillage = Property(
desc='Spillage [hm3]', depends_on=['storage', 'inflows', 'release']
)
### Traits view
traits_view = View(
Group(
VGroup(Item('name'), Item('storage'), Item('spillage'),
label = 'State', style = 'readonly'
),
VGroup(Item('inflows'), Item('release'), label='Control'),
)
)
### Private traits.
_storage = Float
### Traits property implementation.
def _get_storage(self):
new_storage = self._storage - self.release + self.inflows
return min(new_storage, self.max_storage)
def _set_storage(self, storage_value):
self._storage = storage_value
def _get_spillage(self):
new_storage = self._storage - self.release + self.inflows
overflow = new_storage - self.max_storage
return max(overflow, 0)
def print_state(self):
print 'Storage\tRelease\tInflows\tSpillage'
str_format = '\t'.join(['{:7.2f}'for i in range(4)])
print str_format.format(self.storage, self.release, self.inflows,
self.spillage)
print '-' * 79
if __name__ == '__main__':
projectA = Reservoir(
name = 'Project A',
max_storage = 30,
max_release = 5,
hydraulic_head = 60,
efficiency = 0.8
)
state = ReservoirState(reservoir=projectA, storage=25)
state.release = 4
state.inflows = 0
state.print_state()
state.configure_traits()
../../_images/reservoir_state_view.png

いくつかのユースケースでは trait の値を設定してユーザが壊してしまうときのために移譲メカニズムが必要になります。 PrototypeFrom trait がこの動きを実装します。

from traits.api import HasTraits, Str, Float, Range, PrototypedFrom, Instance
class Turbine(HasTraits):
turbine_type = Str
power = Float(1.0, desc='Maximal power delivered by the turbine [Mw]')
class Reservoir(HasTraits):
name = Str
max_storage = Float(1e6, desc='Maximal storage [hm3]')
max_release = Float(10, desc='Maximal release [m3/s]')
head = Float(10, desc='Hydraulic head [m]')
efficiency = Range(0, 1.)
turbine = Instance(Turbine)
installed_capacity = PrototypedFrom('turbine', 'power')
if __name__ == '__main__':
turbine = Turbine(turbine_type='type1', power=5.0)
reservoir = Reservoir(
name = 'Project A',
max_storage = 30,
max_release = 100.0,
head = 60,
efficiency = 0.8,
turbine = turbine,
)
print 'installed capacity is initialised with turbine.power'
print reservoir.installed_capacity
print '-' * 15
print 'updating the turbine power updates the installed capacity'
turbine.power = 10
print reservoir.installed_capacity
print '-' * 15
print 'setting the installed capacity breaks the link between turbine.power'
print 'and the installed_capacity trait'
reservoir.installed_capacity = 8
print turbine.power, reservoir.installed_capacity

3.4.3.6. 通知

Trait はリスナーパターンを実装します。各 trait は静的、動的なリスナーがコールバックを提供します。trait が変更されると全てのリスナーが呼び出されます。

静的なリスナーは _XXX_changed マジックメソッドを利用して定義されます:

from traits.api import HasTraits, Instance, DelegatesTo, Float, Range
from reservoir import Reservoir
class ReservoirState(HasTraits):
"""Keeps track of the reservoir state given the initial storage.
"""
reservoir = Instance(Reservoir, ())
min_storage = Float
max_storage = DelegatesTo('reservoir')
min_release = Float
max_release = DelegatesTo('reservoir')
# state attributes
storage = Range(low='min_storage', high='max_storage')
# control attributes
inflows = Float(desc='Inflows [hm3]')
release = Range(low='min_release', high='max_release')
spillage = Float(desc='Spillage [hm3]')
def print_state(self):
print 'Storage\tRelease\tInflows\tSpillage'
str_format = '\t'.join(['{:7.2f}'for i in range(4)])
print str_format.format(self.storage, self.release, self.inflows,
self.spillage)
print '-' * 79
### Traits listeners ###########
def _release_changed(self, new):
"""When the release is higher than zero, warn all the inhabitants of
the valley.
"""
if new > 0:
print 'Warning, we are releasing {} hm3 of water'.format(new)
if __name__ == '__main__':
projectA = Reservoir(
name = 'Project A',
max_storage = 30,
max_release = 100.0,
hydraulic_head = 60,
efficiency = 0.8
)
state = ReservoirState(reservoir=projectA, storage=10)
state.release = 90
state.inflows = 0
state.print_state()

静的な trait 通知シグネチャはこのどれかになります:

  • def _release_changed(self):

    pass

  • def _release_changed(self, new):

    pass

  • def _release_changed(self, old, new):

    pass

  • def _release_changed(self, name, old, new

    pass

全ての変更に対するリスナー

HasTraits クラスの全ての変更に対するリスナーは _any_trait_changed マジックメソッドで実装できます。

多くの場合、アクティベートが必要なリスナーがどの型を持つかを前持ってしることはできません。traits はその場で動的リスナーとしてリスナーを登録できる機能を提供します

from reservoir import Reservoir
from reservoir_state_property import ReservoirState
def wake_up_watchman_if_spillage(new_value):
if new_value > 0:
print 'Wake up watchman! Spilling {} hm3'.format(new_value)
if __name__ == '__main__':
projectA = Reservoir(
name = 'Project A',
max_storage = 30,
max_release = 100.0,
hydraulic_head = 60,
efficiency = 0.8
)
state = ReservoirState(reservoir=projectA, storage=10)
#register the dynamic listener
state.on_trait_change(wake_up_watchman_if_spillage, name='spillage')
state.release = 90
state.inflows = 0
state.print_state()
print 'Forcing spillage'
state.inflows = 100
state.release = 0
print 'Why do we have two executions of the callback ?'

動的な trait 通知シグネチャは静的なものと異なります:

  • def wake_up_watchman():

    pass

  • def wake_up_watchman(new):

    pass

  • def wake_up_watchman(name, new):

    pass

  • def wake_up_watchman(object, name, new):

    pass

  • def wake_up_watchman(object, name, old, new):

    pass

動的なリスナーの削除は以下のようにします:
  • trait の remove_trait_listener メソッドにリスナーメソッドを引数として渡して呼び出します、

  • on_trait_change メソッドにリスナーメソッドとキーワード引数 remove=True を渡して呼び出します、

  • リスナーを持つインスタンスを削除します。

リスナーは on_trait_change デコレータを利用して追加することもできます:

from traits.api import HasTraits, Instance, DelegatesTo, Float, Range
from traits.api import Property, on_trait_change
from reservoir import Reservoir
class ReservoirState(HasTraits):
"""Keeps track of the reservoir state given the initial storage.
For the simplicity of the example, the release is considered in
hm3/timestep and not in m3/s.
"""
reservoir = Instance(Reservoir, ())
max_storage = DelegatesTo('reservoir')
min_release = Float
max_release = DelegatesTo('reservoir')
# state attributes
storage = Property(depends_on='inflows, release')
# control attributes
inflows = Float(desc='Inflows [hm3]')
release = Range(low='min_release', high='max_release')
spillage = Property(
desc='Spillage [hm3]', depends_on=['storage', 'inflows', 'release']
)
### Private traits. ##########
_storage = Float
### Traits property implementation.
def _get_storage(self):
new_storage = self._storage - self.release + self.inflows
return min(new_storage, self.max_storage)
def _set_storage(self, storage_value):
self._storage = storage_value
def _get_spillage(self):
new_storage = self._storage - self.release + self.inflows
overflow = new_storage - self.max_storage
return max(overflow, 0)
@on_trait_change('storage')
def print_state(self):
print 'Storage\tRelease\tInflows\tSpillage'
str_format = '\t'.join(['{:7.2f}'for i in range(4)])
print str_format.format(self.storage, self.release, self.inflows,
self.spillage)
print '-' * 79
if __name__ == '__main__':
projectA = Reservoir(
name = 'Project A',
max_storage = 30,
max_release = 5,
hydraulic_head = 60,
efficiency = 0.8
)
state = ReservoirState(reservoir=projectA, storage=25)
state.release = 4
state.inflows = 0

on_trait_change メソッドとデコレータでサポートされるパターンは強力です。読者は HasTraits.on_trait_change のドキュメンテーション文字列をみて詳細を調べておくべきです。

3.4.3.7. いくつかのより進んだ traits

以下の例では Enum と List traits の使い方を例示します:

from traits.api import HasTraits, Str, Float, Range, Enum, List
from traitsui.api import View, Item
class IrrigationArea(HasTraits):
name = Str
surface = Float(desc='Surface [ha]')
crop = Enum('Alfalfa', 'Wheat', 'Cotton')
class Reservoir(HasTraits):
name = Str
max_storage = Float(1e6, desc='Maximal storage [hm3]')
max_release = Float(10, desc='Maximal release [m3/s]')
head = Float(10, desc='Hydraulic head [m]')
efficiency = Range(0, 1.)
irrigated_areas = List(IrrigationArea)
def energy_production(self, release):
''' Returns the energy production [Wh] for the given release [m3/s]
'''
power = 1000 * 9.81 * self.head * release * self.efficiency
return power * 3600
traits_view = View(
Item('name'),
Item('max_storage'),
Item('max_release'),
Item('head'),
Item('efficiency'),
Item('irrigated_areas'),
resizable = True
)
if __name__ == '__main__':
upper_block = IrrigationArea(name='Section C', surface=2000, crop='Wheat')
reservoir = Reservoir(
name='Project A',
max_storage=30,
max_release=100.0,
head=60,
efficiency=0.8,
irrigated_areas=[upper_block]
)
release = 80
print 'Releasing {} m3/s produces {} kWh'.format(
release, reservoir.energy_production(release)
)

trait リスナーはリストの中身の変更を検知するのにも使えます、例えば、ある貯水槽に関連する総作地面積の追跡。

from traits.api import HasTraits, Str, Float, Range, Enum, List, Property
from traitsui.api import View, Item
class IrrigationArea(HasTraits):
name = Str
surface = Float(desc='Surface [ha]')
crop = Enum('Alfalfa', 'Wheat', 'Cotton')
class Reservoir(HasTraits):
name = Str
max_storage = Float(1e6, desc='Maximal storage [hm3]')
max_release = Float(10, desc='Maximal release [m3/s]')
head = Float(10, desc='Hydraulic head [m]')
efficiency = Range(0, 1.)
irrigated_areas = List(IrrigationArea)
total_crop_surface = Property(depends_on='irrigated_areas.surface')
def _get_total_crop_surface(self):
return sum([iarea.surface for iarea in self.irrigated_areas])
def energy_production(self, release):
''' Returns the energy production [Wh] for the given release [m3/s]
'''
power = 1000 * 9.81 * self.head * release * self.efficiency
return power * 3600
traits_view = View(
Item('name'),
Item('max_storage'),
Item('max_release'),
Item('head'),
Item('efficiency'),
Item('irrigated_areas'),
Item('total_crop_surface'),
resizable = True
)
if __name__ == '__main__':
upper_block = IrrigationArea(name='Section C', surface=2000, crop='Wheat')
reservoir = Reservoir(
name='Project A',
max_storage=30,
max_release=100.0,
head=60,
efficiency=0.8,
irrigated_areas=[upper_block],
)
release = 80
print 'Releasing {} m3/s produces {} kWh'.format(
release, reservoir.energy_production(release)
)

次の例では Array trait を使って特化した TraitsUI ChacoPlotItem を提供する方法を示します:

import numpy as np
from traits.api import HasTraits, Array, Instance, Float, Property
from traits.api import DelegatesTo
from traitsui.api import View, Item, Group
from chaco.chaco_plot_editor import ChacoPlotItem
from reservoir import Reservoir
class ReservoirEvolution(HasTraits):
reservoir = Instance(Reservoir)
name = DelegatesTo('reservoir')
inflows = Array(dtype=np.float64, shape=(None))
releass = Array(dtype=np.float64, shape=(None))
initial_stock = Float
stock = Property(depends_on='inflows, releases, initial_stock')
month = Property(depends_on='stock')
### Traits view ###########
traits_view = View(
Item('name'),
Group(
ChacoPlotItem('month', 'stock', show_label=False),
),
width = 500,
resizable = True
)
### Traits properties #####
def _get_stock(self):
"""
fixme: should handle cases where we go over the max storage
"""
return self.initial_stock + (self.inflows - self.releases).cumsum()
def _get_month(self):
return np.arange(self.stock.size)
if __name__ == '__main__':
reservoir = Reservoir(
name = 'Project A',
max_storage = 30,
max_release = 100.0,
head = 60,
efficiency = 0.8
)
initial_stock = 10.
inflows_ts = np.array([6., 6, 4, 4, 1, 2, 0, 0, 3, 1, 5, 3])
releases_ts = np.array([4., 5, 3, 5, 3, 5, 5, 3, 2, 1, 3, 3])
view = ReservoirEvolution(
reservoir = reservoir,
inflows = inflows_ts,
releases = releases_ts
)
view.configure_traits()
../../_images/reservoir_evolution.png