指数関数のアニメーションと静止画を作成する SageMath スクリプト

正の実数を底とする指数関数と、その導関数の関係を説明するために、グラフのアニメーションと静止画を、SageMath を使って作成しました。 その SageMath スクリプトを、このページで公開します。

(There is the English(英語) page.)

(最終更新日: 2021年3月24日)

前書き

前のページ で、正の実数 \(a\) を底とする指数関数 \(a^x\) と、その導関数の関係について、説明しています。 この説明の中では、視覚的な理解を促すために、これらの式を描くグラフのアニメーションと静止画を使用しています。 また、補足説明のために、対数関数のグラフのアニメーションと静止画も使用しています。

これらのアニメーションと静止画は、SageMath で作成しました。 その SageMath スクリプトを、次の章 に公開します。

アニメーションと静止画を作成するスクリプト

前のページ に掲載した、アニメーションと静止画を作成するための、SageMath スクリプトを以下に示します。 このスクリプトは、先に、静止画を作成し、次に、アニメーションを作成します。

# This SageMath script have been tested with SageMath 9.1 and FFmpeg 3.4.8
# on Ubuntu 18.04.

# This script outputs the graph image files (SVG format) of
# the following functions:
#
# * The exponential function y=a^x and its derivative y',
#   where "a" is a positive real number.
#   2, e, and 4 are substituted for the "a" respectively.
#   In the script below, this graph is named "exp graph".
#
# * The logarithm function y=log(x).
#   The point (a, log(a)) is added to the graph.
#   This graph is named "log graph".
#
# * Another graph that arranges the above two graphs side by side
#   is also output.
#   This graph is named "parallel graph".
#
# This script outputs the graph animation files (MP4 and WebM formats).
# The animations illustrate the variations in the above graphs
# when the "a" is continuously varied from 1.1 to 5.
# These animations are named "exp animation", "log animation", and
# "parallel animation" respectively.
#
# This script also outputs another animation that plays both
# the "exp animation" and the "log animation" serially.
# This animation is named "serial animation".
#
# The "exp animation" and the "log animation" are grouped together and
# named "single animations".
# Similarly, the "parallel animation" and the "serial animation"
# are grouped together and named "double animations"

# The "a" in the following script represents the above "a".
# It is not an English "indefinite article".

a_min, a_max = 1.1, 5

def create_a_list():
    l=srange(a_min, a_max, 0.02, include_endpoint=True) + [e]
    l.sort()
    return l

def format_a(a):
    return r'\mathrm{e}' if a == e else format(float(a), '.2f')

def format_log_text(a, log_a):
    t= r'$\log(a=' + format_a(a) + ')'

    if a == e:
        t+= '=1'
    else:
        t+= r'\approx' + format(float(round(log_a, 3)), '.3f')
        if log_a < 1:
            t+= '<1'
        else:
            t+= '>1'

    t+= '$'
    return t

def format_graph(g):
    g.axes_labels(['$x$', '$y$'])
    g.fontsize(14)
    g.axes_labels_size(1.3)
    g.set_legend_options(font_size='xx-large', labelspacing=0.5)
    g.set_aspect_ratio(1)

tick_formats={'ticks_integer':True, 'tick_formatter':'latex'}

text_formats={'axis_coords':True, 'horizontal_alignment':'left', \
    'fontsize':'xx-large', 'rgbcolor':'black', \
    'bounding_box':{'boxstyle':'square', 'fc':'w', 'ec':'darkgray'}}

def create_exp_graph(a):
    x_min, x_max = -1, 3

    g=plot([a^x, (a^x)*log(a)], (x, x_min, x_max), \
        legend_label=['$a^x$', "$(a^x)'$"], **tick_formats)

    g+=text('$a=' + format_a(a) + '$', (0.785, 0.64), **text_formats)

    format_graph(g)
    g.set_legend_options(loc='upper right')
    g.set_axes_range(x_min, x_max, 0, 3.5)

    return g

def create_log_graph(a):
    x_min, x_max = 0.5, (a_max +0.5)
    x_axes_min, x_axes_max = (x_min -0.1), (x_max +0.1)
    log_a = log(a)

    g=plot(log(x), (x, x_min, x_max), \
        legend_label=r'$\log(x)$', **tick_formats)

    g+=text(format_log_text(a, log_a), (0.38, 0.92), **text_formats)

    g+=line([(x_axes_min, 1), (x_axes_max, 1)], \
        rgbcolor='black', thickness=0.2)

    g+=point((a, log_a), rgbcolor='red', pointsize=80, \
        legend_label=r'$\left(a, \log(a) \right)$')

    g+=line([(a, log_a), (a, 0)], \
        rgbcolor='red', thickness=0.3)

    g+=arrow((a, log_a), (x_axes_min, log_a), \
        rgbcolor='red', width=0.3, arrowsize=4)

    format_graph(g)
    g.set_legend_options(loc='upper left')
    g.set_axes_range(x_min, x_max, -0.7, 3.0)

    return g

