python слишком быстро для gnuplot, чтобы завершить свою работу - PullRequest
0 голосов
/ 13 января 2020

Вместе с gnuplot и python у меня есть анализ, который вкратце можно описать как gnuplot, получающий доступ к статистике файлов, выкладывающий данные, в то время как Python впоследствии удалит файл навсегда:

import fnmatch
import os
import sys
import time
import PyGnuplot as gp

register = []
for file in os.listdir("."):
    if fnmatch.fnmatch(file, "*.dat"):
    register.append(file)
register.sort()


def plot_map():
    """ Access statistics (column 3) and plot the map with gnuplot. """
    for entry in register:
    input_file = str(entry)
    output_file = str(entry)[:-4] + str(".png")

    gp.c('input = "{}"'.format(input_file))
    gp.c('set output "{}"'.format(output_file))

    gp.c('stats input u 3')  # place holder

    gp.c('set terminal pngcairo')
    gp.c('set title "{}" noenhanced'.format(input_file))
    gp.c('unset key')

    gp.c('set size square; set pm3 map; set palette cubehelix')
    gp.c('sp input u 1:2:3')

    # For these data, task and allocated computer, gnuplot requires
    # this (empirically estimated) safety margin to complete:
    time.sleep(1)
    os.remove(entry)


plot_map()
sys.exit(0)

Как правило, несколько десятков матриц с 50k + записями должны быть проанализированы за один прогон. Однако без time.sleep, оцененным методом проб и ошибок, особенно на менее ресурсоемких компьютерах, Python может продвинуться намного быстрее, чем gnuplot, что в итоге не останется файлов данных для работы с gnuplot.

Как может Python более эффективно модерировать работу, чтобы дождаться завершения задачи gnuplot и только потом удалить рассматриваемый файл?

Ответы [ 2 ]

3 голосов
/ 13 января 2020

В таком случае я бы предложил позвонить gnuplot напрямую , используя subprocess.run. Когда run возвращается, gnuplot завершил работу.

Для примера :

#!/usr/bin/env python3
# file: histdata.py
# vim:fileencoding=utf-8:fdm=marker:ft=python
#
# Copyright © 2012-2018 R.F. Smith <rsmith@xs4all.nl>.
# SPDX-License-Identifier: MIT
# Created: 2012-07-23T01:18:29+02:00
# Last modified: 2019-07-27T13:50:29+0200
"""Make a histogram and calculate entropy of files."""

import math
import os.path
import subprocess as sp
import sys


def main(argv):
    """
    Entry point for histdata.

    Arguments:
        argv: List of file names.
    """
    if len(argv) < 1:
        sys.exit(1)
    for fn in argv:
        hdata, size = readdata(fn)
        e = entropy(hdata, size)
        print(f"entropy of {fn} is {e:.4f} bits/byte")
        histogram_gnuplot(hdata, size, fn)


def readdata(name):
    """
    Read the data from a file and count it.

    Arguments:
        name: String containing the filename to open.

    Returns:
        A tuple (counts list, length of data).
    """
    f = open(name, 'rb')
    data = f.read()
    f.close()
    ba = bytearray(data)
    del data
    counts = [0] * 256
    for b in ba:
        counts[b] += 1
    return (counts, float(len(ba)))


def entropy(counts, sz):
    """
    Calculate the entropy of the data represented by the counts list.

    Arguments:
        counts: List of counts.
        sz: Length of the data in bytes.

    Returns:
        Entropy value.
    """
    ent = 0.0
    for b in counts:
        if b == 0:
            continue
        p = float(b) / sz
        ent -= p * math.log(p, 256)
    return ent * 8


