python版rungms

pygamessがrungmsを設定してない場合でもマトモなエラーを吐かないとレスポンスを貰ったので、きちんと例外投げるように変更しておいた。ついでにドキュメントを追加してバーションを上げておいた。

pygamessはもともと、gamessのインプット作るのめんどくさいっていうのがモジュール作成のモチベーションだったので、gamess実行環境(rungms)が既にあるという前提だったが、最近pybelに対応したことにより、分子設計的な側面が強くなってしまったので、gamess実行環境もまとめて面倒みたほうがいいかなぁと。

rungmsは単なるシェルスクリプトなんだけど、ファイルを直接編集しないといけないので、動くようにするのがめんどくさい(色々なOSに対応するようにしているのでごちゃごちゃしている)。普通に動かせる最小のスクリプトはどんな感じかなぁと1000行超えのスクリプトを削って行ったら200行くらいになったが、残ったコードのほとんどがsetenvだった。どんだけ環境変数好きやねん?と。

これをpythonで書きなおしたら60行くらい。

import os
import sys
import socket
from shutil import copyfile, rmtree
from tempfile import mkstemp, mkdtemp

scr = mkdtemp()
job = sys.argv[1]
gamess_path = "/usr/local/gamess"
ddikick = os.path.join(gamess_path, "ddikick.x")
gamess = os.path.join(gamess_path, "gamess.Jan122009R1.x")
hostname = socket.gethostname()

setenv_data = [
    (" MAKEFP", "efp"), ("GAMMA", "gamma"), ("TRAJECT", "trj"),
    ("RESTART", "rst"), ("  PUNCH", "dat"), ("  INPUT", "F05"),
    (" AOINTS", "F08"), (" MOINTS", "F09"), ("DICTNRY", "F10"),
    ("DRTFILE", "F11"), ("CIVECTR", "F12"), ("CASINTS", "F13"),
    (" CIINTS", "F14"), (" WORK15", "F15"), (" WORK16", "F16"),
    ("CSFSAVE", "F17"), ("FOCKDER", "F18"), (" WORK19", "F19"),
    (" DASORT", "F20"), ("DFTINTS", "F21"), ("DFTGRID", "F22"),
    (" JKFILE", "F23"), (" ORDINT", "F24"), (" EFPIND", "F25"),
    ("SVPWRK1", "F26"), ("SVPWRK2", "F27"), ("  MLTPL", "F28"),
    (" MLTPLT", "F29"), (" DAFL30", "F30"), (" SOINTX", "F31"),
    (" SOINTY", "F32"), (" SOINTZ", "F33"), (" SORESC", "F34"),
    ("GCILIST", "F37"), ("HESSIAN", "F38"), ("QMMMTEI", "F39"),
    ("SOCCDAT", "F40"), (" AABB41", "F41"), (" BBAA42", "F42"),
    (" BBBB43", "F43"), (" MCQD50", "F50"), (" MCQD51", "F51"),
    (" MCQD52", "F52"), (" MCQD53", "F53"), (" MCQD54", "F54"),
    (" MCQD55", "F55"), (" MCQD56", "F56"), (" MCQD57", "F57"),
    (" MCQD58", "F58"), (" MCQD59", "F59"), (" MCQD60", "F60"),
    ("NMRINT1", "F61"), ("NMRINT2", "F62"), ("NMRINT3", "F63"),
    ("NMRINT4", "F64"), ("NMRINT5", "F65"), ("NMRINT6", "F66"),
    ("ELNUINT", "F67"), ("NUNUINT", "F68"), (" NUMOIN", "F69"),
    (" GMCREF", "F70"), (" GMCO2R", "F71"), (" GMCROC", "F72"),
    (" GMCOOC", "F73"), (" GMCCC0", "F74"), (" GMCHMA", "F75"),
    (" GMCEI1", "F76"), (" GMCEI2", "F77"), (" GMCEOB", "F78"),
    (" GMCEDT", "F79"), (" GMCERF", "F80"), (" GMCHCR", "F81"),
    (" GMCGJK", "F82"), (" GMCGAI", "F83"), (" GMCGEO", "F84"),
    (" GMCTE1", "F85"), (" GMCTE2", "F86"), (" GMCHEF", "F87"),
    (" GMCMOL", "F88"), (" GMCMOS", "F89"), (" GMCWGT", "F90"),
    (" GMCRM2", "F91"), (" GMCRM1", "F92"), (" GMCR00", "F93"),
    (" GMCRP1", "F94"), (" GMCRP2", "F95"), (" GMCVEF", "F96"),
    (" GMCDIN", "F97"), (" GMC2SZ", "F98"), (" GMCCCS", "F99")
    ]

for e in setenv_data:
    os.environ[e[0].strip()] = "%s/%s.%s" %(scr, job, e[1])

os.environ["ERICFMT"] = os.path.join(gamess_path, "ericfmt.dat")
os.environ["MCPPATH"] = os.path.join(gamess_path, "mcpdata")
os.environ["EXTBAS"]  = "/dev/null"
os.environ["NUCBAS"]  = "/dev/null"