def make_parallel_graph(exp_graph, log_graph):
    return multi_graphics([ \
        (exp_graph, (0   , 0, 0.45, 0.45)), \
        (log_graph, (0.43, 0, 0.5 , 0.45))])

parallel_graph_formats={'figsize':[13, 8.7]}

# Write the graph image files of "exp", "log", and "parallel"
def write_images():
    for a in [2, e, 4]:
        exp_graph = create_exp_graph(a)
        log_graph = create_log_graph(a)
        parallel_graph = make_parallel_graph(exp_graph, log_graph)

        exp_graph.save('exp-' + str(a) + '.svg')
        log_graph.save('log-' + str(a) + '.svg')
        parallel_graph.save('parallel-' + str(a) + '.svg', \
            **parallel_graph_formats)

# Create the lists of "exp graph" and "log graph" respectively
def create_exp_log_graph_lists():
    a_list = create_a_list()
    exp_graph_list = [create_exp_graph(a) for a in a_list]
    log_graph_list = [create_log_graph(a) for a in a_list]
    return exp_graph_list, log_graph_list

animation_ext_list = ['.mp4', '.webm']

def write_single_animations():
    exp_graph_list, log_graph_list = create_exp_log_graph_lists()

    for file_ext in animation_ext_list:
        animate(exp_graph_list).save('exp' + file_ext)
        animate(log_graph_list).save('log' + file_ext)

def write_parallel_animation(exp_graph_list, log_graph_list):
    parallel_graph_list = [make_parallel_graph(exp_graph, log_graph) \
        for exp_graph, log_graph in zip(exp_graph_list, log_graph_list)]

    for file_ext in animation_ext_list:
        animate(parallel_graph_list, **parallel_graph_formats).save( \
            'parallel' + file_ext)

def write_serial_animation(exp_graph_list, log_graph_list):
    for log_graph in log_graph_list:
        log_graph.set_legend_options(font_size='x-large')

    serial_graph_list = exp_graph_list + log_graph_list

    serial_graph_formats={'figsize':[5.0, 5.0], 'fig_tight':False, \
        'aspect_ratio':'automatic'}

    for file_ext in animation_ext_list:
        animate(serial_graph_list, **serial_graph_formats).save( \
            'serial' + file_ext)

def write_double_animations():
    exp_graph_list, log_graph_list = create_exp_log_graph_lists()

    write_parallel_animation(exp_graph_list, log_graph_list)
    write_serial_animation(exp_graph_list, log_graph_list)

write_images()
write_single_animations()
write_double_animations()

このスクリプトは、アニメーションを作成するために、FFmpeg を使用します。

静止画は、SVG形式で出力されます。 また、アニメーションは、MP4 と WebM の2種類の形式で出力されます。


上記のスクリプトで出力される MP4 ファイルのプロファイルは、「High 4:4:4 Predictive」です。 このプロファイルの MP4 ファイルを、正常に表示できないアプリケーションも存在します。

そこで、MP4 ファイルを、「Main」プロファイルに変換するための、FFmpeg を使用した、シェルスクリプトを、以下に示します。

#!/bin/sh

# This shell script have been tested with FFmpeg 3.4.8 on Ubuntu 18.04.

# This script converts "High 4:4:4 Predictive profile" of 
# the MP4 files (H.264 codec) into "Main profile".
#
# The resolution (width and height) after the conversion
# must both be an even number.
# Therefore, if the width or height of the frame in the input file
# is an odd number, the corresponding edge of the frame is deleted
# by one pixel.
# Since the input videos have margins, the above deletion is not
# a problem.

for target_basename in exp log parallel serial
do
  input_filename="${target_basename}.mp4"    # e.g. exp.mp4
  output_filename="${target_basename}-m.mp4" # e.g. exp-m.mp4

  ffmpeg -y -i "${input_filename}" -codec:v libx264 -profile:v main \
    -pix_fmt yuv420p -vf "crop=trunc(iw/2)*2:trunc(ih/2)*2" \
    "${output_filename}"
done

この2つのスクリプトで作成される、静止画とアニメーションのほとんどは、前のページに掲載されています。 掲載されていないのは、指数関数アニメーションと対数関数アニメーションの両方を、連続して再生するアニメーションです。 上記のスクリプトでは、そのアニメーションを「連続アニメーション」と名付けています。 そのアニメーションのリンクを、以下に示します。

この「連続アニメーション」は、前のページの内容を、別の Web サイトで紹介するときに、動画として表示することを想定して、作成されました。


前のページ: 指数関数と導関数のアニメーション(学生向け)

コメント

このブログの人気の投稿