def histogram_gnuplot(counts, sz, name):
    """
    Use gnuplot to create a histogram from the data in the form of a PDF file.

    Arguments
        counts: List of counts.
        sz: Length of the data in bytes.
        name: Name of the output file.
    """
    counts = [100 * c / sz for c in counts]
    rnd = 1.0 / 256 * 100
    pl = ['set terminal pdfcairo size 18 cm,10 cm']

    pl += ["set style line 1 lc rgb '#E41A1C' pt 1 ps 1 lt 1 lw 4"]
    pl += ["set style line 2 lc rgb '#377EB8' pt 6 ps 1 lt 1 lw 4"]
    pl += ["set style line 3 lc rgb '#4DAF4A' pt 2 ps 1 lt 1 lw 4"]
    pl += ["set style line 4 lc rgb '#984EA3' pt 3 ps 1 lt 1 lw 4"]
    pl += ["set style line 5 lc rgb '#FF7F00' pt 4 ps 1 lt 1 lw 4"]
    pl += ["set style line 6 lc rgb '#FFFF33' pt 5 ps 1 lt 1 lw 4"]
    pl += ["set style line 7 lc rgb '#A65628' pt 7 ps 1 lt 1 lw 4"]
    pl += ["set style line 8 lc rgb '#F781BF' pt 8 ps 1 lt 1 lw 4"]
    pl += ["set palette maxcolors 8"]
    pl += [
        "set palette defined ( 0 '#E41A1C', 1 '#377EB8', 2 '#4DAF4A',"
        " 3 '#984EA3',4 '#FF7F00', 5 '#FFFF33', 6 '#A65628', 7 '#F781BF' )"
    ]
    pl += ["set style line 11 lc rgb '#808080' lt 1 lw 5"]
    pl += ["set border 3 back ls 11"]
    pl += ["set tics nomirror"]
    pl += ["set style line 12 lc rgb '#808080' lt 0 lw 2"]
    pl += ["set grid back ls 12"]
    nm = os.path.basename(name)
    pl += [f"set output 'hist-{nm}.pdf'"]
    pl += ['set xrange[-1:256]']
    pl += ['set yrange[0:*]']
    pl += ['set key right top']
    pl += ['set xlabel "byte value"']
    pl += ['set ylabel "occurance [%]"']
    pl += [f'rnd(x) = {rnd:.6f}']
    pl += [f"plot '-' using 1:2 with points ls 1 title '{name}', "
        f"rnd(x) with lines ls 2 title 'continuous uniform ({rnd:.6f}%)'"]
    for n, v in enumerate(counts):
        pl += [f'{n} {v}']
    pl += ['e']
    pt = '\n'.join(pl)
    sp.run(['gnuplot'], input=pt.encode('utf-8'), check=True)


if __name__ == '__main__':
    main(sys.argv[1:])

Редактировать: Как видите, приведенный выше код имеет значительную историю. Одна вещь, которую я обычно делаю по-другому, это использование встроенных данных (см. help inline внутри gnuplot) в виде документов здесь.

Это более гибко, чем использование файла '-'. Данные являются постоянными и могут использоваться на нескольких графиках.

Например:

pl += ['$data << EOD']
pl += [f'{n} {n**2}' for n in range(20)]
pl += ['EOD']
0 голосов
/ 14 января 2020

К сожалению, ваш минимальный пример неверен в нескольких отношениях и не работает сразу в моей системе.

Основная идея c идеи следующего скрипта (проверено (!) В Windows) это вызвать системную команду в gnuplot. Насколько я могу судить, полное имя файла требуется. Что-то особенное кажется в одинарных и двойных кавычках. Я не уверен, но я предполагаю, что gnuplot требует имя файла в одинарных кавычках, тогда как системная команда (по крайней мере, в Windows), кажется, требует имя файла в двойных кавычках.

В любом случае, по крайней мере, код был работа без проблем под Windows и создание графиков и удаление файлов. Если вы работаете с Linux, я надеюсь, что вы сможете адаптировать его соответствующим образом.

Код:

### generate plots and delete input files
import os
import PyGnuplot as gp

dir_in = r'C:\Test\In'
dir_out = r'C:\Test\Out'

for file in os.listdir(dir_in):
    ffname_in = "'" + os.path.join(dir_in,file) + "'"        # 'full filename_in'
    ffname_del = '"' + os.path.join(dir_in,file) + '"'        # "full filename_del"
    ffname_out = "'" + os.path.join(dir_out,file) + ".pdf'"   # 'full filename_out'
    gp.c('set terminal pdfcairo')
    gp.c('set output ' + ffname_out)
    gp.c('input = ' + ffname_in)
    gp.c('stats input u 2 nooutput')
    gp.c('plot input u 1:2')
    gp.c('set output')
    gp.c('print ' + ffname_in)
    delete_cmd = "system ('del " + ffname_del + "')"
    gp.c(delete_cmd)
### end of code
...