src = job + ".inp"
dest = os.path.join(scr,job) + ".F05"
copyfile(src,dest)
exec_string = "%s %s %s -ddi 1 1 %s -scr %s > t.out" % (ddikick, gamess, job, hostname, scr)
os.system(exec_string)

rmtree(scr)

結局ユーザーが指定しないといけない変数ってGamessのpathくらいだった。

pygamess+pybelで構造最適化計算

昨日は製薬業界の集まりがあって、他社のヒトと少し話す機会があって、LBDDどういう感じですかねと言われて、量子化学計算に真面目に取り組んでますよーっていう話から、ケミストは電子吸引基とか供与基とか言う割に計算して確かめようとしないんですよねー(そうですよねー)っていう流れになったので、そちらのケミストは計算するんですかねー?って聞いたら、するヒトはするし、しないヒトはしないっていう答えが返ってきた。想定通り。

もう少し知りたいのは、ちゃんと計算するケミストは、結局ただの計算マニアで結局普通のヒトなのか、それとも論理的で優秀な傾向が強いのか?ということかな。

さて、pygamessをpybelに対応させたので、より簡潔に書けるようになった。

>>> from pygamess import Gamess
>>> import pybel
>>> g = Gamess()
>>> g.run_type('optimize')
>>> mol = pybel.readstring('smi','C')
>>> mol.make3D()
>>> optimized_mol = g.run(mol)
>>> optimized_mol.energy
-37.0895866208

やっぱコンストラクタに引数をわたせるようにするべきだよなぁ。

ProductName 初めてのPython 第3版
Mark Lutz
オライリージャパン / 4830円 ( 2009-02-26 )


量子化学計算したいならこれかな。

FlaskでOpenIDを使う

Flask-OpenIDのサンプルアプリを Flask-SQLAlchemyPyJadeを使って書きなおしてみた。

ディレクトリ構造

$ tree
.
├── app.py
└── templates
    ├── create_profile.jade
    ├── index.jade
    ├── layout.jade
    └── login.jade

app.py

 import os
from flask import Flask, request, session, g, \
    redirect, url_for, abort, render_template, flash, jsonify
from flaskext.openid import OpenID
from flaskext.sqlalchemy import SQLAlchemy

DEBUG = True
SECRET_KEY = 'openidsample'
databese_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'oidsample.db')
SQLALCHEMY_DATABASE_URI = 'sqlite:///%s' % databese_file

app = Flask(__name__)
app.config.from_object(__name__)
app.jinja_env.add_extension('pyjade.ext.jinja.PyJadeExtension')
db = SQLAlchemy(app)
oid = OpenID(app, os.path.join(os.path.dirname(__file__), 'openid'))

class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(60))
    email = db.Column(db.String(200))
    openid = db.Column(db.String(200))

    def __init__(self, name, email, openid):
        self.name = name
        self.email = email
        self.openid = openid

@app.before_request
def lookup_current_user():
    g.user = None
    if 'openid' in session:
        g.user = User.query.filter_by(openid=session['openid']).first()

@app.route('/')
def show_index():
    return render_template('index.jade')

@app.route('/login', methods=['GET', 'POST'])
@oid.loginhandler
def login():
    if g.user is not None:
        return redirect(oid.get_next_url())
    if request.method == 'POST':
        openid = request.form.get('openid')
        if openid:
            return oid.try_login(openid, ask_for=['email', 'fullname', 'nickname'])
    return render_template('login.jade', next=oid.get_next_url(),
                           error=oid.fetch_error())

@oid.after_login
def create_or_login(resp):
    session['openid'] = resp.identity_url
    user = User.query.filter_by(openid=resp.identity_url).first()
    if user is not None:
        flash(u'Successfully signed in')
        g.user = user
        return redirect(oid.get_next_url())
    return redirect(url_for('create_profile', next=oid.get_next_url(),
                            name=resp.fullname or resp.nickname,
                            email=resp.email))

@app.route('/create-profile', methods=['GET', 'POST'])
def create_profile():
    if g.user is not None or 'openid' not in session:
        return redirect(url_for('index'))
    if request.method == 'POST':
        name = request.form['name']
        email = request.form['email']
        if not name:
            flash(u'Error: you have to provide a name')
        elif '@' not in email:
            flash(u'Error: you have to enter a valid email address')
        else:
            flash(u'Profile successfully created')
            db.session.add(User(name, email, session['openid']))
            db.session.commit()
            return redirect(oid.get_next_url())
    return render_template('create_profile.jade', next_url=oid.get_next_url())

@app.route('/logout')
def logout():
    session.pop('openid', None)
    flash(u'You were signed out')
    return redirect(oid.get_next_url())

if __name__ == '__main__':
    app.run()

layout.jade

bootstrap2をちょっと使いまわした。

!!! 5
html.no-js(lang='en')
  head
    title openid sample
  body
  .container
    block body

index.jade

extends "layout.jade"
block body
    if g.user
    h1 you are logged in as {{ g.user.name }}
    p
      a(href="{{ url_for('logout') }}") Logout
  else
    h1
      a(href="{{ url_for('login') }}") Login

login.jade

Flask-OpenIDのサンプルをhtml2jadeで変換した。

extends "layout.jade"
block body
  h2 Sign in
  form(action='', method='post')
    | {% if error %}
    p.error
      strong Error:
      | {{ error }}
    | {% endif %}
    p
      | OpenID:
      input(type='text', name='openid', size='30')
      input(type='submit', value='Sign in')
      input(type='hidden', name='next', value='{{ next }}')

create_profile.jade

Flask-OpenIDのサンプルをhtml2jadeで変換した。

extends "layout.jade"
block body
  h2 Create Profile
  p
    | Hey!  This is the first time you signed in on this website.  In
    | order to proceed we need a couple of more information from you:
    form(action='', method='post')
      dl
        dt
          | Name:
          dd
            input(type='text', name='name', size='30', value="{{ request.values.name }}")
            dt
              | E-Mail:
              dd
                input(type='text', name='email', size='30', value="{{ request.values.email }}")
      p
        input(type='submit', value='Create profile')
        input(type='hidden', name='next', value="{{ next }}")
    p If you don't want to proceed, you can a(href="{{ url_for('logout')}}") sign out again.

データベースを初期化するのに

>>> from app import db
>>> db.create_all()

と叩く必要がある。

FlaskのテストでAssertionError: Popped wrong request context.

Flask-Testingでテストを書いていて

@app.route('/json/tags')
@cache.cached(timeout=600)

みたいにキャッシュしているurlにgetすると

def test_get_json_tags(self):
    response = self.client.get("/json/tags")
    self.assert200(response)

タイトルのように

AssertionError: Popped wrong request context.

って怒られるんだが、どうすればいいのかわからない。

あと、テストブームが来たのでPython Testing: Beginner's Guideを注文してしまった。

ProductName Python Testing: Beginner's Guide
Daniel Arbuckle
Packt Publishing / 3220円 ( 2010-01-31 )


Flask-TestingのAssertion一覧

Flask-Testingを使おうと思ったんだけど、assertionの種類がドキュメントに載ってなかったのでソース読んだ。

assertTemplateUsed

指定されたテンプレートが使用されているかどうか調べる。

assertContext

コンテキスト中に指定された名前の変数が存在するかどうかと、その変数の値が想定通りかチェックする。

assertRedirects

リダイレクトされるかどうか。 status_codeが301か302であるかどうかを調べてる。

assertStatus

response statusが正しいか調べる。以下のメソッドも用意されている。

  • assert200
  • assert400
  • assert401
  • assert403
  • assert404
  • assert405

twill

twillっていうWeb browsing用のユーティリティも用意されているんだけどどういう時に使うのかよく分からなかった。codeとfindとnot findがあるのは分かるんだけど、テストにどう組み込んだらいいのかよくわからん。

pygamessをアップデートした

Gamessのpythonラッパーであるpygamessを0.2.0にアップデートした。

主な変更点

  • pybelに対応した
  • テストをpyvowsに移行した

pybelも使えるようになったので、よりpythonicにコードを書けるようになっていい感じ。 構造さえ用意すれば、こんなに簡単に量子化学計算ができて、量子化学計算にありがちなごちゃごちゃしたインプットファイルの作成から解放される。

>>> import pybel, pygamess
>>> g = pygamess.Gamess()
>>> mol = pybel.readfile("mol", "examples/ethane.mol").next()
>>> nmol = g.run(mol)
>>> nmol.energy
-78.30530748

それから、テストをpyvowsに変えてから快適です。自分にはBDDはあっているなぁ。

TODO

ドキュメントをきちんと書く

HTTPieは読みやすいcurl

これはちょっとテストしたい時に便利そう。

HTTPie - cURL for humans.

PythonでFasta fileを扱う

pyfastaってのがあった。

Biopythonでいいんじゃないかと思ったが、pyfastaのほうがメモリ効率がいいのかな。ゲノムを扱うような気がする。

あとでちゃんと見てみる。

Cuisine ( Chef-like functionality for Fabric )

FabricにFile I/O,User/Group Management, Package Management, Text-processingの機能を加えたもの(slide 53)

複数台のサーバーを管理することがあったら使ってみよう。

pybelで立体構造を立ち上げる

pybel(openbabel)で立体構造の立ち上げを行うのは簡単だ。

import pybel
mol = pybel.readstring("smi", "c1ccccc1C")
mol.make3D()
mol.write("mol")

コードを眺めていたら構造をたちあげるメソッドは

  • make3d
  • localopt
  • globalopt # ただし2.3.1ではコメントアウトされてた

make3dもglobaloptもlocaloptのstep数を変えて呼び出している。

localoptメソッドはデフォルトではmmff94パラメータを使って最急降下法で構造最適化をしている。